Compare commits

..

21 Commits

Author SHA1 Message Date
Anselm
8def1bb29e IndexedDB & timer perf improvements 2023-12-20 11:04:45 +00:00
Anselm
d379b04e33 Publish 2023-12-18 15:15:27 +00:00
Anselm
17a30e054e Add passphrase based auth + example 2023-12-18 15:13:37 +00:00
Anselm
7d8f4b4c00 Publish jazz-browser-auth0 2023-12-05 19:12:16 +00:00
Anselm
e2a3896bf0 v0.0.0 2023-12-05 19:11:00 +00:00
Anselm
446de8e0ff Fix for not receiving user_metadata 2023-12-05 19:10:15 +00:00
Anselm
5ae6c95878 Use authorization params everywhere 2023-12-05 17:07:18 +00:00
Anselm
7cde349a50 Actually deploy auth0 example 2023-12-05 16:45:41 +00:00
Anselm
61e640f574 Fix auth0 example dependencies 2023-12-05 16:43:18 +00:00
Anselm
ed122d9d8e Publish 2023-12-05 16:41:12 +00:00
Anselm
34817f4536 Auth0 integration and example 2023-12-05 16:38:37 +00:00
Anselm
0998a0eabf Correct homepage path 2023-11-24 17:09:34 +00:00
Anselm
a96108478b Homepage updates 2023-11-24 17:04:03 +00:00
Anselm
a4cf4c40d4 publish 2023-11-08 11:42:36 +00:00
Anselm Eickhoff
934fe4d29b Merge pull request #127 from KyleAMathews/main
fix(jazz-nodejs): also set peersToLoadFrom on newly created accounts
2023-11-07 17:18:02 +00:00
Anselm Eickhoff
408012f2e5 remove redundant peer add 2023-11-07 17:17:41 +00:00
Kyle Mathews
d0078b830e fix(jazz-nodejs): also set peersToLoadFrom on newly created accounts 2023-11-03 14:58:43 -07:00
Anselm Eickhoff
e52948b2b7 Merge pull request #125 from gardencmp/jazz-nodejs
jazz-nodejs MVP
2023-11-02 12:11:56 +00:00
Anselm
53bb1b230b Fix interpolation 2023-11-02 11:53:19 +00:00
Anselm
54e83aeaaa use NOMAD_ADDR secret 2023-11-02 11:43:32 +00:00
Anselm
aa3129cab5 Provide localNode to AccountMigrations 2023-10-27 14:48:36 +01:00
99 changed files with 3781 additions and 687 deletions

View File

@@ -12,7 +12,7 @@ jobs:
strategy:
matrix:
# example: ["chat", "todo", "pets", "twit", "file-drop"]
example: ["twit", "chat"]
example: ["twit", "chat", "counter-js-auth0"]
steps:
- uses: actions/checkout@v3
@@ -54,32 +54,32 @@ jobs:
cache-from: type=gha
cache-to: type=gha,mode=max
# build-homepage:
# runs-on: ubuntu-latest
build-homepage:
runs-on: ubuntu-latest
# steps:
# - uses: actions/checkout@v3
# with:
# submodules: true
steps:
- uses: actions/checkout@v3
with:
submodules: true
# - name: Set up Docker Buildx
# uses: docker/setup-buildx-action@v2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
# - name: Login to GitHub Container Registry
# uses: docker/login-action@v2
# with:
# registry: ghcr.io
# username: gardencmp
# password: ${{ secrets.GITHUB_TOKEN }}
- name: Login to GitHub Container Registry
uses: docker/login-action@v2
with:
registry: ghcr.io
username: gardencmp
password: ${{ secrets.GITHUB_TOKEN }}
# - name: Docker Build & Push
# uses: docker/build-push-action@v4
# with:
# context: ./homepage/homepage-jazz
# push: true
# tags: ghcr.io/gardencmp/${{github.event.repository.name}}-homepage-jazz:${{github.head_ref || github.ref_name}}-${{github.sha}}-${{github.run_number}}-${{github.run_attempt}}
# cache-from: type=gha
# cache-to: type=gha,mode=max
- name: Docker Build & Push
uses: docker/build-push-action@v4
with:
context: ./homepage
push: true
tags: ghcr.io/gardencmp/${{github.event.repository.name}}-homepage-jazz:${{github.head_ref || github.ref_name}}-${{github.sha}}-${{github.run_number}}-${{github.run_attempt}}
cache-from: type=gha
cache-to: type=gha,mode=max
deploy-examples:
runs-on: ubuntu-latest
@@ -87,7 +87,7 @@ jobs:
strategy:
matrix:
# example: ["chat", "todo", "pets", "twit", "file-drop"]
example: ["twit", "chat"]
example: ["twit", "chat", "counter-js-auth0"]
steps:
- uses: actions/checkout@v3
@@ -115,38 +115,38 @@ jobs:
envsubst '${DOCKER_USER} ${DOCKER_PASSWORD} ${DOCKER_TAG} ${BRANCH_SUFFIX} ${BRANCH_SUBDOMAIN}' < job-template.nomad > job-instance.nomad;
cat job-instance.nomad;
NOMAD_ADDR='http://control1v2-london:4646' nomad job run job-instance.nomad;
NOMAD_ADDR=${{ secrets.NOMAD_ADDR }} nomad job run job-instance.nomad;
working-directory: ./examples/${{ matrix.example }}
# deploy-homepage:
# runs-on: ubuntu-latest
# needs: build-homepage
deploy-homepage:
runs-on: ubuntu-latest
needs: build-homepage
# steps:
# - uses: actions/checkout@v3
# with:
# submodules: true
# - uses: gacts/install-nomad@v1
# - name: Tailscale
# uses: tailscale/github-action@v1
# with:
# authkey: ${{ secrets.TAILSCALE_AUTHKEY }}
steps:
- uses: actions/checkout@v3
with:
submodules: true
- uses: gacts/install-nomad@v1
- name: Tailscale
uses: tailscale/github-action@v1
with:
authkey: ${{ secrets.TAILSCALE_AUTHKEY }}
# - name: Deploy on Nomad
# run: |
# if [ "${{github.ref_name}}" == "main" ]; then
# export BRANCH_SUFFIX="";
# export BRANCH_SUBDOMAIN="";
# else
# export BRANCH_SUFFIX=-${{github.head_ref || github.ref_name}};
# export BRANCH_SUBDOMAIN=${{github.head_ref || github.ref_name}}.;
# fi
- name: Deploy on Nomad
run: |
if [ "${{github.ref_name}}" == "main" ]; then
export BRANCH_SUFFIX="";
export BRANCH_SUBDOMAIN="";
else
export BRANCH_SUFFIX=-${{github.head_ref || github.ref_name}};
export BRANCH_SUBDOMAIN=${{github.head_ref || github.ref_name}}.;
fi
# export DOCKER_USER=gardencmp;
# export DOCKER_PASSWORD=${{ secrets.DOCKER_PULL_PAT }};
# export DOCKER_TAG=ghcr.io/gardencmp/${{github.event.repository.name}}-homepage-jazz:${{github.head_ref || github.ref_name}}-${{github.sha}}-${{github.run_number}}-${{github.run_attempt}};
export DOCKER_USER=gardencmp;
export DOCKER_PASSWORD=${{ secrets.DOCKER_PULL_PAT }};
export DOCKER_TAG=ghcr.io/gardencmp/${{github.event.repository.name}}-homepage-jazz:${{github.head_ref || github.ref_name}}-${{github.sha}}-${{github.run_number}}-${{github.run_attempt}};
# envsubst '${DOCKER_USER} ${DOCKER_PASSWORD} ${DOCKER_TAG} ${BRANCH_SUFFIX} ${BRANCH_SUBDOMAIN}' < job-template.nomad > job-instance.nomad;
# cat job-instance.nomad;
# NOMAD_ADDR='http://control1v2-london:4646' nomad job run job-instance.nomad;
# working-directory: ./homepage/homepage-jazz
envsubst '${DOCKER_USER} ${DOCKER_PASSWORD} ${DOCKER_TAG} ${BRANCH_SUFFIX} ${BRANCH_SUBDOMAIN}' < job-template.nomad > job-instance.nomad;
cat job-instance.nomad;
NOMAD_ADDR=${{ secrets.NOMAD_ADDR }} nomad job run job-instance.nomad;
working-directory: ./homepage

View File

@@ -0,0 +1,18 @@
module.exports = {
root: true,
env: { browser: true, es2020: true },
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:react-hooks/recommended',
],
ignorePatterns: ['dist', '.eslintrc.cjs'],
parser: '@typescript-eslint/parser',
plugins: ['react-refresh'],
rules: {
'react-refresh/only-export-components': [
'warn',
{ allowConstantExport: true },
],
},
}

24
examples/chat-passphrase/.gitignore vendored Normal file
View File

@@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

View File

@@ -0,0 +1,17 @@
# jazz-example-chat
## 0.0.47
### Patch Changes
- Implement passphrase based auth
- Updated dependencies
- jazz-react-auth-passphrase@0.5.1
## 0.0.46
### Patch Changes
- Updated dependencies
- jazz-react@0.5.0
- jazz-react-auth-local@0.4.16

View File

@@ -0,0 +1,4 @@
FROM caddy:2.7.3-alpine
LABEL org.opencontainers.image.source="https://github.com/gardencmp/jazz"
COPY ./dist /usr/share/caddy/

View File

@@ -0,0 +1,64 @@
# Jazz Todo List Example
Live version: https://example-todo.jazz.tools
## Installing & running the example locally
Start by checking out just the example app to a folder:
```bash
npx degit gardencmp/jazz/examples/todo jazz-example-todo
cd jazz-example-todo
```
(This ensures that you have the example app without git history or our multi-package monorepo)
Install dependencies:
```bash
npm install
```
Start the dev server:
```bash
npm run dev
```
## Structure
- [`src/basicComponents`](./src/basicComponents): simple components to build the UI, unrelated to Jazz (uses [shadcn/ui](https://ui.shadcn.com))
- [`src/components`](./src/components/): helper components that do contain Jazz-specific logic, but aren't very relevant to understand the basics of Jazz and CoJSON
- [`src/1_types.ts`](./src/1_types.ts),
[`src/2_main.tsx`](./src/2_main.tsx),
[`src/3_NewProjectForm.tsx`](./src/3_NewProjectForm.tsx),
[`src/4_ProjectTodoTable.tsx`](./src/4_ProjectTodoTable.tsx): the main files for this example, see the walkthrough below
## Walkthrough
### Main parts
1. Defining the data model with CoJSON: [`src/1_types.ts`](./src/1_types.ts)
2. The top-level provider `<WithJazz/>` and routing: [`src/2_main.tsx`](./src/2_main.tsx)
3. Creating a new todo project: [`src/3_NewProjectForm.tsx`](./src/3_NewProjectForm.tsx)
4. Reactively rendering a todo project as a table, adding and editing tasks: [`src/4_ProjectTodoTable.tsx`](./src/4_ProjectTodoTable.tsx)
### Helpers
- (not yet explained) Creating invite links/QR codes with `<InviteButton/>`: [`src/components/InviteButton.tsx`](./src/components/InviteButton.tsx)
This is the whole Todo List app!
## Questions / problems / feedback
If you have feedback, let us know on [Discord](https://discord.gg/utDMjHYg42) or open an issue or PR to fix something that seems wrong.
## Configuration: sync server
By default, the example app uses [Jazz Global Mesh](https://jazz.tools/mesh) (`wss://sync.jazz.tools`) - so cross-device use, invites and collaboration should just work.
You can also run a local sync server by running `npx cojson-simple-sync` and adding the query param `?sync=ws://localhost:4200` to the URL of the example app (for example: `http://localhost:5173/?sync=ws://localhost:4200`), or by setting the `sync` parameter of the `<WithJazz>` provider component in [./src/2_main.tsx](./src/2_main.tsx).

View File

@@ -0,0 +1,14 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/png" href="/jazz-logo.png" />
<link rel="stylesheet" href="/src/index.css" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Jazz Chat Example</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/app.tsx"></script>
</body>
</html>

View File

@@ -0,0 +1,56 @@
job "chat$BRANCH_SUFFIX" {
region = "global"
datacenters = ["*"]
group "static" {
count = 4
network {
port "http" {
to = 80
}
}
constraint {
attribute = "${node.class}"
operator = "="
value = "mesh"
}
spread {
attribute = "${node.datacenter}"
weight = 100
}
constraint {
distinct_hosts = true
}
task "server" {
driver = "docker"
config {
image = "$DOCKER_TAG"
ports = ["http"]
auth = {
username = "$DOCKER_USER"
password = "$DOCKER_PASSWORD"
}
}
service {
tags = ["public"]
name = "chat$BRANCH_SUFFIX"
port = "http"
provider = "consul"
}
resources {
cpu = 50 # MHz
memory = 50 # MB
}
}
}
}
# deploy bump 4

View File

@@ -0,0 +1,48 @@
{
"name": "jazz-example-chat-passphrase",
"private": true,
"version": "0.0.47",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
"preview": "vite preview"
},
"dependencies": {
"@radix-ui/react-checkbox": "^1.0.4",
"@radix-ui/react-slot": "^1.0.2",
"@radix-ui/react-toast": "^1.1.4",
"@types/qrcode": "^1.5.1",
"class-variance-authority": "^0.7.0",
"clsx": "^2.0.0",
"hash-slash": "^0.1.3",
"jazz-react": "^0.5.0",
"jazz-react-auth-passphrase": "^0.5.1",
"lucide-react": "^0.274.0",
"qrcode": "^1.5.3",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-router": "^6.16.0",
"react-router-dom": "^6.16.0",
"react-use": "^17.4.0",
"tailwind-merge": "^1.14.0",
"tailwindcss-animate": "^1.0.7",
"uniqolor": "^1.1.0"
},
"devDependencies": {
"@types/react": "^18.2.15",
"@types/react-dom": "^18.2.7",
"@typescript-eslint/eslint-plugin": "^6.0.0",
"@typescript-eslint/parser": "^6.0.0",
"@vitejs/plugin-react-swc": "^3.3.2",
"autoprefixer": "^10.4.14",
"eslint": "^8.45.0",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-refresh": "^0.4.3",
"postcss": "^8.4.27",
"tailwindcss": "^3.3.3",
"typescript": "^5.0.2",
"vite": "^4.4.5"
}
}

View File

@@ -0,0 +1,6 @@
export default {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

View File

@@ -0,0 +1,45 @@
import { WithJazz, useJazz } from 'jazz-react';
import { PassphraseAuth, PassphraseAuthBasicUI } from 'jazz-react-auth-passphrase';
import ReactDOM from 'react-dom/client';
import { HashRoute } from 'hash-slash';
import { ChatWindow } from './chatWindow.tsx';
import { Chat } from './dataModel.ts';
import {wordlist} from '@scure/bip39/wordlists/english';
ReactDOM.createRoot(document.getElementById('root')!).render(
<WithJazz
auth={PassphraseAuth({
appName: 'Jazz Chat Example',
wordlist,
Component: PassphraseAuthBasicUI
})}
apiKey="api_z9d034j3t34ht034ir"
>
<App />
</WithJazz>,
);
function App() {
return <div className='flex flex-col items-center justify-between w-screen h-screen p-2 dark:bg-black dark:text-white'>
<button onClick={useJazz().logOut} className='rounded mb-5 px-2 py-1 bg-stone-200 dark:bg-stone-800 dark:text-white self-end'>
Log Out
</button>
{HashRoute({
'/': <Home />,
'/chat/:id': (id) => <ChatWindow chatId={id as Chat['id']} />,
}, { reportToParentFrame: true })}
</div>
}
function Home() {
const { me } = useJazz();
return <button className='rounded py-2 px-4 bg-stone-200 dark:bg-stone-800 dark:text-white my-auto'
onClick={() => {
const group = me.createGroup().addMember('everyone', 'writer');
const chat = group.createList<Chat>();
location.hash = '/chat/' + chat.id;
}}>
Create New Chat
</button>
}

View File

@@ -0,0 +1,43 @@
import { useAutoSub } from 'jazz-react';
import { Chat, Message } from './dataModel.ts';
export function ChatWindow(props: { chatId: Chat['id'] }) {
const chat = useAutoSub(props.chatId);
return chat ? <div className='w-full max-w-xl h-full flex flex-col items-stretch'>
{
chat.map((msg, i) => (
<ChatBubble key={msg?.id}
text={msg?.text}
by={chat.meta.edits[i].by?.profile?.name}
byMe={chat.meta.edits[i].by?.isMe}
at={chat.meta.edits[i].at} />
))
}
<ChatInput onSubmit={(text) => {
const msg = chat.meta.group.createMap<Message>({ text });
chat.append(msg.id);
}}/>
</div> : <div>Loading...</div>;
}
function ChatBubble(props: { text?: string, by?: string, at?: Date, byMe?: boolean }) {
return <div className={`${props.byMe ? 'items-end' : 'items-start'} flex flex-col`}>
<div className='rounded-xl bg-stone-100 dark:bg-stone-700 dark:text-white py-2 px-4 mt-2 min-w-[5rem]'>
{ props.text }
</div>
<div className='text-xs text-neutral-500 ml-2'>
{ props.by } { props.at?.getHours() }:{ props.at?.getMinutes() }
</div>
</div>;
}
function ChatInput(props: { onSubmit: (text: string) => void }) {
return <input className='rounded p-2 border mt-auto dark:bg-black dark:text-white dark:border-stone-700'
placeholder='Type a message and press Enter'
onKeyDown={({ key, currentTarget: input }) => {
if (key !== 'Enter' || !input.value) return;
props.onSubmit(input.value);
input.value = '';
}}/>
}

View File

@@ -0,0 +1,4 @@
import { CoMap, CoList } from 'cojson';
export type Chat = CoList<Message['id']>;
export type Message = CoMap<{ text: string }>;

View File

@@ -0,0 +1,78 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
:root {
--background: 0 0% 100%;
--foreground: 20 14.3% 4.1%;
--card: 0 0% 100%;
--card-foreground: 20 14.3% 4.1%;
--popover: 0 0% 100%;
--popover-foreground: 20 14.3% 4.1%;
--primary: 24 9.8% 10%;
--primary-foreground: 60 9.1% 97.8%;
--secondary: 60 4.8% 95.9%;
--secondary-foreground: 24 9.8% 10%;
--muted: 60 4.8% 95.9%;
--muted-foreground: 25 5.3% 44.7%;
--accent: 60 4.8% 95.9%;
--accent-foreground: 24 9.8% 10%;
--destructive: 0 84.2% 60.2%;
--destructive-foreground: 60 9.1% 97.8%;
--border: 20 5.9% 90%;
--input: 20 5.9% 90%;
--ring: 20 14.3% 4.1%;
--radius: 0.5rem;
}
.dark {
--background: 20 14.3% 4.1%;
--foreground: 60 9.1% 97.8%;
--card: 20 14.3% 4.1%;
--card-foreground: 60 9.1% 97.8%;
--popover: 20 14.3% 4.1%;
--popover-foreground: 60 9.1% 97.8%;
--primary: 60 9.1% 97.8%;
--primary-foreground: 24 9.8% 10%;
--secondary: 12 6.5% 15.1%;
--secondary-foreground: 60 9.1% 97.8%;
--muted: 12 6.5% 15.1%;
--muted-foreground: 24 5.4% 63.9%;
--accent: 12 6.5% 15.1%;
--accent-foreground: 60 9.1% 97.8%;
--destructive: 0 62.8% 30.6%;
--destructive-foreground: 60 9.1% 97.8%;
--border: 12 6.5% 15.1%;
--input: 12 6.5% 15.1%;
--ring: 24 5.7% 82.9%;
}
}
@layer base {
* {
@apply border-border;
}
body {
@apply bg-background text-foreground;
margin: 0;
padding: 0;
}
}

View File

@@ -0,0 +1 @@
/// <reference types="vite/client" />

View File

@@ -0,0 +1,75 @@
/** @type {import('tailwindcss').Config} */
module.exports = {
content: [
'./pages/**/*.{ts,tsx}',
'./components/**/*.{ts,tsx}',
'./app/**/*.{ts,tsx}',
'./src/**/*.{ts,tsx}',
],
theme: {
container: {
center: true,
padding: "2rem",
screens: {
"2xl": "1400px",
},
},
extend: {
colors: {
border: "hsl(var(--border))",
input: "hsl(var(--input))",
ring: "hsl(var(--ring))",
background: "hsl(var(--background))",
foreground: "hsl(var(--foreground))",
primary: {
DEFAULT: "hsl(var(--primary))",
foreground: "hsl(var(--primary-foreground))",
},
secondary: {
DEFAULT: "hsl(var(--secondary))",
foreground: "hsl(var(--secondary-foreground))",
},
destructive: {
DEFAULT: "hsl(var(--destructive))",
foreground: "hsl(var(--destructive-foreground))",
},
muted: {
DEFAULT: "hsl(var(--muted))",
foreground: "hsl(var(--muted-foreground))",
},
accent: {
DEFAULT: "hsl(var(--accent))",
foreground: "hsl(var(--accent-foreground))",
},
popover: {
DEFAULT: "hsl(var(--popover))",
foreground: "hsl(var(--popover-foreground))",
},
card: {
DEFAULT: "hsl(var(--card))",
foreground: "hsl(var(--card-foreground))",
},
},
borderRadius: {
lg: "var(--radius)",
md: "calc(var(--radius) - 2px)",
sm: "calc(var(--radius) - 4px)",
},
keyframes: {
"accordion-down": {
from: { height: 0 },
to: { height: "var(--radix-accordion-content-height)" },
},
"accordion-up": {
from: { height: "var(--radix-accordion-content-height)" },
to: { height: 0 },
},
},
animation: {
"accordion-down": "accordion-down 0.2s ease-out",
"accordion-up": "accordion-up 0.2s ease-out",
},
},
},
plugins: [require("tailwindcss-animate")],
}

View File

@@ -0,0 +1,29 @@
{
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"lib": ["ES2023", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx",
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
}
},
"include": ["src"],
"references": [{ "path": "./tsconfig.node.json" }]
}

View File

@@ -0,0 +1,10 @@
{
"compilerOptions": {
"composite": true,
"skipLibCheck": true,
"module": "ESNext",
"moduleResolution": "bundler",
"allowSyntheticDefaultImports": true
},
"include": ["vite.config.ts"]
}

View File

@@ -0,0 +1,16 @@
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react-swc'
import path from "path";
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react()],
resolve: {
alias: {
"@": path.resolve(__dirname, "./src"),
},
},
build: {
minify: false
}
})

24
examples/counter-js-auth0/.gitignore vendored Normal file
View File

@@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

View File

@@ -0,0 +1,4 @@
FROM caddy:2.7.3-alpine
LABEL org.opencontainers.image.source="https://github.com/gardencmp/jazz"
COPY ./dist /usr/share/caddy/

View File

@@ -0,0 +1,16 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Jazz Counter Example (Auth0)</title>
</head>
<body>
<div id="root">
<div id="count">...initializing</div>
<button id="increment">Increment</button>
<button id="login">Login</button>
</div>
<script type="module" src="/index.ts"></script>
</body>
</html>

View File

@@ -0,0 +1,113 @@
import * as auth0 from "@auth0/auth0-spa-js";
import { Account, CoID, CoList, CoMap, Profile } from "cojson";
import {
ResolvedAccount,
autoSub,
autoSubResolution,
createBrowserNode,
} from "jazz-browser";
import { BrowserAuth0 } from "jazz-browser-auth0";
const auth0options = {
domain: "dev-12uyj8w4t4yjzkwa.us.auth0.com",
clientId: "TcYtq9an3PDyInQJvD1k8PtqupYG4PnA",
};
const authorizationParams = {
redirect_uri: window.location.origin,
audience: `https://${auth0options.domain}/api/v2/`,
scope: "read:current_user update:current_user_metadata",
};
window.onload = async () => {
const auth0Client = await auth0.createAuth0Client(auth0options);
const query = window.location.search;
if (query.includes("code=") && query.includes("state=")) {
await auth0Client.handleRedirectCallback();
window.history.replaceState({}, document.title, "/");
} else {
document
.getElementById("login")
?.addEventListener("click", async () => {
await auth0Client.loginWithRedirect({
authorizationParams,
});
});
}
if (!(await auth0Client.isAuthenticated())) {
return;
}
let accessToken: string | undefined;
try {
accessToken = await auth0Client.getTokenSilently({
authorizationParams,
});
} catch (e) {
alert(
"Failed to get access token silently, creating popup - this should only happen on localhost. Otherwise, check that allowed callback URLs are set correctly."
);
accessToken = await auth0Client.getTokenWithPopup({
authorizationParams,
});
}
if (!accessToken) {
throw new Error("No access token");
}
const { node, done } = await createBrowserNode({
auth: new BrowserAuth0(
{
domain: auth0options.domain,
clientID: auth0options.clientId,
accessToken,
},
auth0options.domain,
"jazz-browser-auth0-example"
),
migration: (account) => {
if (!account.get("root")) {
account.set(
"root",
account.createMap({
countIncrements: account.createList([]).id,
}).id
);
}
},
});
autoSub<Account<Profile, CoMap<{ countIncrements: CoID<CoList<number>> }>>>(
"me",
node,
(me) => {
if (me?.root?.countIncrements) {
document.getElementById("count")!.innerText =
me.root.countIncrements.reduce((sum, inc) => sum + inc, 0) +
"";
}
}
);
const increments = await autoSubResolution(
"me",
(
me: ResolvedAccount<
Account<
Profile,
CoMap<{ countIncrements: CoID<CoList<number>> }>
>
>
) => {
return me?.root?.countIncrements;
},
node
);
document.getElementById("increment")!.addEventListener("click", () => {
increments.append(1);
});
};

View File

@@ -0,0 +1,56 @@
job "counter-js-auth0$BRANCH_SUFFIX" {
region = "global"
datacenters = ["*"]
group "static" {
count = 4
network {
port "http" {
to = 80
}
}
constraint {
attribute = "${node.class}"
operator = "="
value = "mesh"
}
spread {
attribute = "${node.datacenter}"
weight = 100
}
constraint {
distinct_hosts = true
}
task "server" {
driver = "docker"
config {
image = "$DOCKER_TAG"
ports = ["http"]
auth = {
username = "$DOCKER_USER"
password = "$DOCKER_PASSWORD"
}
}
service {
tags = ["public"]
name = "counter-js-auth0$BRANCH_SUFFIX"
port = "http"
provider = "consul"
}
resources {
cpu = 50 # MHz
memory = 50 # MB
}
}
}
}
# deploy bump 4

View File

@@ -0,0 +1,22 @@
{
"name": "counter-js-auth0",
"private": true,
"version": "0.0.46",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
"preview": "vite preview"
},
"dependencies": {
"@auth0/auth0-spa-js": "^2.1.2",
"jazz-browser": "^0.6.0",
"jazz-browser-auth0": "^0.6.2"
},
"devDependencies": {
"@vitejs/plugin-react-swc": "^3.3.2",
"typescript": "^5.0.2",
"vite": "^4.4.5"
}
}

View File

@@ -0,0 +1,29 @@
{
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"lib": ["ES2023", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx",
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
}
},
"include": ["src"],
"references": [{ "path": "./tsconfig.node.json" }]
}

View File

@@ -0,0 +1,10 @@
{
"compilerOptions": {
"composite": true,
"skipLibCheck": true,
"module": "ESNext",
"moduleResolution": "bundler",
"allowSyntheticDefaultImports": true
},
"include": ["vite.config.ts"]
}

View File

@@ -0,0 +1,16 @@
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react-swc'
import path from "path";
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react()],
resolve: {
alias: {
"@": path.resolve(__dirname, "./src"),
},
},
build: {
minify: false
}
})

View File

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -49,7 +49,7 @@ export default function RootLayout({
mainLogo={<JazzLogo className="w-24" />}
items={[
{ title: "Toolkit", href: "/" },
{ title: "Global Mesh", href: "/mesh" },
{ title: "Mesh / Pricing", href: "/mesh" },
{
title: "Docs & Guides",
href: "https://github.com/gardencmp/jazz/blob/main/DOCS.md",

248
homepage/app/mesh/page.mdx Normal file
View File

@@ -0,0 +1,248 @@
import { Slogan, Grid, GridCard, GridItem, ComingSoonBadge } from '@/components/forMdx';
import { fmtPrice, pricePer1MtxSyncedOut, pricePerTxSyncedOut, pricePer1MtxStored, pricePerTxStored } from '@/components/pricing';
export const metadata = {
title: "jazz - Jazz Mesh",
description: "Serverless sync & storage for Jazz apps.",
};
# Jazz Mesh
<Slogan>Serverless sync & storage for Jazz apps.</Slogan>
Real-time sync and storage infrastructure that scales up to millions of users.<br/>
Pricing that scales down to zero.
## The first Collaboration Delivery Network
<Slogan small>Build demanding apps with distributed state, backed by a new kind of cloud.</Slogan>
<Grid>
<GridCard>
#### Optimal mesh routing.
Get ultra-low latency between any group of users with our decentralized mesh interconnect.
</GridCard>
<GridCard>
#### Smart caching.
Give users instant load times, with their latest data state always cached close to them.
</GridCard>
<GridCard>
#### Blob storage & media streaming.
Store files and media streams as idiomatic `CoValues` without S3.
</GridCard>
</Grid>
## Pricing
<Slogan small></Slogan>
<Grid>
<GridCard>
### Mesh Free
<span className="text-2xl">$0</span>
- Unlimited projects
- For individual developers
- Low-latency sync
- Egress/mo: 5 million ops <span className="text-xs">or 50GB blobs</span>
- Storage: 2.5 million ops <span className="text-xs">or 25GB blobs</span>
</GridCard>
<GridCard>
### Mesh Starter <ComingSoonBadge/>
<span className="text-2xl">$9</span>/developer/mo
- Unlimited projects
- Up to 3 developers
- Low-latency sync
- Egress/mo: 50 million ops <span className="text-xs">or 500GB blobs</span>
- Storage: 25 million ops <span className="text-xs">or 250GB blobs</span>
<div className="text-xs">
- Extra egress: {fmtPrice(10 * pricePer1MtxSyncedOut)} per 10 million ops or 100GB blobs
- Extra storage: {fmtPrice(10 * pricePer1MtxStored)} per 10 million ops or 100GB blobs
</div>
</GridCard>
<GridCard>
### Mesh Pro <ComingSoonBadge/>
<span className="text-2xl">$19</span>/developer/mo
- Unlimited projects
- Up to 50 developers, SSO/SAML
- Ultra-low-latency sync
- Egress/mo: 100 million ops <span className="text-xs">or 1TB blobs</span>
- Storage: 50 million ops <span className="text-xs">or 500GB blobs</span>
<div className="text-xs">
- Extra egress: {fmtPrice(10 * pricePer1MtxSyncedOut)} per 10 million ops or 100GB blobs
- Extra storage: {fmtPrice(10 * pricePer1MtxStored)} per 10 million ops or 100GB blobs
</div>
</GridCard>
{/*<GridCard>
### Mesh Enterprise <ComingSoonBadge/>
<span className="text-2xl">Custom</span>
- Custom SLA
- Custom cloud deployment
- Dedicated support
- Audit logs
</GridCard>*/}
</Grid>
An operation represents an **individual user action**, or **10KB of data** for blobs/streams.
<Grid>
<GridItem className="col-start-1">
#### Egress:
<div className="text-sm">
- Operations sent out from Jazz Mesh, each counted once for every device it is synced out to.
- Depending on cache behavior each op should only be synced out once per connection, ideally once per device requesting it.
</div>
</GridItem>
<GridItem>
#### Operations stored:
<div className="text-sm">
- Operations that are continuously persisted.
- Includes backups, hot storage and edge caches.
</div>
</GridItem>
</Grid>
**Examples:**
The number of ops generated is highly app-specific and depends on user behaviour, but here are some examples:
<div className="text-sm">
- **Session A: 4 users co-oping 10 pages of text, typing them out as individual character inserts:**
- 3,000 inserts/page &times; 10 pages = 30,000 ops -> 30k ops stored
- 30,000 ops, each synced out to 3 other users -> 90k ops egress (one-time)
- **You could have ~50 such sessions/mo within Mesh Free** and you can keep storing ~80 such texts.
- **You could have ~500 such sessions/mo within Mesh Starter included usage** and you can keep storing ~800 such texts.
- **You could have ~1000 such sessions/mo within Mesh Pro included usage** and you can keep storing ~1600 such texts.
- Each further such session would cost you about {fmtPrice(90000 * pricePerTxSyncedOut)} and {fmtPrice(30000 * pricePerTxStored)} per month to keep storing the text.
- **Session B: 3 users collaborating on a canvas, moving shapes around at 10 FPS for 10s/min for 5 hours**
- 3 users &times; 10 FPS &times; 10s/min &times; 60min/h &times; 5h = 90k ops -> 90k ops stored
- 90k ops, each synced out to 2 other users -> 180k ops egress (one-time)
- **You could have ~20 such sessions/mo within Mesh Free** and you can keep storing 20 such canvases.
- **You could have ~250 such sessions/mo within Mesh Starter included usage** and you can keep storing 250 such canvases.
- **You could have ~500 such sessions/mo within Mesh Pro included usage** and you can keep storing 500 such canvases.
- Each further such session would cost you about {fmtPrice(180000 * pricePerTxSyncedOut)} and {fmtPrice(90000 * pricePerTxStored)} per month to keep storing the canvas.
- **Session C: A livestreamer streaming video (1GB total) to 25 viewers (combined live & on-demand)**
- 1GB = 100,000 ops (10KB each) -> 100k ops stored
- 100,000 ops, each synced out to 25 viewers -> 2.5M ops egress (one-time)
- **You could have ~2 such livestreams/mo within Mesh Free** and you can keep storing 25 such videos.
- **You could have ~20 such livestreams/mo within Mesh Starter included usage** and you can keep storing 250 such videos.
- **You could have ~40 such livestreams/mo within Mesh Pro included usage** and you can keep storing 500 such videos.
- Each further such livestream would cost you about {fmtPrice(2500000 * pricePerTxSyncedOut)} and {fmtPrice(100000 * pricePerTxStored)} per month to keep storing the video.
</div>
## Global Footprint
We're rapidly expanding our network of sync & storage nodes. This is our current best-effort coverage:
<Grid className="grid-cols-2 md:grid-cols-3 lg:grid-cols-4">
<GridItem>
<div className="text-sm">
**Under 50ms RTT**
- Frankfurt
- New York
- Newark
- North California
- North Virginia
- San Francisco
- Singapore
- Toronto
</div>
</GridItem>
<GridItem>
<div className="text-sm">
**Under 100ms RTT**
- Amsterdam
- Atlanta
- London
- Ohio
- Paris
</div>
</GridItem>
<GridItem>
<div className="text-sm">
**Under 200ms RTT**
- Bangalore
- Dallas
- Mumbai
- Oregon
**Under 300ms RTT**
- Seoul
- Tokyo
</div>
</GridItem>
<GridItem>
<div className="text-sm">
**Under 400ms RTT**
- Sao Paulo
- Sydney
**Under 500ms RTT**
- Cape Town
</div>
</GridItem>
</Grid>
### Enterprise
Custom deployment in the cloud, your private cloud, on-premises or hybrids?
SLAs and dedicated support? White-glove integration services?
Let's talk: <a href="mailto:hello@gcmp.io">hello@gcmp.io</a>
## Custom Deployment Scenarios
<Slogan>You can rely on Jazz Mesh. But you don't have to.</Slogan>
<p>Because Jazz is open-source, you can optionally run your own sync nodes &mdash; in a variety of setups.</p>
<Grid>
<GridCard>
#### Jazz Mesh + Data Backup Node.
<p className="no-prose text-base">Connect your users to Jazz Mesh for all its benefits, but also run and connect your own data backup node (just in case.)</p>
<div className="text-sm">
Extra costs:
- Instance costs for the backup node.
- Moderate self-hosted storage costs.
- Every op is additionally synced to your backup node and counted as synced out.
</div>
</GridCard>
<GridCard>
#### Jazz Mesh + DIY Mesh.
<p className="no-prose text-base">Connect your users to Jazz Mesh, or your own nodes as a lower-performance fallback. The two networks stay in constant sync.</p>
<div className="text-sm">
Extra costs:
- N × instance cost for your sync nodes.
- Typically moderate self-hosted egress costs.
- High self-hosted storage costs.
- Every op is additionally synced to your DIY mesh and counted as synced out.
</div>
</GridCard>
<GridCard>
#### Completely DIY Mesh.
<p className="no-prose text-base">Build your own network of sync and storage nodes.
Handle networking, security and backups yourself.</p>
<div className="text-sm">
Costs:
- N × instance cost for your sync nodes.
- Very high self-hosted egress costs.
- High self-hosted storage costs.
</div>
</GridCard>
</Grid>

View File

@@ -34,7 +34,7 @@ Jazz is an open-source toolkit for building apps with **sync** & **secure collab
<h2 className="md:mt-24">Hard things are easy now</h2>
Jazz replaces APIs, DBs and message queues with **a single new abstraction: CoJSON**.
Jazz replaces APIs, databases and message queues with **a single new abstraction: collaborative data**.
This means you get **built-in capabilities** that took best-in-class apps years to build:
@@ -71,20 +71,7 @@ This means you get **built-in capabilities** that took best-in-class apps years
</Grid>
</div>
## CoJSON
<Slogan small>The collaborative core.</Slogan>
Jazz is built around **CoJSON,** a new abstraction for **sync** & **secure collaborative data.** And while it does all the heavy lifting...
- **multi-device co-editing**
- **user identities & accounts**
- **permissions** & **roles**
- **sync** & **caching**
- **persistence**
...its API couldn't be simpler: CoJSON makes collaboration and secure access control feel like **inherent properties of your data**.
### Collaborative Values
## Collaborative Values
<Slogan small>Your new building blocks.</Slogan>
@@ -99,7 +86,7 @@ CoValues also **keep their full edit history,** including author metadata and po
<div className="text-sm">
- Collaborative key-value map
- Possible values:
- Immutable JSON & IDs of other CoValues
- Immutable JSON & other CoValues
</div>
</GridCard>
<GridCard>
@@ -107,7 +94,7 @@ CoValues also **keep their full edit history,** including author metadata and po
<div className="text-sm">
- Collaborative ordered list
- Possible items:
- Immutable JSON & IDs of other CoValues
- Immutable JSON & other CoValues
</div>
</GridCard>
<GridItem className="col-span-full lg:col-span-1 mb-10 lg:ml-4 [&>p]:m-0 pt-4">
@@ -139,7 +126,7 @@ A shocking amount of UI is text editing. CoJSON offers correct, versatile primit
### `CoStream`
<div className="text-sm">
- Collection of independent per-user items streams:
- Immutable JSON & IDs of other CoValues
- Immutable JSON & other CoValues
- Great for presence, reactions, polls, replies etc.
</div>
</GridCard>
@@ -185,7 +172,7 @@ A simple API to define access control from anywhere, verifiably enforced by encr
## The Jazz Toolkit
<Slogan small>Idiomatic bindings for CoJSON, with batteries included.</Slogan>
<Slogan small>A high-level toolkit for building apps around CoValues.</Slogan>
Supported environments:
<div className="text-sm">
@@ -264,15 +251,15 @@ Supported environments:
</GridCard>
</Grid>
## Global Mesh
## Jazz Mesh
<Slogan small>Serverless sync & storage for Jazz apps</Slogan>
To give you sync & secure collaborative data instantly on a global scale, we're running Global Mesh. It works with any CoJSON-based app, requires no setup and has straightforward, scale-to-zero pricing.
To give you sync and secure collaborative data instantly on a global scale, we're running Jazz Mesh. It works with any Jazz-based app, requires no setup and has straightforward, scale-to-zero pricing.
Global Mesh is currently free &mdash; and it's set up as the default sync & storage peer in Jazz, letting you start building multi-user apps with persistence right away, no backend needed.
Jazz Mesh is currently free &mdash; and it's set up as the default sync & storage peer in Jazz, letting you start building multi-user apps with persistence right away, no backend needed.
<Link href="/mesh" target="_blank">Learn more about Global Mesh</Link>
<Link href="/mesh" target="_blank">Learn more about Jazz Mesh</Link>
## Get Started

File diff suppressed because one or more lines are too long

View File

@@ -1,11 +1,19 @@
import { ComingSoonBadge, Grid, GridCard, GridItem } from "./forMdx";
export const pricePer1MtxSyncedOut = 1;
export const pricePer1MtxStored = 2;
export const pricePer1MtxSyncedOut = 0.1;
export const pricePer1MtxStored = 0.2;
export const pricePerTxSyncedOut = pricePer1MtxSyncedOut / 1_000_000;
export const pricePerTxStored = pricePer1MtxStored / 1_000_000;
export function fmtPrice(raw: number) {
return raw.toLocaleString("en-US", {
style: "currency",
currency: "USD",
maximumSignificantDigits: 2,
});
}
export function Pricing() {
const worstCaseBytesPerTx = 200_000;

View File

@@ -1,202 +0,0 @@
import { Slogan, Grid, GridCard, GridItem, ComingSoonBadge } from '@/components/forMdx';
import { pricePer1MtxSyncedOut, pricePerTxSyncedOut, pricePer1MtxStored, pricePerTxStored } from '@/components/pricing';
export const metadata = {
title: "jazz - Global Mesh",
description: "Serverless sync & storage for Jazz apps.",
};
# Jazz Global Mesh
<Slogan>Serverless sync & storage for Jazz apps.</Slogan>
Real-time sync and storage infrastructure that scales up to millions of users.<br/>
Pricing that scales down to zero.
## The first Collaboration Delivery Network
<Slogan small>Build demanding apps with distributed state, backed by a new kind of cloud.</Slogan>
<Grid>
<GridCard>
#### Optimal mesh routing.
Get ultra-low latency between any group of users with our decentralized mesh interconnect.
</GridCard>
<GridCard>
#### Smart caching.
Give users instant load times, with their latest data state always cached close to them.
</GridCard>
<GridCard>
#### Blob storage & media streaming.
Store files and media streams as idiomatic `CoValues` without S3.
</GridCard>
</Grid>
## Pricing
<Slogan small></Slogan>
### Free Tier
<span className="text-lg font-medium bg-emerald-200 dark:bg-emerald-800 px-2 py-1 rounded">Until we implement billing all usage of Global Mesh is free!</span>
<p className="text-sm">Later, any usage under $1/mo will be free.</p>
<Grid>
<GridItem className="md:col-span-2">
### Unlimited <ComingSoonBadge/>
<div className="lg:text-2xl border rounded-lg px-1 py-3 text-center">${pricePer1MtxSyncedOut} <small>per 1M TXs synced out</small> + ${pricePer1MtxStored}<small>/mo per 1M TXs stored</small></div>
<p><small>$6/mo minimum usage</small></p>
A TX (transaction) represents an **individual user action**, or **up to 100KB of binary data**.
</GridItem>
<GridItem className="col-start-1">
#### Transactions synced out:
<div className="text-sm">
- Transactions sent out from Global Mesh, each counted once for every device it is synced out to.
- Depending on cache behavior each transaction should only be synced out once per connection, ideally once per device requesting it.
</div>
</GridItem>
<GridItem>
#### Transactions stored:
<div className="text-sm">
- Transactions that are continuously persisted.
- Counted per second.
- Includes backups, hot storage and edge caches.
</div>
</GridItem>
</Grid>
**Examples:**
The number of transactions generated is highly app-specific and depends on user behaviour, but here are some examples:
<div className="text-sm">
- 4 users co-editing 10 pages of text, typing them out as individual character inserts:
- 3,000 inserts/page &times; 10 pages = 30,000 transactions
- 30,000 transactions stored = ${30000 * pricePerTxStored} / mo
- 3 &times; 30,000 transactions synced out = ${3 * 30000 * pricePerTxSyncedOut} one-time
- 4 users collaborating on a canvas, moving shapes around at 10 FPS for 10s/min for 2h/day for a month
- 4 users &times; 10 FPS &times; 10s/min &times; 60min/h * 2h/day &times; 30days = 1.44M transactions
- 1.44M transactions stored = ${1440000 * pricePerTxStored} / mo = ${1440000 * pricePerTxStored / 4} / mo / user
- 3 &times; 1.44M transactions synced out = ${(3 * 1440000 * pricePerTxSyncedOut).toLocaleString("en-US", { maximumSignificantDigits: 3, })} one-time = ${(3 * 1440000 * pricePerTxSyncedOut / 4).toLocaleString("en-US", { maximumSignificantDigits: 3, })} one-time / user
- A livestreamer streaming video (1GB total) to 100 viewers (combined live & on-demand)
- 1GB = 10,000 transactions (100KB each)
- 10,000 transactions stored = ${10000 * pricePerTxStored} / mo (= ${10000 * 1000 * pricePerTxStored} per 1TB stored)
- 100 &times; 10,000 transaction synced out = ${100 * 10000 * pricePerTxSyncedOut} one-time (= ${10000 * 1000 * pricePerTxSyncedOut} per 1TB egress)
</div>
## Global Footprint
We're rapidly expanding our network of sync & storage nodes. This is our current best-effort coverage:
<Grid className="grid-cols-2 md:grid-cols-3 lg:grid-cols-4">
<GridItem>
<div className="text-sm">
**Under 50ms RTT**
- Frankfurt
- New York
- Newark
- North California
- North Virginia
- San Francisco
- Singapore
- Toronto
</div>
</GridItem>
<GridItem>
<div className="text-sm">
**Under 100ms RTT**
- Amsterdam
- Atlanta
- London
- Ohio
- Paris
</div>
</GridItem>
<GridItem>
<div className="text-sm">
**Under 200ms RTT**
- Bangalore
- Dallas
- Mumbai
- Oregon
**Under 300ms RTT**
- Seoul
- Tokyo
</div>
</GridItem>
<GridItem>
<div className="text-sm">
**Under 400ms RTT**
- Sao Paulo
- Sydney
**Under 500ms RTT**
- Cape Town
</div>
</GridItem>
</Grid>
### Enterprise
Custom deployment in the cloud, your private cloud, on-premises or hybrids?
SLAs and dedicated support? White-glove integration services?
Let's talk: <a href="mailto:hello@gcmp.io">hello@gcmp.io</a>
## Custom Deployment Scenarios
<Slogan>You can rely on Global Mesh. But you don't have to.</Slogan>
<p>Because Jazz is open-source, you can optionally run your own sync nodes &mdash; in a variety of setups.</p>
<Grid>
<GridCard>
#### Global Mesh + Data Backup Node.
<p className="no-prose text-base">Connect your users to Global Mesh for all its benefits, but also run and connect your own data backup node (just in case.)</p>
<div className="text-sm">
Extra costs:
- Instance costs for the backup node.
- Moderate self-hosted storage costs.
- Every transaction is additionally synced to your backup node and counted as synced out.
</div>
</GridCard>
<GridCard>
#### Global Mesh + DIY Mesh.
<p className="no-prose text-base">Connect your users to Global Mesh, or your own nodes as a lower-performance fallback. The two networks stay in constant sync.</p>
<div className="text-sm">
Extra costs:
- N × instance cost for your sync nodes.
- Typically moderate self-hosted egress costs.
- High self-hosted storage costs.
- Every transaction is additionally synced to your DIY mesh and counted as synced out.
</div>
</GridCard>
<GridCard>
#### Completely DIY Mesh.
<p className="no-prose text-base">Build your own network of sync and storage nodes.
Handle networking, security and backups yourself.</p>
<div className="text-sm">
Costs:
- N × instance cost for your sync nodes.
- Very high self-hosted egress costs.
- High self-hosted storage costs.
</div>
</GridCard>
</Grid>

View File

@@ -22,14 +22,14 @@ await rm("./codeSamples", { recursive: true, force: true });
(
await Promise.all(
(
await readdir(path.join("../../", dir))
await readdir(path.join("../", dir))
).map(async (f) =>
(f.endsWith(".ts") && f !== "vite-env.d.ts") ||
f.endsWith(".tsx")
? [
f,
await readFile(
path.join("../../", dir, f),
path.join("../", dir, f),
"utf8"
),
]

View File

@@ -18,5 +18,6 @@
"updated": "lerna updated --include-merged-tags",
"publish-all": "yarn run gen-docs && lerna publish --include-merged-tags",
"gen-docs": "ts-node generateDocs.ts"
}
},
"version": "0.0.0"
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,228 @@
/* eslint @typescript-eslint/no-explicit-any: 0 */
const isFunction = (func: any) => typeof func === 'function';
const isObject = (supposedObject: any) =>
typeof supposedObject === 'object' &&
supposedObject !== null &&
!Array.isArray(supposedObject);
const isThenable = (obj: any) => isObject(obj) && isFunction(obj.then);
const identity = (val: any) => val;
export { isObject, isThenable, identity, isFunction };
enum States {
PENDING = 'PENDING',
RESOLVED = 'RESOLVED',
REJECTED = 'REJECTED',
}
interface Handler<T, U> {
onSuccess: HandlerOnSuccess<T, U>;
onFail: HandlerOnFail<U>;
}
type HandlerOnSuccess<T, U = any> = (value: T) => U | Thenable<U>;
type HandlerOnFail<U = any> = (reason: any) => U | Thenable<U>;
type Finally<U> = () => U | Thenable<U>;
interface Thenable<T> {
then<U>(
onSuccess?: HandlerOnSuccess<T, U>,
onFail?: HandlerOnFail<U>,
): Thenable<U>;
then<U>(
onSuccess?: HandlerOnSuccess<T, U>,
onFail?: (reason: any) => void,
): Thenable<U>;
}
type Resolve<R> = (value?: R | Thenable<R>) => void;
type Reject = (value?: any) => void;
export class SyncPromise<T> {
private state: States = States.PENDING;
private handlers: Handler<T, any>[] = [];
private value: T | any;
public constructor(callback: (resolve: Resolve<T>, reject: Reject) => void) {
try {
callback(this.resolve as Resolve<T>, this.reject);
} catch (e) {
this.reject(e);
}
}
private resolve = (value: T) => {
return this.setResult(value, States.RESOLVED);
};
private reject = (reason: any) => {
return this.setResult(reason, States.REJECTED);
};
private setResult = (value: T | any, state: States) => {
const set = () => {
if (this.state !== States.PENDING) {
return null;
}
if (isThenable(value)) {
return (value as Thenable<T>).then(this.resolve, this.reject);
}
this.value = value;
this.state = state;
return this.executeHandlers();
};
set();
};
private executeHandlers = () => {
if (this.state === States.PENDING) {
return null;
}
this.handlers.forEach((handler) => {
if (this.state === States.REJECTED) {
return handler.onFail(this.value);
}
return handler.onSuccess(this.value);
});
this.handlers = [];
};
private attachHandler = (handler: Handler<T, any>) => {
this.handlers = [...this.handlers, handler];
this.executeHandlers();
};
public then<U>(
onSuccess: HandlerOnSuccess<T, U>,
onFail?: HandlerOnFail<U>,
) {
return new SyncPromise<U>((resolve, reject) => {
return this.attachHandler({
onSuccess: (result) => {
try {
return resolve(onSuccess(result));
} catch (e) {
return reject(e);
}
},
onFail: (reason) => {
if (!onFail) {
return reject(reason);
}
try {
return resolve(onFail(reason));
} catch (e) {
return reject(e);
}
},
});
});
}
public catch<U>(onFail: HandlerOnFail<U>) {
return this.then<U>(identity, onFail);
}
// methods
public toString() {
return `[object SyncPromise]`;
}
public finally<U>(cb: Finally<U>) {
return new SyncPromise<U>((resolve, reject) => {
let val: U | any;
let isRejected: boolean;
return this.then(
(value) => {
isRejected = false;
val = value;
return cb();
},
(reason) => {
isRejected = true;
val = reason;
return cb();
},
).then(() => {
if (isRejected) {
return reject(val);
}
return resolve(val);
});
});
}
public spread<U>(handler: (...args: any[]) => U) {
return this.then<U>((collection) => {
if (Array.isArray(collection)) {
return handler(...collection);
}
return handler(collection);
});
}
// static
public static resolve<U = any>(value?: U | Thenable<U>) {
return new SyncPromise<U>((resolve) => {
return resolve(value);
});
}
public static reject<U>(reason?: any) {
return new SyncPromise<U>((resolve, reject) => {
return reject(reason);
});
}
public static all<U = any>(collection: (U | Thenable<U>)[]) {
return new SyncPromise<U[]>((resolve, reject) => {
if (!Array.isArray(collection)) {
return reject(new TypeError('An array must be provided.'));
}
if (collection.length === 0) {
return resolve([]);
}
let counter = collection.length;
const resolvedCollection: U[] = [];
const tryResolve = (value: U, index: number) => {
counter -= 1;
resolvedCollection[index] = value;
if (counter !== 0) {
return null;
}
return resolve(resolvedCollection);
};
return collection.forEach((item, index) => {
return SyncPromise.resolve(item)
.then((value) => {
return tryResolve(value, index);
})
.catch(reject);
});
});
}
}

View File

@@ -1,5 +1,23 @@
# cojson
## 0.6.3
### Patch Changes
- Implement passphrase based auth
## 0.6.2
### Patch Changes
- Add peersToLoadFrom for node creation as well
## 0.6.1
### Patch Changes
- Provide localNode to AccountMigrations
## 0.6.0
### Minor Changes

View File

@@ -5,7 +5,7 @@
"types": "src/index.ts",
"type": "module",
"license": "MIT",
"version": "0.6.0",
"version": "0.6.3",
"devDependencies": {
"@noble/curves": "^1.2.0",
"@types/jest": "^29.5.3",

View File

@@ -15,6 +15,7 @@ import {
import { AgentID } from "../ids.js";
import { CoMap } from "./coMap.js";
import { Group, InviteSecret } from "./group.js";
import { LocalNode } from "../index.js";
export function accountHeaderForInitialAgentSecret(
agentSecret: AgentSecret
@@ -169,5 +170,6 @@ export type AccountMigration<
Meta extends AccountMeta = AccountMeta
> = (
account: ControlledAccount<P, R, Meta>,
profile: P
profile: P,
localNode: LocalNode
) => void | Promise<void>;

View File

@@ -2,6 +2,7 @@ import {
CoValueCore,
newRandomSessionID,
MAX_RECOMMENDED_TX_SIZE,
idforHeader,
} from "./coValueCore.js";
import { accountOrAgentIDfromSessionID } from "./typeUtils/accountOrAgentIDfromSessionID.js";
import { LocalNode } from "./localNode.js";
@@ -36,7 +37,7 @@ import { Group, EVERYONE } from "./coValues/group.js";
import type { Everyone } from "./coValues/group.js";
import { base64URLtoBytes, bytesToBase64url } from "./base64url.js";
import { parseJSON } from "./jsonStringify.js";
import { Account, Profile } from "./coValues/account.js";
import { Account, Profile, accountHeaderForInitialAgentSecret } from "./coValues/account.js";
import { expectGroup } from "./typeUtils/expectGroup.js";
import { isAccountID } from "./typeUtils/isAccountID.js";
@@ -80,6 +81,8 @@ export const cojsonInternals = {
parseJSON,
accountOrAgentIDfromSessionID,
isAccountID,
accountHeaderForInitialAgentSecret,
idforHeader
};
export {

View File

@@ -74,10 +74,12 @@ export class LocalNode {
Meta extends AccountMeta = AccountMeta
>({
name,
peersToLoadFrom,
migration,
initialAgentSecret = newRandomAgentSecret(),
}: {
name: string;
peersToLoadFrom?: Peer[];
migration?: AccountMigration<P, R, Meta>;
initialAgentSecret?: AgentSecret;
}): Promise<{
@@ -107,8 +109,14 @@ export class LocalNode {
"After creating account"
);
if (peersToLoadFrom) {
for (const peer of peersToLoadFrom) {
nodeWithAccount.syncManager.addPeer(peer);
}
}
if (migration) {
await migration(accountOnNodeWithAccount, profile as P);
await migration(accountOnNodeWithAccount, profile as P, nodeWithAccount);
}
nodeWithAccount.account = new ControlledAccount(
@@ -196,7 +204,8 @@ export class LocalNode {
if (migration) {
await migration(
controlledAccount as ControlledAccount<P, R, Meta>,
profile as P
profile as P,
node
);
node.account = new ControlledAccount(
controlledAccount.core,

View File

@@ -160,10 +160,8 @@ export function newStreamPair<T>(
// make sure write resolves before corresponding read, but make sure writes are still in order
await lastWritePromise;
lastWritePromise = new Promise((resolve) => {
setTimeout(() => {
enqueue(chunk);
resolve();
});
enqueue(chunk);
resolve();
});
}
},

View File

@@ -195,7 +195,7 @@ export class SyncManager {
return await this.handleKnownState(msg, peer);
}
case "content":
await new Promise<void>((resolve) => setTimeout(resolve, 0));
// await new Promise<void>((resolve) => setTimeout(resolve, 0));
return await this.handleNewContent(msg, peer);
case "done":
return await this.handleUnsubscribe(msg);
@@ -355,9 +355,9 @@ export class SyncManager {
e
);
});
await new Promise<void>((resolve) => {
setTimeout(resolve, 0);
});
// await new Promise<void>((resolve) => {
// setTimeout(resolve, 0);
// });
} catch (e) {
console.error(
new Date(),

View File

@@ -288,9 +288,16 @@ export function autoSub(
if (!effectiveId) return () => {};
// const ctxId = Math.random().toString(16).slice(2);
// let updateN = 0;
const context = new AutoSubContext(node, () => {
const rootResolved = context.values[effectiveId]?.lastLoaded;
// const n = updateN;
// updateN++;
// console.time("AutoSubContext.onUpdate " + n + " " + ctxId);
callback(rootResolved);
// console.timeEnd("AutoSubContext.onUpdate " + n + " " + ctxId);
});
context.autoSub(effectiveId, [], "");

View File

@@ -0,0 +1,21 @@
module.exports = {
extends: [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"plugin:require-extensions/recommended",
],
parser: "@typescript-eslint/parser",
plugins: ["@typescript-eslint", "require-extensions"],
parserOptions: {
project: "./tsconfig.json",
},
root: true,
rules: {
"no-unused-vars": "off",
"@typescript-eslint/no-unused-vars": [
"error",
{ argsIgnorePattern: "^_", varsIgnorePattern: "^_" },
],
"@typescript-eslint/no-floating-promises": "error",
},
};

View File

@@ -0,0 +1,171 @@
# Based on https://raw.githubusercontent.com/github/gitignore/main/Node.gitignore
# Logs
logs
_.log
npm-debug.log_
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
.pnpm-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
# Runtime data
pids
_.pid
_.seed
\*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
\*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# Snowpack dependency directory (https://snowpack.dev/)
web_modules/
# TypeScript cache
\*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional stylelint cache
.stylelintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
\*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local
# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache
# Next.js build output
.next
out
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# vuepress v2.x temp and cache directory
.temp
.cache
# Docusaurus cache and generated files
.docusaurus
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
# Stores VSCode versions used for testing VSCode extensions
.vscode-test
# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.\*
.DS_Store

View File

@@ -0,0 +1,2 @@
coverage
node_modules

View File

@@ -0,0 +1,33 @@
# jazz-browser-auth-local
## 0.5.1
### Patch Changes
- Implement passphrase based auth
## 0.5.0
### Minor Changes
- Make addMember and removeMember take loaded Accounts instead of just IDs
### Patch Changes
- Updated dependencies
- jazz-browser@0.6.0
## 0.4.17
### Patch Changes
- Allow account migrations to be async
- Updated dependencies
- jazz-browser@0.5.1
## 0.4.16
### Patch Changes
- Updated dependencies
- jazz-browser@0.5.0

View File

@@ -0,0 +1,18 @@
{
"name": "jazz-browser-auth-passphrase",
"version": "0.5.1",
"main": "dist/index.js",
"types": "src/index.ts",
"license": "MIT",
"dependencies": {
"@scure/bip39": "^1.2.2",
"jazz-browser": "^0.6.0",
"typescript": "^5.1.6"
},
"scripts": {
"lint": "eslint src/**/*.ts",
"build": "npm run lint && rm -rf ./dist && tsc --sourceMap --outDir dist",
"prepublishOnly": "npm run build"
},
"gitHead": "33c27053293b4801b968c61d5c4c989f93a67d13"
}

View File

@@ -0,0 +1,177 @@
import {
AccountID,
AccountMigration,
AgentSecret,
cojsonInternals,
LocalNode,
Peer,
} from "cojson";
import { agentSecretFromSecretSeed } from "cojson/src/crypto";
import { AuthProvider, SessionProvider } from "jazz-browser";
type SessionStorageData = {
accountID: AccountID;
accountSecret: AgentSecret;
};
const sessionStorageKey = "jazz-logged-in-secret";
export interface BrowserPassphraseAuthDriver {
onReady: (next: {
signUp: (username: string, passphrase: string) => Promise<void>;
logIn: (passphrase: string) => Promise<void>;
}) => void;
onSignedIn: (next: { logOut: () => void }) => void;
}
export class BrowserPassphraseAuth implements AuthProvider {
driver: BrowserPassphraseAuthDriver;
appName: string;
appHostname: string;
wordlist: string[];
constructor(
driver: BrowserPassphraseAuthDriver,
wordlist: string[],
appName: string,
// TODO: is this a safe default?
appHostname: string = window.location.hostname
) {
this.driver = driver;
this.wordlist = wordlist;
this.appName = appName;
this.appHostname = appHostname;
}
async createNode(
getSessionFor: SessionProvider,
initialPeers: Peer[],
migration?: AccountMigration
): Promise<LocalNode> {
if (sessionStorage[sessionStorageKey]) {
const sessionStorageData = JSON.parse(
sessionStorage[sessionStorageKey]
) as SessionStorageData;
const sessionID = await getSessionFor(sessionStorageData.accountID);
const node = await LocalNode.withLoadedAccount({
accountID: sessionStorageData.accountID,
accountSecret: sessionStorageData.accountSecret,
sessionID,
peersToLoadFrom: initialPeers,
migration,
});
this.driver.onSignedIn({ logOut });
return Promise.resolve(node);
} else {
const node = await new Promise<LocalNode>(
(doneSigningUpOrLoggingIn) => {
this.driver.onReady({
signUp: async (username, passphrase) => {
const node = await signUp(
username,
passphrase,
this.wordlist,
getSessionFor,
this.appName,
this.appHostname,
migration
);
for (const peer of initialPeers) {
node.syncManager.addPeer(peer);
}
doneSigningUpOrLoggingIn(node);
this.driver.onSignedIn({ logOut });
},
logIn: async (passphrase: string) => {
const node = await logIn(
passphrase,
this.wordlist,
getSessionFor,
this.appHostname,
initialPeers,
migration
);
doneSigningUpOrLoggingIn(node);
this.driver.onSignedIn({ logOut });
},
});
}
);
return node;
}
}
}
import * as bip39 from '@scure/bip39';
async function signUp(
username: string,
passphrase: string,
wordlist: string[],
getSessionFor: SessionProvider,
appName: string,
appHostname: string,
migration?: AccountMigration
): Promise<LocalNode> {
const secretSeed = bip39.mnemonicToEntropy(passphrase, wordlist);
const { node, accountID, accountSecret } =
await LocalNode.withNewlyCreatedAccount({
name: username,
initialAgentSecret: agentSecretFromSecretSeed(secretSeed),
migration,
});
sessionStorage[sessionStorageKey] = JSON.stringify({
accountID,
accountSecret,
} satisfies SessionStorageData);
node.currentSessionID = await getSessionFor(accountID);
return node;
}
async function logIn(
passphrase: string,
wordlist: string[],
getSessionFor: SessionProvider,
appHostname: string,
initialPeers: Peer[],
migration?: AccountMigration
): Promise<LocalNode> {
const accountSecretSeed = bip39.mnemonicToEntropy(passphrase, wordlist);
const accountSecret = agentSecretFromSecretSeed(accountSecretSeed);
if (!accountSecret) {
throw new Error("Invalid credential");
}
const accountID = cojsonInternals.idforHeader(cojsonInternals.accountHeaderForInitialAgentSecret(accountSecret)) as AccountID;
sessionStorage[sessionStorageKey] = JSON.stringify({
accountID,
accountSecret,
} satisfies SessionStorageData);
const node = await LocalNode.withLoadedAccount({
accountID,
accountSecret,
sessionID: await getSessionFor(accountID),
peersToLoadFrom: initialPeers,
migration,
});
return node;
}
function logOut() {
delete sessionStorage[sessionStorageKey];
}

View File

@@ -0,0 +1,16 @@
{
"compilerOptions": {
"lib": ["ESNext", "DOM"],
"module": "esnext",
"target": "ES2020",
"moduleResolution": "bundler",
"moduleDetection": "force",
"strict": true,
"jsx": "react",
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"noUncheckedIndexedAccess": true,
"esModuleInterop": true,
},
"include": ["./src/**/*"],
}

View File

@@ -0,0 +1,83 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
"@noble/ciphers@^0.1.3":
version "0.1.4"
resolved "https://registry.yarnpkg.com/@noble/ciphers/-/ciphers-0.1.4.tgz#96327dca147829ed9eee0d96cfdf7c57915765f0"
integrity sha512-d3ZR8vGSpy3v/nllS+bD/OMN5UZqusWiQqkyj7AwzTnhXFH72pF5oB4Ach6DQ50g5kXxC28LdaYBEpsyv9KOUQ==
"@noble/curves@^1.1.0":
version "1.1.0"
resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.1.0.tgz#f13fc667c89184bc04cccb9b11e8e7bae27d8c3d"
integrity sha512-091oBExgENk/kGj3AZmtBDMpxQPDtxQABR2B9lb1JbVTs6ytdzZNwvhxQ4MWasRNEzlbEH8jCWFCwhF/Obj5AA==
dependencies:
"@noble/hashes" "1.3.1"
"@noble/hashes@1.3.1", "@noble/hashes@^1.3.1":
version "1.3.1"
resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.3.1.tgz#8831ef002114670c603c458ab8b11328406953a9"
integrity sha512-EbqwksQwz9xDRGfDST86whPBgM65E0OH/pCgqW0GBVzO22bNE+NuIbeTb714+IfSjU3aRk47EUvXIb5bTsenKA==
"@scure/base@^1.1.1":
version "1.1.1"
resolved "https://registry.yarnpkg.com/@scure/base/-/base-1.1.1.tgz#ebb651ee52ff84f420097055f4bf46cfba403938"
integrity sha512-ZxOhsSyxYwLJj3pLZCefNitxsj093tb2vq90mp2txoYeBqbcjDjqFhyM8eUjq/uFm6zJ+mUuqxlS2FkuSY1MTA==
"@types/prop-types@*":
version "15.7.5"
resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.5.tgz#5f19d2b85a98e9558036f6a3cacc8819420f05cf"
integrity sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==
"@types/react@^18.2.19":
version "18.2.19"
resolved "https://registry.yarnpkg.com/@types/react/-/react-18.2.19.tgz#f77cb2c8307368e624d464a25b9675fa35f95a8b"
integrity sha512-e2S8wmY1ePfM517PqCG80CcE48Xs5k0pwJzuDZsfE8IZRRBfOMCF+XqnFxu6mWtyivum1MQm4aco+WIt6Coimw==
dependencies:
"@types/prop-types" "*"
"@types/scheduler" "*"
csstype "^3.0.2"
"@types/scheduler@*":
version "0.16.3"
resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.3.tgz#cef09e3ec9af1d63d2a6cc5b383a737e24e6dcf5"
integrity sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ==
cojson@^0.0.14:
version "0.0.14"
resolved "https://registry.yarnpkg.com/cojson/-/cojson-0.0.14.tgz#e7b190ade1efc20d6f0fa12411d7208cdc0f19f7"
integrity sha512-TFenIGswEEhnZlCmq+B1NZPztjovZ72AjK1YkkZca54ZFbB1lAHdPt2hqqu/QBO24C9+6DtuoS2ixm6gbSBWCg==
dependencies:
"@noble/ciphers" "^0.1.3"
"@noble/curves" "^1.1.0"
"@noble/hashes" "^1.3.1"
"@scure/base" "^1.1.1"
fast-json-stable-stringify "^2.1.0"
isomorphic-streams "https://github.com/sgwilym/isomorphic-streams.git#aa9394781bfc92f8d7c981be7daf8af4b4cd4fae"
csstype@^3.0.2:
version "3.1.2"
resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.2.tgz#1d4bf9d572f11c14031f0436e1c10bc1f571f50b"
integrity sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==
fast-json-stable-stringify@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633"
integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==
"isomorphic-streams@git+https://github.com/sgwilym/isomorphic-streams.git#aa9394781bfc92f8d7c981be7daf8af4b4cd4fae":
version "1.0.3"
resolved "git+https://github.com/sgwilym/isomorphic-streams.git#aa9394781bfc92f8d7c981be7daf8af4b4cd4fae"
jazz-react@^0.0.6:
version "0.0.6"
resolved "https://registry.yarnpkg.com/jazz-react/-/jazz-react-0.0.6.tgz#53b0245720b10ec31ac8deba45fd8a052b313b06"
integrity sha512-JlYTKUVPpuK3T7cLfk2YwHh3yH+2BPVSuWIQui35U52/gce+HmTMGolqFYGghWMVQtwclaZ0IoEbtuycPiADOQ==
dependencies:
cojson "^0.0.14"
typescript "^5.1.6"
typescript@^5.1.6:
version "5.1.6"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.1.6.tgz#02f8ac202b6dad2c0dd5e0913745b47a37998274"
integrity sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA==

View File

@@ -0,0 +1,21 @@
module.exports = {
extends: [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"plugin:require-extensions/recommended",
],
parser: "@typescript-eslint/parser",
plugins: ["@typescript-eslint", "require-extensions"],
parserOptions: {
project: "./tsconfig.json",
},
root: true,
rules: {
"no-unused-vars": "off",
"@typescript-eslint/no-unused-vars": [
"error",
{ argsIgnorePattern: "^_", varsIgnorePattern: "^_" },
],
"@typescript-eslint/no-floating-promises": "error",
},
};

171
packages/jazz-browser-auth0/.gitignore vendored Normal file
View File

@@ -0,0 +1,171 @@
# Based on https://raw.githubusercontent.com/github/gitignore/main/Node.gitignore
# Logs
logs
_.log
npm-debug.log_
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
.pnpm-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
# Runtime data
pids
_.pid
_.seed
\*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
\*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# Snowpack dependency directory (https://snowpack.dev/)
web_modules/
# TypeScript cache
\*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional stylelint cache
.stylelintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
\*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local
# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache
# Next.js build output
.next
out
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# vuepress v2.x temp and cache directory
.temp
.cache
# Docusaurus cache and generated files
.docusaurus
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
# Stores VSCode versions used for testing VSCode extensions
.vscode-test
# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.\*
.DS_Store

View File

@@ -0,0 +1,2 @@
coverage
node_modules

View File

@@ -0,0 +1,39 @@
# jazz-browser-auth-local
## 0.6.2
### Patch Changes
- Fix bug where user_metadata wasn't received
## 0.6.1
### Patch Changes
- Initial implementation
## 0.5.0
### Minor Changes
- Make addMember and removeMember take loaded Accounts instead of just IDs
### Patch Changes
- Updated dependencies
- jazz-browser@0.6.0
## 0.4.17
### Patch Changes
- Allow account migrations to be async
- Updated dependencies
- jazz-browser@0.5.1
## 0.4.16
### Patch Changes
- Updated dependencies
- jazz-browser@0.5.0

View File

@@ -0,0 +1,21 @@
{
"name": "jazz-browser-auth0",
"version": "0.6.2",
"main": "dist/index.js",
"types": "src/index.ts",
"license": "MIT",
"dependencies": {
"auth0-js": "^9.23.3",
"jazz-browser": "^0.6.0",
"typescript": "^5.1.6"
},
"scripts": {
"lint": "eslint src/**/*.ts",
"build": "npm run lint && rm -rf ./dist && tsc --sourceMap --outDir dist",
"prepublishOnly": "npm run build"
},
"gitHead": "33c27053293b4801b968c61d5c4c989f93a67d13",
"devDependencies": {
"@types/auth0-js": "^9.21.5"
}
}

View File

@@ -0,0 +1,123 @@
import {
AccountID,
AccountMigration,
AgentSecret,
LocalNode,
Peer,
} from "cojson";
import { AuthProvider, SessionProvider } from "jazz-browser";
import { Auth0UserProfile, Authentication, Management } from "auth0-js";
type CredentialData = {
accountID: AccountID;
accountSecret: AgentSecret;
};
export class BrowserAuth0 implements AuthProvider {
clientID: string;
auth0domain: string;
accessToken: string;
appName: string;
appHostname: string;
/** Create or use Jazz account linked to an Auth0 account. The access token must have been retreived from a WebAuthn instance created with the following options:
*
* - `audience: https://{yourDomain}/api/v2/`
* - `scope: "read:current_user update:current_user_metadata"`
*/
constructor(
auth0Options: {
clientID: string;
domain: string;
accessToken: string;
},
appName: string,
// TODO: is this a safe default?
appHostname: string = window.location.hostname
) {
this.clientID = auth0Options.clientID;
this.auth0domain = auth0Options.domain;
this.accessToken = auth0Options.accessToken;
this.appName = appName;
this.appHostname = appHostname;
}
async createNode(
getSessionFor: SessionProvider,
initialPeers: Peer[],
migration?: AccountMigration
): Promise<LocalNode> {
const auth0client = new Authentication({
clientID: this.clientID,
domain: this.auth0domain,
});
const user = await new Promise<Auth0UserProfile>((resolve, reject) => {
auth0client.userInfo(this.accessToken, (err, user) => {
if (err) reject(err);
else resolve(user);
});
});
const management = new Management({
token: this.accessToken,
domain: this.auth0domain,
});
const metadata = await new Promise<Record<string, string>>(
(resolve, reject) => {
management.getUser(user.sub, (err, profile) => {
if (err) reject(err);
else resolve(profile.user_metadata);
});
}
);
const existingCredentialString = metadata?.["jazz_credential"];
if (existingCredentialString) {
const existingCredential = JSON.parse(
existingCredentialString
) as CredentialData;
return LocalNode.withLoadedAccount({
accountID: existingCredential.accountID,
accountSecret: existingCredential.accountSecret,
sessionID: await getSessionFor(existingCredential.accountID),
peersToLoadFrom: initialPeers,
migration,
});
} else {
const username =
user.nickname || user.name || user.email || user.sub;
const { node, accountID, accountSecret } =
await LocalNode.withNewlyCreatedAccount({
name: username,
migration,
peersToLoadFrom: initialPeers,
});
await new Promise<void>((resolve, reject) =>
management.patchUserMetadata(
user.sub,
{
jazz_credential: JSON.stringify({
accountID,
accountSecret,
} as CredentialData),
},
(err) => {
if (err) reject(err);
else resolve();
}
)
);
return node;
}
}
}

View File

@@ -0,0 +1,16 @@
{
"compilerOptions": {
"lib": ["ESNext", "DOM"],
"module": "esnext",
"target": "ES2020",
"moduleResolution": "bundler",
"moduleDetection": "force",
"strict": true,
"jsx": "react",
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"noUncheckedIndexedAccess": true,
"esModuleInterop": true,
},
"include": ["./src/**/*"],
}

View File

@@ -0,0 +1,83 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
"@noble/ciphers@^0.1.3":
version "0.1.4"
resolved "https://registry.yarnpkg.com/@noble/ciphers/-/ciphers-0.1.4.tgz#96327dca147829ed9eee0d96cfdf7c57915765f0"
integrity sha512-d3ZR8vGSpy3v/nllS+bD/OMN5UZqusWiQqkyj7AwzTnhXFH72pF5oB4Ach6DQ50g5kXxC28LdaYBEpsyv9KOUQ==
"@noble/curves@^1.1.0":
version "1.1.0"
resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.1.0.tgz#f13fc667c89184bc04cccb9b11e8e7bae27d8c3d"
integrity sha512-091oBExgENk/kGj3AZmtBDMpxQPDtxQABR2B9lb1JbVTs6ytdzZNwvhxQ4MWasRNEzlbEH8jCWFCwhF/Obj5AA==
dependencies:
"@noble/hashes" "1.3.1"
"@noble/hashes@1.3.1", "@noble/hashes@^1.3.1":
version "1.3.1"
resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.3.1.tgz#8831ef002114670c603c458ab8b11328406953a9"
integrity sha512-EbqwksQwz9xDRGfDST86whPBgM65E0OH/pCgqW0GBVzO22bNE+NuIbeTb714+IfSjU3aRk47EUvXIb5bTsenKA==
"@scure/base@^1.1.1":
version "1.1.1"
resolved "https://registry.yarnpkg.com/@scure/base/-/base-1.1.1.tgz#ebb651ee52ff84f420097055f4bf46cfba403938"
integrity sha512-ZxOhsSyxYwLJj3pLZCefNitxsj093tb2vq90mp2txoYeBqbcjDjqFhyM8eUjq/uFm6zJ+mUuqxlS2FkuSY1MTA==
"@types/prop-types@*":
version "15.7.5"
resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.5.tgz#5f19d2b85a98e9558036f6a3cacc8819420f05cf"
integrity sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==
"@types/react@^18.2.19":
version "18.2.19"
resolved "https://registry.yarnpkg.com/@types/react/-/react-18.2.19.tgz#f77cb2c8307368e624d464a25b9675fa35f95a8b"
integrity sha512-e2S8wmY1ePfM517PqCG80CcE48Xs5k0pwJzuDZsfE8IZRRBfOMCF+XqnFxu6mWtyivum1MQm4aco+WIt6Coimw==
dependencies:
"@types/prop-types" "*"
"@types/scheduler" "*"
csstype "^3.0.2"
"@types/scheduler@*":
version "0.16.3"
resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.3.tgz#cef09e3ec9af1d63d2a6cc5b383a737e24e6dcf5"
integrity sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ==
cojson@^0.0.14:
version "0.0.14"
resolved "https://registry.yarnpkg.com/cojson/-/cojson-0.0.14.tgz#e7b190ade1efc20d6f0fa12411d7208cdc0f19f7"
integrity sha512-TFenIGswEEhnZlCmq+B1NZPztjovZ72AjK1YkkZca54ZFbB1lAHdPt2hqqu/QBO24C9+6DtuoS2ixm6gbSBWCg==
dependencies:
"@noble/ciphers" "^0.1.3"
"@noble/curves" "^1.1.0"
"@noble/hashes" "^1.3.1"
"@scure/base" "^1.1.1"
fast-json-stable-stringify "^2.1.0"
isomorphic-streams "https://github.com/sgwilym/isomorphic-streams.git#aa9394781bfc92f8d7c981be7daf8af4b4cd4fae"
csstype@^3.0.2:
version "3.1.2"
resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.2.tgz#1d4bf9d572f11c14031f0436e1c10bc1f571f50b"
integrity sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==
fast-json-stable-stringify@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633"
integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==
"isomorphic-streams@git+https://github.com/sgwilym/isomorphic-streams.git#aa9394781bfc92f8d7c981be7daf8af4b4cd4fae":
version "1.0.3"
resolved "git+https://github.com/sgwilym/isomorphic-streams.git#aa9394781bfc92f8d7c981be7daf8af4b4cd4fae"
jazz-react@^0.0.6:
version "0.0.6"
resolved "https://registry.yarnpkg.com/jazz-react/-/jazz-react-0.0.6.tgz#53b0245720b10ec31ac8deba45fd8a052b313b06"
integrity sha512-JlYTKUVPpuK3T7cLfk2YwHh3yH+2BPVSuWIQui35U52/gce+HmTMGolqFYGghWMVQtwclaZ0IoEbtuycPiADOQ==
dependencies:
cojson "^0.0.14"
typescript "^5.1.6"
typescript@^5.1.6:
version "5.1.6"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.1.6.tgz#02f8ac202b6dad2c0dd5e0913745b47a37998274"
integrity sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA==

View File

@@ -1,5 +1,19 @@
# jazz-autosub
## 0.6.2
### Patch Changes
- Add peersToLoadFrom for node creation as well
- Updated dependencies
- cojson@0.6.2
## 0.6.1
### Patch Changes
- Fix wrong import from cojson
## 0.6.0
### Minor Changes

View File

@@ -5,9 +5,9 @@
"types": "src/index.ts",
"type": "module",
"license": "MIT",
"version": "0.6.0",
"version": "0.6.2",
"dependencies": {
"cojson": "^0.6.0",
"cojson": "^0.6.2",
"cojson-transport-nodejs-ws": "^0.5.1",
"jazz-autosub": "^0.6.0"
},

View File

@@ -17,8 +17,8 @@ import {
Profile,
SessionID,
cojsonReady,
cojsonInternals
} from "cojson";
import { newRandomSessionID } from "cojson/src/coValueCore";
import { readFile, writeFile } from "node:fs/promises";
if (!("crypto" in globalThis)) {
@@ -72,7 +72,7 @@ export async function createOrResumeWorker<
// TODO: locked sessions similar to browser
const sessionID =
process.env.JAZZ_WORKER_SESSION ||
newRandomSessionID(existingCredentials.accountID);
cojsonInternals.newRandomSessionID(existingCredentials.accountID);
console.log("Loading worker", existingCredentials.accountID);
@@ -94,13 +94,12 @@ export async function createOrResumeWorker<
} else {
const newWorker = await LocalNode.withNewlyCreatedAccount({
name: workerName,
peersToLoadFrom: [wsPeer],
migration,
});
localNode = newWorker.node;
localNode.syncManager.addPeer(wsPeer);
await credentialStorage.save(
workerName,
newWorker.accountID,

View File

@@ -0,0 +1,21 @@
module.exports = {
extends: [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"plugin:require-extensions/recommended",
],
parser: "@typescript-eslint/parser",
plugins: ["@typescript-eslint", "require-extensions"],
parserOptions: {
project: "./tsconfig.json",
},
root: true,
rules: {
"no-unused-vars": "off",
"@typescript-eslint/no-unused-vars": [
"error",
{ argsIgnorePattern: "^_", varsIgnorePattern: "^_" },
],
"@typescript-eslint/no-floating-promises": "error",
},
};

View File

@@ -0,0 +1,171 @@
# Based on https://raw.githubusercontent.com/github/gitignore/main/Node.gitignore
# Logs
logs
_.log
npm-debug.log_
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
.pnpm-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
# Runtime data
pids
_.pid
_.seed
\*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
\*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# Snowpack dependency directory (https://snowpack.dev/)
web_modules/
# TypeScript cache
\*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional stylelint cache
.stylelintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
\*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local
# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache
# Next.js build output
.next
out
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# vuepress v2.x temp and cache directory
.temp
.cache
# Docusaurus cache and generated files
.docusaurus
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
# Stores VSCode versions used for testing VSCode extensions
.vscode-test
# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.\*
.DS_Store

View File

@@ -0,0 +1,2 @@
coverage
node_modules

View File

@@ -0,0 +1,25 @@
# jazz-react-auth-local
## 0.5.1
### Patch Changes
- Implement passphrase based auth
- Updated dependencies
- jazz-browser-auth-passphrase@0.5.1
## 0.4.17
### Patch Changes
- Updated dependencies
- jazz-browser-auth-local@0.5.0
- jazz-react@0.5.1
## 0.4.16
### Patch Changes
- Updated dependencies
- jazz-react@0.5.0
- jazz-browser-auth-local@0.4.16

View File

@@ -0,0 +1,24 @@
{
"name": "jazz-react-auth-passphrase",
"version": "0.5.1",
"main": "dist/index.js",
"types": "src/index.tsx",
"license": "MIT",
"dependencies": {
"jazz-browser-auth-passphrase": "^0.5.1",
"jazz-react": "^0.5.1",
"typescript": "^5.1.6"
},
"devDependencies": {
"@types/react": "^18.2.19"
},
"peerDependencies": {
"react": "17 - 18"
},
"scripts": {
"lint": "eslint src/**/*.tsx",
"build": "npm run lint && rm -rf ./dist && tsc --sourceMap --outDir dist",
"prepublishOnly": "npm run build"
},
"gitHead": "33c27053293b4801b968c61d5c4c989f93a67d13"
}

View File

@@ -0,0 +1,234 @@
import { useMemo, useState, ReactNode } from "react";
import { BrowserPassphraseAuth } from "jazz-browser-auth-passphrase";
import { ReactAuthHook } from "jazz-react";
import { generateMnemonic } from "@scure/bip39";
import { cojsonInternals } from "cojson";
export type PassphraseAuthComponent = (props: {
loading: boolean;
logIn: (passphrase: string) => void;
signUp: (username: string, passphrase: string) => void;
generateRandomPassphrase: () => string;
}) => ReactNode;
export function PassphraseAuth({
appName,
appHostname,
wordlist,
Component = PassphraseAuthBasicUI,
}: {
appName: string;
appHostname?: string;
wordlist: string[];
Component?: PassphraseAuthComponent;
}): ReactAuthHook {
return function useLocalAuth() {
const [authState, setAuthState] = useState<
| { state: "loading" }
| {
state: "ready";
logIn: (passphrase: string) => void;
signUp: (username: string, passphrase: string) => void;
}
| { state: "signedIn"; logOut: () => void }
>({ state: "loading" });
const [logOutCounter, setLogOutCounter] = useState(0);
const auth = useMemo(() => {
return new BrowserPassphraseAuth(
{
onReady(next) {
setAuthState({
state: "ready",
logIn: next.logIn,
signUp: next.signUp,
});
},
onSignedIn(next) {
setAuthState({
state: "signedIn",
logOut: () => {
next.logOut();
setAuthState({ state: "loading" });
setLogOutCounter((c) => c + 1);
},
});
},
},
wordlist,
appName,
appHostname
);
}, [appName, appHostname, logOutCounter]);
const generateRandomPassphrase = () => {
return generateMnemonic(
wordlist,
cojsonInternals.secretSeedLength * 8
);
};
const AuthUI =
authState.state === "ready"
? Component({
loading: false,
logIn: authState.logIn,
signUp: authState.signUp,
generateRandomPassphrase,
})
: Component({
loading: false,
logIn: () => {},
signUp: (_) => {},
generateRandomPassphrase,
});
return {
auth,
AuthUI,
logOut:
authState.state === "signedIn" ? authState.logOut : undefined,
};
};
}
export const PassphraseAuthBasicUI = ({
logIn,
signUp,
generateRandomPassphrase,
}: {
logIn: (passphrase: string) => void;
signUp: (username: string, passphrase: string) => void;
generateRandomPassphrase: () => string;
}) => {
const [username, setUsername] = useState<string>("");
const [passphrase, setPassphrase] = useState<string>("");
const [loginPassphrase, setLoginPassphrase] = useState<string>("");
return (
<div
style={{
width: "100vw",
height: "100vh",
display: "flex",
alignItems: "center",
justifyContent: "center",
}}
>
<div
style={{
width: "30rem",
display: "flex",
flexDirection: "column",
gap: "2rem",
}}
>
<form
style={{
width: "30rem",
display: "flex",
flexDirection: "column",
gap: "0.5rem",
}}
onSubmit={(e) => {
e.preventDefault();
setPassphrase("");
setUsername("");
signUp(username, passphrase);
}}
>
<div style={{ display: "flex", gap: "0.5rem" }}>
<textarea
placeholder="Passphrase"
value={passphrase}
onChange={(e) => setPassphrase(e.target.value)}
style={{
border: "2px solid #000",
padding: "11px 8px",
borderRadius: "6px",
height: "7rem",
flex: 1
}}
/>
<button
type="button"
onClick={(e) => {
setPassphrase(generateRandomPassphrase());
e.preventDefault();
}}
style={{
padding: "11px 8px",
borderRadius: "6px",
background: "#eee",
}}
>
Random
</button>
</div>
<input
placeholder="Display name"
value={username}
onChange={(e) => setUsername(e.target.value)}
style={{
border: "2px solid #000",
padding: "11px 8px",
borderRadius: "6px",
}}
/>
<input
type="submit"
value="Sign Up as new account"
style={{
background: "#000",
color: "#fff",
padding: "13px 5px",
border: "none",
borderRadius: "6px",
cursor: "pointer",
}}
/>
</form>
<div style={{textAlign: "center"}}>&mdash; or &mdash;</div>
<form
style={{
width: "30rem",
display: "flex",
flexDirection: "column",
gap: "0.5rem",
}}
onSubmit={(e) => {
e.preventDefault();
setLoginPassphrase("");
logIn(loginPassphrase);
}}
>
<textarea
placeholder="Passphrase"
value={loginPassphrase}
onChange={(e) => setLoginPassphrase(e.target.value)}
style={{
border: "2px solid #000",
padding: "11px 8px",
borderRadius: "6px",
height: "7rem",
}}
/>
<input
type="submit"
value="Log in as existing account"
style={{
background: "#000",
color: "#fff",
padding: "13px 5px",
border: "none",
borderRadius: "6px",
cursor: "pointer",
}}
/>
</form>
</div>
</div>
);
};

View File

@@ -0,0 +1,16 @@
{
"compilerOptions": {
"lib": ["ESNext", "DOM"],
"module": "esnext",
"target": "ES2020",
"moduleResolution": "bundler",
"moduleDetection": "force",
"strict": true,
"jsx": "react-jsx",
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"noUncheckedIndexedAccess": true,
"esModuleInterop": true,
},
"include": ["./src/**/*"],
}

View File

@@ -0,0 +1,83 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
"@noble/ciphers@^0.1.3":
version "0.1.4"
resolved "https://registry.yarnpkg.com/@noble/ciphers/-/ciphers-0.1.4.tgz#96327dca147829ed9eee0d96cfdf7c57915765f0"
integrity sha512-d3ZR8vGSpy3v/nllS+bD/OMN5UZqusWiQqkyj7AwzTnhXFH72pF5oB4Ach6DQ50g5kXxC28LdaYBEpsyv9KOUQ==
"@noble/curves@^1.1.0":
version "1.1.0"
resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.1.0.tgz#f13fc667c89184bc04cccb9b11e8e7bae27d8c3d"
integrity sha512-091oBExgENk/kGj3AZmtBDMpxQPDtxQABR2B9lb1JbVTs6ytdzZNwvhxQ4MWasRNEzlbEH8jCWFCwhF/Obj5AA==
dependencies:
"@noble/hashes" "1.3.1"
"@noble/hashes@1.3.1", "@noble/hashes@^1.3.1":
version "1.3.1"
resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.3.1.tgz#8831ef002114670c603c458ab8b11328406953a9"
integrity sha512-EbqwksQwz9xDRGfDST86whPBgM65E0OH/pCgqW0GBVzO22bNE+NuIbeTb714+IfSjU3aRk47EUvXIb5bTsenKA==
"@scure/base@^1.1.1":
version "1.1.1"
resolved "https://registry.yarnpkg.com/@scure/base/-/base-1.1.1.tgz#ebb651ee52ff84f420097055f4bf46cfba403938"
integrity sha512-ZxOhsSyxYwLJj3pLZCefNitxsj093tb2vq90mp2txoYeBqbcjDjqFhyM8eUjq/uFm6zJ+mUuqxlS2FkuSY1MTA==
"@types/prop-types@*":
version "15.7.5"
resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.5.tgz#5f19d2b85a98e9558036f6a3cacc8819420f05cf"
integrity sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==
"@types/react@^18.2.19":
version "18.2.19"
resolved "https://registry.yarnpkg.com/@types/react/-/react-18.2.19.tgz#f77cb2c8307368e624d464a25b9675fa35f95a8b"
integrity sha512-e2S8wmY1ePfM517PqCG80CcE48Xs5k0pwJzuDZsfE8IZRRBfOMCF+XqnFxu6mWtyivum1MQm4aco+WIt6Coimw==
dependencies:
"@types/prop-types" "*"
"@types/scheduler" "*"
csstype "^3.0.2"
"@types/scheduler@*":
version "0.16.3"
resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.3.tgz#cef09e3ec9af1d63d2a6cc5b383a737e24e6dcf5"
integrity sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ==
cojson@^0.0.14:
version "0.0.14"
resolved "https://registry.yarnpkg.com/cojson/-/cojson-0.0.14.tgz#e7b190ade1efc20d6f0fa12411d7208cdc0f19f7"
integrity sha512-TFenIGswEEhnZlCmq+B1NZPztjovZ72AjK1YkkZca54ZFbB1lAHdPt2hqqu/QBO24C9+6DtuoS2ixm6gbSBWCg==
dependencies:
"@noble/ciphers" "^0.1.3"
"@noble/curves" "^1.1.0"
"@noble/hashes" "^1.3.1"
"@scure/base" "^1.1.1"
fast-json-stable-stringify "^2.1.0"
isomorphic-streams "https://github.com/sgwilym/isomorphic-streams.git#aa9394781bfc92f8d7c981be7daf8af4b4cd4fae"
csstype@^3.0.2:
version "3.1.2"
resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.2.tgz#1d4bf9d572f11c14031f0436e1c10bc1f571f50b"
integrity sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==
fast-json-stable-stringify@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633"
integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==
"isomorphic-streams@git+https://github.com/sgwilym/isomorphic-streams.git#aa9394781bfc92f8d7c981be7daf8af4b4cd4fae":
version "1.0.3"
resolved "git+https://github.com/sgwilym/isomorphic-streams.git#aa9394781bfc92f8d7c981be7daf8af4b4cd4fae"
jazz-react@^0.0.6:
version "0.0.6"
resolved "https://registry.yarnpkg.com/jazz-react/-/jazz-react-0.0.6.tgz#53b0245720b10ec31ac8deba45fd8a052b313b06"
integrity sha512-JlYTKUVPpuK3T7cLfk2YwHh3yH+2BPVSuWIQui35U52/gce+HmTMGolqFYGghWMVQtwclaZ0IoEbtuycPiADOQ==
dependencies:
cojson "^0.0.14"
typescript "^5.1.6"
typescript@^5.1.6:
version "5.1.6"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.1.6.tgz#02f8ac202b6dad2c0dd5e0913745b47a37998274"
integrity sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA==

165
yarn.lock
View File

@@ -25,6 +25,11 @@
resolved "https://registry.yarnpkg.com/@assemblyscript/loader/-/loader-0.10.1.tgz#70e45678f06c72fa2e350e8553ec4a4d72b92e06"
integrity sha512-H71nDOOL8Y7kWRLqf6Sums+01Q5msqBW2KhDUTemh1tvY04eSkSXrK0uj/4mmY0Xr16/3zyZmsrxN7CKuRbNRg==
"@auth0/auth0-spa-js@^2.1.2":
version "2.1.2"
resolved "https://registry.yarnpkg.com/@auth0/auth0-spa-js/-/auth0-spa-js-2.1.2.tgz#2217db13ce0feb480a190ed165b36681bd48633c"
integrity sha512-xdA65Z/U7++Y7L9Uwh8Q8OVOs6qgFz+fb7GAzHFjpr1icO37B//xdzLXm7ZRgA19RWrsNe1nme3h896igJSvvw==
"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.21.4", "@babel/code-frame@^7.22.10", "@babel/code-frame@^7.22.5":
version "7.22.10"
resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.22.10.tgz#1c20e612b768fefa75f6e90d6ecb86329247f0a3"
@@ -1149,6 +1154,11 @@
resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.3.2.tgz#6f26dbc8fbc7205873ce3cee2f690eba0d421b39"
integrity sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ==
"@noble/hashes@~1.3.2":
version "1.3.3"
resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.3.3.tgz#39908da56a4adc270147bb07968bf3b16cfe1699"
integrity sha512-V7/fPHgl+jsVPXqqeOzT8egNj2iBIVt+ECeMMG8TdcnTikP3oaBtUVqpT/gYCR68aEBJSF+XbYUxStjbFMqIIA==
"@nodelib/fs.scandir@2.1.5":
version "2.1.5"
resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5"
@@ -1749,6 +1759,19 @@
resolved "https://registry.yarnpkg.com/@scure/base/-/base-1.1.1.tgz#ebb651ee52ff84f420097055f4bf46cfba403938"
integrity sha512-ZxOhsSyxYwLJj3pLZCefNitxsj093tb2vq90mp2txoYeBqbcjDjqFhyM8eUjq/uFm6zJ+mUuqxlS2FkuSY1MTA==
"@scure/base@~1.1.4":
version "1.1.5"
resolved "https://registry.yarnpkg.com/@scure/base/-/base-1.1.5.tgz#1d85d17269fe97694b9c592552dd9e5e33552157"
integrity sha512-Brj9FiG2W1MRQSTB212YVPRrcbjkv48FoZi/u4l/zds/ieRrqsh7aUf6CLwkAq61oKXr/ZlTzlY66gLIj3TFTQ==
"@scure/bip39@^1.2.2":
version "1.2.2"
resolved "https://registry.yarnpkg.com/@scure/bip39/-/bip39-1.2.2.tgz#f3426813f4ced11a47489cbcf7294aa963966527"
integrity sha512-HYf9TUXG80beW+hGAt3TRM8wU6pQoYur9iNypTROm42dorCGmLnFe3eWjz3gOq6G62H2WRh0FCzAR1PI+29zIA==
dependencies:
"@noble/hashes" "~1.3.2"
"@scure/base" "~1.1.4"
"@sigstore/bundle@^1.1.0":
version "1.1.0"
resolved "https://registry.yarnpkg.com/@sigstore/bundle/-/bundle-1.1.0.tgz#17f8d813b09348b16eeed66a8cf1c3d6bd3d04f1"
@@ -1918,6 +1941,11 @@
"@tufjs/canonical-json" "1.0.0"
minimatch "^9.0.0"
"@types/auth0-js@^9.21.5":
version "9.21.5"
resolved "https://registry.yarnpkg.com/@types/auth0-js/-/auth0-js-9.21.5.tgz#2adec936a48ddaaf761a9118a759dfcdecfa3316"
integrity sha512-hQInFZkMPjrV8s49lefXBd6nym8+kdFxhdzzjlMAvy+7qHSk+TLCwUoMhvU6FQP3prj2j4NTtsufaqkUwiPgug==
"@types/babel__core@^7.1.14":
version "7.20.1"
resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.20.1.tgz#916ecea274b0c776fec721e333e55762d3a9614b"
@@ -2649,6 +2677,11 @@ arrify@^2.0.1:
resolved "https://registry.yarnpkg.com/arrify/-/arrify-2.0.1.tgz#c9655e9331e0abcd588d2a7cad7e9956f66701fa"
integrity sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==
asap@^2.0.0:
version "2.0.6"
resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46"
integrity sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==
assertion-error@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-1.1.0.tgz#e60b6b0e8f301bd97e5375215bda406c85118c0b"
@@ -2671,6 +2704,20 @@ asynckit@^0.4.0:
resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==
auth0-js@^9.23.3:
version "9.23.3"
resolved "https://registry.yarnpkg.com/auth0-js/-/auth0-js-9.23.3.tgz#ec3c1e04eeb409fac780c84d32388dd80d3a52d7"
integrity sha512-VVh3/1SiECWlwDU3XQ3Xhg40W9Buu18jYZYO93PEerApacm508v67SxBLKh5WeCGaqCUyPamU+NusguhWVn1Qw==
dependencies:
base64-js "^1.5.1"
idtoken-verifier "^2.2.4"
js-cookie "^2.2.0"
minimist "^1.2.5"
qs "^6.10.1"
superagent "^7.1.5"
url-join "^4.0.1"
winchan "^0.2.2"
autoprefixer@^10.4.14:
version "10.4.15"
resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-10.4.15.tgz#a1230f4aeb3636b89120b34a1f513e2f6834d530"
@@ -2767,7 +2814,7 @@ balanced-match@^1.0.0:
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee"
integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==
base64-js@^1.2.0, base64-js@^1.3.1:
base64-js@^1.2.0, base64-js@^1.3.1, base64-js@^1.5.1:
version "1.5.1"
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a"
integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==
@@ -3303,6 +3350,11 @@ compare-func@^2.0.0:
array-ify "^1.0.0"
dot-prop "^5.1.0"
component-emitter@^1.3.0:
version "1.3.1"
resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.1.tgz#ef1d5796f7d93f135ee6fb684340b26403c97d17"
integrity sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==
compress-commons@^4.1.0:
version "4.1.1"
resolved "https://registry.yarnpkg.com/compress-commons/-/compress-commons-4.1.1.tgz#df2a09a7ed17447642bad10a85cc9a19e5c42a7d"
@@ -3416,6 +3468,11 @@ convert-source-map@^2.0.0:
resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a"
integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==
cookiejar@^2.1.3:
version "2.1.4"
resolved "https://registry.yarnpkg.com/cookiejar/-/cookiejar-2.1.4.tgz#ee669c1fea2cf42dc31585469d193fef0d65771b"
integrity sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==
copy-to-clipboard@^3.3.1:
version "3.3.3"
resolved "https://registry.yarnpkg.com/copy-to-clipboard/-/copy-to-clipboard-3.3.3.tgz#55ac43a1db8ae639a4bd99511c148cdd1b83a1b0"
@@ -3481,6 +3538,11 @@ cross-spawn@^7.0.0, cross-spawn@^7.0.2, cross-spawn@^7.0.3:
shebang-command "^2.0.0"
which "^2.0.1"
crypto-js@^4.2.0:
version "4.2.0"
resolved "https://registry.yarnpkg.com/crypto-js/-/crypto-js-4.2.0.tgz#4d931639ecdfd12ff80e8186dba6af2c2e856631"
integrity sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==
css-in-js-utils@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/css-in-js-utils/-/css-in-js-utils-3.1.0.tgz#640ae6a33646d401fc720c54fc61c42cd76ae2bb"
@@ -3736,6 +3798,14 @@ devtools-protocol@^0.0.1182435:
resolved "https://registry.yarnpkg.com/devtools-protocol/-/devtools-protocol-0.0.1182435.tgz#68155464f987d66390fae28b02782a1b9c153a0c"
integrity sha512-EmlkWb62wSbQNE1gRZZsi4KZYRaF5Skpp183LhRU7+sadKR06O1dHCjZmFSEG6Kv7P6S/UYLxcY3NlYwqKM99w==
dezalgo@^1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/dezalgo/-/dezalgo-1.0.4.tgz#751235260469084c132157dfa857f386d4c33d81"
integrity sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==
dependencies:
asap "^2.0.0"
wrappy "1"
didyoumean@^1.2.2:
version "1.2.2"
resolved "https://registry.yarnpkg.com/didyoumean/-/didyoumean-1.2.2.tgz#989346ffe9e839b4555ecf5666edea0d3e8ad037"
@@ -3985,6 +4055,11 @@ es-to-primitive@^1.2.1:
is-date-object "^1.0.1"
is-symbol "^1.0.2"
es6-promise@^4.2.8:
version "4.2.8"
resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.8.tgz#4eb21594c972bc40553d276e510539143db53e0a"
integrity sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==
esbuild-android-64@0.14.54:
version "0.14.54"
resolved "https://registry.yarnpkg.com/esbuild-android-64/-/esbuild-android-64-0.14.54.tgz#505f41832884313bbaffb27704b8bcaa2d8616be"
@@ -4436,6 +4511,11 @@ fast-loops@^1.1.3:
resolved "https://registry.yarnpkg.com/fast-loops/-/fast-loops-1.1.3.tgz#ce96adb86d07e7bf9b4822ab9c6fac9964981f75"
integrity sha512-8EZzEP0eKkEEVX+drtd9mtuQ+/QrlfW/5MlwcwK5Nds6EkZ/tRzEexkzUY2mIssnAyVLT+TKHuRXmFNNXYUd6g==
fast-safe-stringify@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz#c406a83b6e70d9e35ce3b30a81141df30aeba884"
integrity sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==
fast-shallow-equal@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/fast-shallow-equal/-/fast-shallow-equal-1.0.0.tgz#d4dcaf6472440dcefa6f88b98e3251e27f25628b"
@@ -4606,6 +4686,16 @@ formdata-polyfill@^4.0.10:
dependencies:
fetch-blob "^3.1.2"
formidable@^2.0.1:
version "2.1.2"
resolved "https://registry.yarnpkg.com/formidable/-/formidable-2.1.2.tgz#fa973a2bec150e4ce7cac15589d7a25fc30ebd89"
integrity sha512-CM3GuJ57US06mlpQ47YcunuUZ9jpm8Vx+P2CGt2j7HpgkKZO/DJYQ0Bobim8G6PFQmK5lOqOOdUXboU+h73A4g==
dependencies:
dezalgo "^1.0.4"
hexoid "^1.0.0"
once "^1.4.0"
qs "^6.11.0"
fraction.js@^4.2.0:
version "4.2.0"
resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.2.0.tgz#448e5109a313a3527f5a3ab2119ec4cf0e0e2950"
@@ -5128,6 +5218,11 @@ hdr-histogram-percentiles-obj@^3.0.0:
resolved "https://registry.yarnpkg.com/hdr-histogram-percentiles-obj/-/hdr-histogram-percentiles-obj-3.0.0.tgz#9409f4de0c2dda78e61de2d9d78b1e9f3cba283c"
integrity sha512-7kIufnBqdsBGcSZLPJwqHT3yhk1QTsSlFsVD3kx5ixH/AlgBs9yM1q6DPhXZ8f8gtdqgh7N7/5btRLpQsS2gHw==
hexoid@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/hexoid/-/hexoid-1.0.0.tgz#ad10c6573fb907de23d9ec63a711267d9dc9bc18"
integrity sha512-QFLV0taWQOZtvIRIAdBChesmogZrtuXvVWsFHZTk2SU+anspqZ2vMnoLg7IE1+Uk16N19APic1BuF8bC8c2m5g==
hosted-git-info@^2.1.4:
version "2.8.9"
resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.9.tgz#dffc0bf9a21c02209090f2aa69429e1414daf3f9"
@@ -5241,6 +5336,18 @@ iconv-lite@^0.6.2:
dependencies:
safer-buffer ">= 2.1.2 < 3.0.0"
idtoken-verifier@^2.2.4:
version "2.2.4"
resolved "https://registry.yarnpkg.com/idtoken-verifier/-/idtoken-verifier-2.2.4.tgz#5749bd3fc9b757db40ad764484173584fb19fb55"
integrity sha512-5t7O8cNHpJBB8FnwLD0qFZqy/+qGICObQKUl0njD6vXKHhpZPLEe8LU7qv/GBWB3Qv5e/wAIFHYVi4SoQwdOxQ==
dependencies:
base64-js "^1.5.1"
crypto-js "^4.2.0"
es6-promise "^4.2.8"
jsbn "^1.1.0"
unfetch "^4.2.0"
url-join "^4.0.1"
ieee754@^1.1.13:
version "1.2.1"
resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352"
@@ -6083,7 +6190,7 @@ jiti@^1.18.2:
resolved "https://registry.yarnpkg.com/jiti/-/jiti-1.19.1.tgz#fa99e4b76a23053e0e7cde098efe1704a14c16f1"
integrity sha512-oVhqoRDaBXf7sjkll95LHVS6Myyyb1zaunVwk4Z0+WPSW4gjS0pl01zYKHScTuyEhQsFxV5L4DR5r+YqSyqyyg==
js-cookie@^2.2.1:
js-cookie@^2.2.0, js-cookie@^2.2.1:
version "2.2.1"
resolved "https://registry.yarnpkg.com/js-cookie/-/js-cookie-2.2.1.tgz#69e106dc5d5806894562902aa5baec3744e9b2b8"
integrity sha512-HvdH2LzI/EAZcUwA8+0nKNtWHqS+ZmijLA30RwZA0bo7ToCckjK5MkGhjED9KoRcXO6BaGI3I9UIzSA1FKFPOQ==
@@ -6108,6 +6215,11 @@ js-yaml@^3.10.0, js-yaml@^3.13.0, js-yaml@^3.13.1, js-yaml@^3.6.1:
argparse "^1.0.7"
esprima "^4.0.0"
jsbn@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-1.1.0.tgz#b01307cb29b618a1ed26ec79e911f803c4da0040"
integrity sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==
jsesc@^2.5.1:
version "2.5.2"
resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4"
@@ -6696,6 +6808,11 @@ merge2@^1.3.0, merge2@^1.4.1:
resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae"
integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==
methods@^1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee"
integrity sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==
micromatch@^4.0.2, micromatch@^4.0.4, micromatch@^4.0.5:
version "4.0.5"
resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6"
@@ -6716,6 +6833,11 @@ mime-types@^2.1.12:
dependencies:
mime-db "1.52.0"
mime@2.6.0:
version "2.6.0"
resolved "https://registry.yarnpkg.com/mime/-/mime-2.6.0.tgz#a2a682a95cd4d0cb1d6257e28f83da7e35800367"
integrity sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==
mimic-fn@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b"
@@ -8004,6 +8126,13 @@ qrcode@^1.5.3:
pngjs "^5.0.0"
yargs "^15.3.1"
qs@^6.10.1, qs@^6.10.3, qs@^6.11.0:
version "6.11.2"
resolved "https://registry.yarnpkg.com/qs/-/qs-6.11.2.tgz#64bea51f12c1f5da1bc01496f48ffcff7c69d7d9"
integrity sha512-tDNIz22aBzCDxLtVH++VnTfzxlfeK5CbqohpSqpJgj1Wg/cQbStNAz3NuqCs5vV+pjBsK4x4pN9HlVh7rcYRiA==
dependencies:
side-channel "^1.0.4"
query-selector-shadow-dom@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/query-selector-shadow-dom/-/query-selector-shadow-dom-1.0.1.tgz#1c7b0058eff4881ac44f45d8f84ede32e9a2f349"
@@ -9028,6 +9157,23 @@ sucrase@^3.32.0:
pirates "^4.0.1"
ts-interface-checker "^0.1.9"
superagent@^7.1.5:
version "7.1.6"
resolved "https://registry.yarnpkg.com/superagent/-/superagent-7.1.6.tgz#64f303ed4e4aba1e9da319f134107a54cacdc9c6"
integrity sha512-gZkVCQR1gy/oUXr+kxJMLDjla434KmSOKbx5iGD30Ql+AkJQ/YlPKECJy2nhqOsHLjGHzoDTXNSjhnvWhzKk7g==
dependencies:
component-emitter "^1.3.0"
cookiejar "^2.1.3"
debug "^4.3.4"
fast-safe-stringify "^2.1.1"
form-data "^4.0.0"
formidable "^2.0.1"
methods "^1.1.2"
mime "2.6.0"
qs "^6.10.3"
readable-stream "^3.6.0"
semver "^7.3.7"
supports-color@^5.3.0:
version "5.5.0"
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f"
@@ -9532,6 +9678,11 @@ unbzip2-stream@1.4.3:
buffer "^5.2.1"
through "^2.3.8"
unfetch@^4.2.0:
version "4.2.0"
resolved "https://registry.yarnpkg.com/unfetch/-/unfetch-4.2.0.tgz#7e21b0ef7d363d8d9af0fb929a5555f6ef97a3be"
integrity sha512-F9p7yYCn6cIW9El1zi0HI6vqpeIvBsr3dSuRO6Xuppb1u5rXpCPmMvLSyECLhybr9isec8Ohl0hPekMVrEinDA==
uniqolor@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/uniqolor/-/uniqolor-1.1.0.tgz#7519f81133cd54a1f4a59c33c81dbe04a3ad155d"
@@ -9602,6 +9753,11 @@ uri-js@^4.2.2:
dependencies:
punycode "^2.1.0"
url-join@^4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/url-join/-/url-join-4.0.1.tgz#b642e21a2646808ffa178c4c5fda39844e12cde7"
integrity sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==
use-callback-ref@^1.3.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/use-callback-ref/-/use-callback-ref-1.3.0.tgz#772199899b9c9a50526fedc4993fc7fa1f7e32d5"
@@ -9895,6 +10051,11 @@ wide-align@^1.1.5:
dependencies:
string-width "^1.0.2 || 2 || 3 || 4"
winchan@^0.2.2:
version "0.2.2"
resolved "https://registry.yarnpkg.com/winchan/-/winchan-0.2.2.tgz#6766917b88e5e1cb75f455ffc7cc13f51e5c834e"
integrity sha512-pvN+IFAbRP74n/6mc6phNyCH8oVkzXsto4KCHPJ2AScniAnA1AmeLI03I2BzjePpaClGSI4GUMowzsD3qz5PRQ==
wordwrap@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb"