Compare commits
23 Commits
cojson@0.5
...
jazz-react
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8def1bb29e | ||
|
|
d379b04e33 | ||
|
|
17a30e054e | ||
|
|
7d8f4b4c00 | ||
|
|
e2a3896bf0 | ||
|
|
446de8e0ff | ||
|
|
5ae6c95878 | ||
|
|
7cde349a50 | ||
|
|
61e640f574 | ||
|
|
ed122d9d8e | ||
|
|
34817f4536 | ||
|
|
0998a0eabf | ||
|
|
a96108478b | ||
|
|
a4cf4c40d4 | ||
|
|
934fe4d29b | ||
|
|
408012f2e5 | ||
|
|
d0078b830e | ||
|
|
e52948b2b7 | ||
|
|
53bb1b230b | ||
|
|
54e83aeaaa | ||
|
|
aa3129cab5 | ||
|
|
90520dddd7 | ||
|
|
03eb77070a |
106
.github/workflows/build-and-deploy.yaml
vendored
106
.github/workflows/build-and-deploy.yaml
vendored
@@ -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
|
||||
18
examples/chat-passphrase/.eslintrc.cjs
Normal file
18
examples/chat-passphrase/.eslintrc.cjs
Normal 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
24
examples/chat-passphrase/.gitignore
vendored
Normal 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?
|
||||
17
examples/chat-passphrase/CHANGELOG.md
Normal file
17
examples/chat-passphrase/CHANGELOG.md
Normal 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
|
||||
4
examples/chat-passphrase/Dockerfile
Normal file
4
examples/chat-passphrase/Dockerfile
Normal 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/
|
||||
64
examples/chat-passphrase/README.md
Normal file
64
examples/chat-passphrase/README.md
Normal 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).
|
||||
14
examples/chat-passphrase/index.html
Normal file
14
examples/chat-passphrase/index.html
Normal 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>
|
||||
56
examples/chat-passphrase/job-template.nomad
Normal file
56
examples/chat-passphrase/job-template.nomad
Normal 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
|
||||
48
examples/chat-passphrase/package.json
Normal file
48
examples/chat-passphrase/package.json
Normal 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"
|
||||
}
|
||||
}
|
||||
6
examples/chat-passphrase/postcss.config.js
Normal file
6
examples/chat-passphrase/postcss.config.js
Normal file
@@ -0,0 +1,6 @@
|
||||
export default {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
}
|
||||
BIN
examples/chat-passphrase/public/jazz-logo.png
Normal file
BIN
examples/chat-passphrase/public/jazz-logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 7.3 KiB |
45
examples/chat-passphrase/src/app.tsx
Normal file
45
examples/chat-passphrase/src/app.tsx
Normal 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>
|
||||
}
|
||||
43
examples/chat-passphrase/src/chatWindow.tsx
Normal file
43
examples/chat-passphrase/src/chatWindow.tsx
Normal 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 = '';
|
||||
}}/>
|
||||
}
|
||||
4
examples/chat-passphrase/src/dataModel.ts
Normal file
4
examples/chat-passphrase/src/dataModel.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
import { CoMap, CoList } from 'cojson';
|
||||
|
||||
export type Chat = CoList<Message['id']>;
|
||||
export type Message = CoMap<{ text: string }>;
|
||||
78
examples/chat-passphrase/src/index.css
Normal file
78
examples/chat-passphrase/src/index.css
Normal 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;
|
||||
}
|
||||
}
|
||||
1
examples/chat-passphrase/src/vite-env.d.ts
vendored
Normal file
1
examples/chat-passphrase/src/vite-env.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/// <reference types="vite/client" />
|
||||
75
examples/chat-passphrase/tailwind.config.js
Normal file
75
examples/chat-passphrase/tailwind.config.js
Normal 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")],
|
||||
}
|
||||
29
examples/chat-passphrase/tsconfig.json
Normal file
29
examples/chat-passphrase/tsconfig.json
Normal 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" }]
|
||||
}
|
||||
10
examples/chat-passphrase/tsconfig.node.json
Normal file
10
examples/chat-passphrase/tsconfig.node.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"skipLibCheck": true,
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "bundler",
|
||||
"allowSyntheticDefaultImports": true
|
||||
},
|
||||
"include": ["vite.config.ts"]
|
||||
}
|
||||
16
examples/chat-passphrase/vite.config.ts
Normal file
16
examples/chat-passphrase/vite.config.ts
Normal 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
24
examples/counter-js-auth0/.gitignore
vendored
Normal 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?
|
||||
4
examples/counter-js-auth0/Dockerfile
Normal file
4
examples/counter-js-auth0/Dockerfile
Normal 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/
|
||||
16
examples/counter-js-auth0/index.html
Normal file
16
examples/counter-js-auth0/index.html
Normal 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>
|
||||
113
examples/counter-js-auth0/index.ts
Normal file
113
examples/counter-js-auth0/index.ts
Normal 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);
|
||||
});
|
||||
};
|
||||
56
examples/counter-js-auth0/job-template.nomad
Normal file
56
examples/counter-js-auth0/job-template.nomad
Normal 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
|
||||
22
examples/counter-js-auth0/package.json
Normal file
22
examples/counter-js-auth0/package.json
Normal 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"
|
||||
}
|
||||
}
|
||||
29
examples/counter-js-auth0/tsconfig.json
Normal file
29
examples/counter-js-auth0/tsconfig.json
Normal 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" }]
|
||||
}
|
||||
10
examples/counter-js-auth0/tsconfig.node.json
Normal file
10
examples/counter-js-auth0/tsconfig.node.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"skipLibCheck": true,
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "bundler",
|
||||
"allowSyntheticDefaultImports": true
|
||||
},
|
||||
"include": ["vite.config.ts"]
|
||||
}
|
||||
16
examples/counter-js-auth0/vite.config.ts
Normal file
16
examples/counter-js-auth0/vite.config.ts
Normal 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
|
||||
}
|
||||
})
|
||||
@@ -1,5 +1,24 @@
|
||||
# twit-stresstest
|
||||
|
||||
## 0.2.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- Make addMember and removeMember take loaded Accounts instead of just IDs
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-nodejs@0.6.0
|
||||
|
||||
## 0.1.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Allow account migrations to be async
|
||||
- Updated dependencies
|
||||
- jazz-nodejs@0.5.3
|
||||
|
||||
## 0.1.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "twit-stresstest",
|
||||
"version": "0.1.0",
|
||||
"version": "0.2.0",
|
||||
"main": "dist/twit-stresstest/index.js",
|
||||
"type":"module",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"jazz-nodejs": "^0.5.0"
|
||||
"jazz-nodejs": "^0.6.0"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "rm -rf ./dist && tsc --sourceMap --outDir dist",
|
||||
|
||||
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
@@ -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
248
homepage/app/mesh/page.mdx
Normal 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 × 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 × 10 FPS × 10s/min × 60min/h × 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 — 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>
|
||||
@@ -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 — 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 — 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
|
||||
|
||||
14
homepage/codeSamples/examples/chat/src/index.tsx
Normal file
14
homepage/codeSamples/examples/chat/src/index.tsx
Normal file
File diff suppressed because one or more lines are too long
@@ -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;
|
||||
@@ -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 × 10 pages = 30,000 transactions
|
||||
- 30,000 transactions stored = ${30000 * pricePerTxStored} / mo
|
||||
- 3 × 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 × 10 FPS × 10s/min × 60min/h * 2h/day × 30days = 1.44M transactions
|
||||
- 1.44M transactions stored = ${1440000 * pricePerTxStored} / mo = ${1440000 * pricePerTxStored / 4} / mo / user
|
||||
- 3 × 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 × 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 — 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>
|
||||
@@ -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"
|
||||
),
|
||||
]
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
@@ -1,5 +1,17 @@
|
||||
# cojson-simple-sync
|
||||
|
||||
## 0.6.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- Make addMember and removeMember take loaded Accounts instead of just IDs
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- cojson@0.6.0
|
||||
- cojson-storage-sqlite@0.5.2
|
||||
|
||||
## 0.5.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"types": "src/index.ts",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"version": "0.5.0",
|
||||
"version": "0.6.0",
|
||||
"devDependencies": {
|
||||
"@types/jest": "^29.5.3",
|
||||
"@types/ws": "^8.5.5",
|
||||
@@ -16,8 +16,8 @@
|
||||
"typescript": "5.0.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"cojson": "^0.5.0",
|
||||
"cojson-storage-sqlite": "^0.5.0",
|
||||
"cojson": "^0.6.0",
|
||||
"cojson-storage-sqlite": "^0.5.2",
|
||||
"ws": "^8.13.0"
|
||||
},
|
||||
"scripts": {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { AnonymousControlledAccount, LocalNode, cojsonInternals, cojsonReady } from "cojson";
|
||||
import { ControlledAgent, LocalNode, cojsonInternals, cojsonReady } from "cojson";
|
||||
import { WebSocketServer } from "ws";
|
||||
import { SQLiteStorage } from "cojson-storage-sqlite";
|
||||
import { websocketReadableStream, websocketWritableStream } from "./websocketStreams.js";
|
||||
@@ -15,7 +15,7 @@ const agentSecret = cojsonInternals.newRandomAgentSecret();
|
||||
const agentID = cojsonInternals.getAgentID(agentSecret);
|
||||
|
||||
const localNode = new LocalNode(
|
||||
new AnonymousControlledAccount(agentSecret),
|
||||
new ControlledAgent(agentSecret),
|
||||
cojsonInternals.newRandomSessionID(agentID)
|
||||
);
|
||||
|
||||
|
||||
@@ -1,5 +1,16 @@
|
||||
# cojson-storage-indexeddb
|
||||
|
||||
## 0.6.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- Make addMember and removeMember take loaded Accounts instead of just IDs
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- cojson@0.6.0
|
||||
|
||||
## 0.5.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"name": "cojson-storage-indexeddb",
|
||||
"version": "0.5.0",
|
||||
"version": "0.6.0",
|
||||
"main": "dist/index.js",
|
||||
"types": "src/index.ts",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"cojson": "^0.5.0",
|
||||
"cojson": "^0.6.0",
|
||||
"typescript": "^5.1.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { expect, test } from "vitest";
|
||||
import { AnonymousControlledAccount, LocalNode, cojsonInternals } from "cojson";
|
||||
import { ControlledAgent, LocalNode, cojsonInternals } from "cojson";
|
||||
import { IDBStorage } from ".";
|
||||
|
||||
test.skip("Should be able to initialize and load from empty DB", async () => {
|
||||
const agentSecret = cojsonInternals.newRandomAgentSecret();
|
||||
|
||||
const node = new LocalNode(
|
||||
new AnonymousControlledAccount(agentSecret),
|
||||
new ControlledAgent(agentSecret),
|
||||
cojsonInternals.newRandomSessionID(
|
||||
cojsonInternals.getAgentID(agentSecret)
|
||||
)
|
||||
@@ -27,7 +27,7 @@ test("Should be able to sync data to database and then load that from a new node
|
||||
const agentSecret = cojsonInternals.newRandomAgentSecret();
|
||||
|
||||
const node1 = new LocalNode(
|
||||
new AnonymousControlledAccount(agentSecret),
|
||||
new ControlledAgent(agentSecret),
|
||||
cojsonInternals.newRandomSessionID(
|
||||
cojsonInternals.getAgentID(agentSecret)
|
||||
)
|
||||
@@ -50,7 +50,7 @@ test("Should be able to sync data to database and then load that from a new node
|
||||
await new Promise((resolve) => setTimeout(resolve, 200));
|
||||
|
||||
const node2 = new LocalNode(
|
||||
new AnonymousControlledAccount(agentSecret),
|
||||
new ControlledAgent(agentSecret),
|
||||
cojsonInternals.newRandomSessionID(
|
||||
cojsonInternals.getAgentID(agentSecret)
|
||||
)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
228
packages/cojson-storage-indexeddb/src/syncPromises.ts
Normal file
228
packages/cojson-storage-indexeddb/src/syncPromises.ts
Normal 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);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,12 @@
|
||||
# cojson-storage-sqlite
|
||||
|
||||
## 0.5.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- cojson@0.6.0
|
||||
|
||||
## 0.5.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
{
|
||||
"name": "cojson-storage-sqlite",
|
||||
"type": "module",
|
||||
"version": "0.5.1",
|
||||
"version": "0.5.2",
|
||||
"main": "dist/index.js",
|
||||
"types": "src/index.ts",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"better-sqlite3": "^8.5.2",
|
||||
"cojson": "^0.5.0",
|
||||
"cojson": "^0.6.0",
|
||||
"typescript": "^5.1.6",
|
||||
"@types/better-sqlite3": "^7.6.4"
|
||||
},
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
# cojson-transport-nodejs-ws
|
||||
|
||||
## 0.5.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- cojson@0.6.0
|
||||
|
||||
## 0.5.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "cojson-transport-nodejs-ws",
|
||||
"type": "module",
|
||||
"version": "0.5.0",
|
||||
"version": "0.5.1",
|
||||
"main": "dist/index.js",
|
||||
"types": "src/index.ts",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"cojson": "^0.5.0",
|
||||
"cojson": "^0.6.0",
|
||||
"typescript": "^5.1.6",
|
||||
"ws": "^8.14.2"
|
||||
},
|
||||
|
||||
@@ -1,5 +1,35 @@
|
||||
# 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
|
||||
|
||||
- Make addMember and removeMember take loaded Accounts instead of just IDs
|
||||
|
||||
## 0.5.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Allow account migrations to be async
|
||||
|
||||
## 0.5.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
"types": "src/index.ts",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"version": "0.5.1",
|
||||
"version": "0.6.3",
|
||||
"devDependencies": {
|
||||
"@noble/curves": "^1.2.0",
|
||||
"@types/jest": "^29.5.3",
|
||||
|
||||
@@ -27,7 +27,7 @@ import { Group } from "./coValues/group.js";
|
||||
import { LocalNode } from "./localNode.js";
|
||||
import { CoValueKnownState, NewContentMessage } from "./sync.js";
|
||||
import { AgentID, RawCoID, SessionID, TransactionID } from "./ids.js";
|
||||
import { AccountID, GeneralizedControlledAccount } from "./coValues/account.js";
|
||||
import { AccountID, ControlledAccountOrAgent } from "./coValues/account.js";
|
||||
import { Stringified, parseJSON, stableStringify } from "./jsonStringify.js";
|
||||
import { coreToCoValue } from "./coreToCoValue.js";
|
||||
import { expectGroup } from "./typeUtils/expectGroup.js";
|
||||
@@ -133,7 +133,7 @@ export class CoValueCore {
|
||||
}
|
||||
|
||||
testWithDifferentAccount(
|
||||
account: GeneralizedControlledAccount,
|
||||
account: ControlledAccountOrAgent,
|
||||
currentSessionID: SessionID
|
||||
): CoValueCore {
|
||||
const newNode = this.node.testWithDifferentAccount(
|
||||
|
||||
@@ -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
|
||||
@@ -36,7 +37,7 @@ export class Account<
|
||||
R extends CoMap = CoMap,
|
||||
Meta extends AccountMeta = AccountMeta
|
||||
> extends Group<P, R, Meta> {
|
||||
getCurrentAgentID(): AgentID {
|
||||
currentAgentID(): AgentID {
|
||||
const agents = this.keys().filter((k): k is AgentID =>
|
||||
k.startsWith("sealer_")
|
||||
);
|
||||
@@ -51,7 +52,7 @@ export class Account<
|
||||
}
|
||||
}
|
||||
|
||||
export interface GeneralizedControlledAccount {
|
||||
export interface ControlledAccountOrAgent {
|
||||
id: AccountID | AgentID;
|
||||
agentSecret: AgentSecret;
|
||||
|
||||
@@ -64,12 +65,12 @@ export interface GeneralizedControlledAccount {
|
||||
|
||||
/** @hidden */
|
||||
export class ControlledAccount<
|
||||
P extends Profile = Profile,
|
||||
R extends CoMap = CoMap,
|
||||
Meta extends AccountMeta = AccountMeta
|
||||
>
|
||||
P extends Profile = Profile,
|
||||
R extends CoMap = CoMap,
|
||||
Meta extends AccountMeta = AccountMeta
|
||||
>
|
||||
extends Account<P, R, Meta>
|
||||
implements GeneralizedControlledAccount
|
||||
implements ControlledAccountOrAgent
|
||||
{
|
||||
agentSecret: AgentSecret;
|
||||
|
||||
@@ -116,8 +117,8 @@ Meta extends AccountMeta = AccountMeta
|
||||
}
|
||||
|
||||
/** @hidden */
|
||||
export class AnonymousControlledAccount
|
||||
implements GeneralizedControlledAccount
|
||||
export class ControlledAgent
|
||||
implements ControlledAccountOrAgent
|
||||
{
|
||||
agentSecret: AgentSecret;
|
||||
|
||||
@@ -158,10 +159,17 @@ export type ProfileShape = {
|
||||
};
|
||||
export type ProfileMeta = { type: "profile" };
|
||||
|
||||
export class Profile<Shape extends ProfileShape = ProfileShape, Meta extends ProfileMeta = ProfileMeta> extends CoMap<Shape, Meta> {
|
||||
export class Profile<
|
||||
Shape extends ProfileShape = ProfileShape,
|
||||
Meta extends ProfileMeta = ProfileMeta
|
||||
> extends CoMap<Shape, Meta> {}
|
||||
|
||||
}
|
||||
|
||||
export type AccountMigration< P extends Profile = Profile,
|
||||
R extends CoMap = CoMap,
|
||||
Meta extends AccountMeta = AccountMeta> = (account: ControlledAccount<P, R, Meta>, profile: P) => void;
|
||||
export type AccountMigration<
|
||||
P extends Profile = Profile,
|
||||
R extends CoMap = CoMap,
|
||||
Meta extends AccountMeta = AccountMeta
|
||||
> = (
|
||||
account: ControlledAccount<P, R, Meta>,
|
||||
profile: P,
|
||||
localNode: LocalNode
|
||||
) => void | Promise<void>;
|
||||
|
||||
@@ -18,7 +18,7 @@ import {
|
||||
getAgentID,
|
||||
} from "../crypto.js";
|
||||
import { AgentID, isAgentID } from "../ids.js";
|
||||
import { AccountID, Profile } from "./account.js";
|
||||
import { Account, AccountID, ControlledAccountOrAgent, Profile } from "./account.js";
|
||||
import { Role } from "../permissions.js";
|
||||
import { base58 } from "@scure/base";
|
||||
|
||||
@@ -94,13 +94,13 @@ export class Group<
|
||||
*
|
||||
* @category 2. Role changing
|
||||
*/
|
||||
addMember(accountID: AccountID | Everyone, role: Role): this {
|
||||
return this.addMemberInternal(accountID, role);
|
||||
addMember(account: Account | ControlledAccountOrAgent | Everyone, role: Role): this {
|
||||
return this.addMemberInternal(account, role);
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
addMemberInternal(
|
||||
accountID: AccountID | AgentID | Everyone,
|
||||
account: Account | ControlledAccountOrAgent | AgentID | Everyone,
|
||||
role: Role
|
||||
): this {
|
||||
return this.mutate((mutable) => {
|
||||
@@ -110,15 +110,15 @@ export class Group<
|
||||
throw new Error("Can't add member without read key secret");
|
||||
}
|
||||
|
||||
if (accountID === EVERYONE) {
|
||||
if (account === EVERYONE) {
|
||||
if (!(role === "reader" || role === "writer")) {
|
||||
throw new Error(
|
||||
"Can't make everyone something other than reader or writer"
|
||||
);
|
||||
}
|
||||
mutable.set(accountID, role, "trusting");
|
||||
mutable.set(account, role, "trusting");
|
||||
|
||||
if (mutable.get(accountID) !== role) {
|
||||
if (mutable.get(account) !== role) {
|
||||
throw new Error("Failed to set role");
|
||||
}
|
||||
|
||||
@@ -128,18 +128,16 @@ export class Group<
|
||||
"trusting"
|
||||
);
|
||||
} else {
|
||||
const agent = this.core.node.resolveAccountAgent(
|
||||
accountID,
|
||||
"Expected to know agent to add them to group"
|
||||
);
|
||||
mutable.set(accountID, role, "trusting");
|
||||
const memberKey = typeof account === "string" ? account : account.id;
|
||||
const agent = typeof account === "string" ? account : account.currentAgentID();
|
||||
mutable.set(memberKey, role, "trusting");
|
||||
|
||||
if (mutable.get(accountID) !== role) {
|
||||
if (mutable.get(memberKey) !== role) {
|
||||
throw new Error("Failed to set role");
|
||||
}
|
||||
|
||||
mutable.set(
|
||||
`${currentReadKey.id}_for_${accountID}`,
|
||||
`${currentReadKey.id}_for_${memberKey}`,
|
||||
seal({
|
||||
message: currentReadKey.secret,
|
||||
from: this.core.node.account.currentSealerSecret(),
|
||||
@@ -225,15 +223,14 @@ export class Group<
|
||||
*
|
||||
* @category 2. Role changing
|
||||
*/
|
||||
removeMember(accountID: AccountID): this {
|
||||
return this.removeMemberInternal(accountID);
|
||||
removeMember(account: Account | ControlledAccountOrAgent | Everyone): this {
|
||||
return this.removeMemberInternal(account);
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
removeMemberInternal(accountID: AccountID | AgentID): this {
|
||||
const afterRevoke = this.mutate((map) => {
|
||||
map.set(accountID, "revoked", "trusting");
|
||||
});
|
||||
removeMemberInternal(account: Account | ControlledAccountOrAgent | AgentID | Everyone): this {
|
||||
const memberKey = typeof account === "string" ? account : account.id;
|
||||
const afterRevoke = this.set(memberKey, "revoked", "trusting");
|
||||
|
||||
return afterRevoke.rotateReadKey();
|
||||
}
|
||||
|
||||
@@ -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";
|
||||
@@ -27,7 +28,7 @@ import {
|
||||
} from "./crypto.js";
|
||||
import { connectedPeers } from "./streamUtils.js";
|
||||
import {
|
||||
AnonymousControlledAccount,
|
||||
ControlledAgent,
|
||||
ControlledAccount,
|
||||
} from "./coValues/account.js";
|
||||
import type { Role } from "./permissions.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 {
|
||||
@@ -108,7 +111,7 @@ export {
|
||||
SessionID,
|
||||
Media,
|
||||
CoValueCore,
|
||||
AnonymousControlledAccount,
|
||||
ControlledAgent,
|
||||
ControlledAccount,
|
||||
cryptoReady as cojsonReady,
|
||||
MAX_RECOMMENDED_TX_SIZE,
|
||||
|
||||
@@ -26,9 +26,9 @@ import {
|
||||
Account,
|
||||
AccountMeta,
|
||||
accountHeaderForInitialAgentSecret,
|
||||
GeneralizedControlledAccount,
|
||||
ControlledAccountOrAgent,
|
||||
ControlledAccount,
|
||||
AnonymousControlledAccount,
|
||||
ControlledAgent,
|
||||
AccountID,
|
||||
Profile,
|
||||
AccountMigration,
|
||||
@@ -52,7 +52,7 @@ export class LocalNode {
|
||||
/** @internal */
|
||||
coValues: { [key: RawCoID]: CoValueState } = {};
|
||||
/** @category 3. Low-level */
|
||||
account: GeneralizedControlledAccount;
|
||||
account: ControlledAccountOrAgent;
|
||||
/** @category 3. Low-level */
|
||||
currentSessionID: SessionID;
|
||||
/** @category 3. Low-level */
|
||||
@@ -60,7 +60,7 @@ export class LocalNode {
|
||||
|
||||
/** @category 3. Low-level */
|
||||
constructor(
|
||||
account: GeneralizedControlledAccount,
|
||||
account: ControlledAccountOrAgent,
|
||||
currentSessionID: SessionID
|
||||
) {
|
||||
this.account = account;
|
||||
@@ -68,27 +68,29 @@ export class LocalNode {
|
||||
}
|
||||
|
||||
/** @category 2. Node Creation */
|
||||
static withNewlyCreatedAccount<
|
||||
static async withNewlyCreatedAccount<
|
||||
P extends Profile = Profile,
|
||||
R extends CoMap = CoMap,
|
||||
Meta extends AccountMeta = AccountMeta
|
||||
>({
|
||||
name,
|
||||
peersToLoadFrom,
|
||||
migration,
|
||||
initialAgentSecret = newRandomAgentSecret(),
|
||||
}: {
|
||||
name: string;
|
||||
peersToLoadFrom?: Peer[];
|
||||
migration?: AccountMigration<P, R, Meta>;
|
||||
initialAgentSecret?: AgentSecret;
|
||||
}): {
|
||||
}): Promise<{
|
||||
node: LocalNode;
|
||||
accountID: AccountID;
|
||||
accountSecret: AgentSecret;
|
||||
sessionID: SessionID;
|
||||
} {
|
||||
}> {
|
||||
const throwawayAgent = newRandomAgentSecret();
|
||||
const setupNode = new LocalNode(
|
||||
new AnonymousControlledAccount(throwawayAgent),
|
||||
new ControlledAgent(throwawayAgent),
|
||||
newRandomSessionID(getAgentID(throwawayAgent))
|
||||
);
|
||||
|
||||
@@ -107,8 +109,14 @@ export class LocalNode {
|
||||
"After creating account"
|
||||
);
|
||||
|
||||
if (peersToLoadFrom) {
|
||||
for (const peer of peersToLoadFrom) {
|
||||
nodeWithAccount.syncManager.addPeer(peer);
|
||||
}
|
||||
}
|
||||
|
||||
if (migration) {
|
||||
migration(accountOnNodeWithAccount, profile as P);
|
||||
await migration(accountOnNodeWithAccount, profile as P, nodeWithAccount);
|
||||
}
|
||||
|
||||
nodeWithAccount.account = new ControlledAccount(
|
||||
@@ -156,7 +164,7 @@ export class LocalNode {
|
||||
migration?: AccountMigration<P, R, Meta>;
|
||||
}): Promise<LocalNode> {
|
||||
const loadingNode = new LocalNode(
|
||||
new AnonymousControlledAccount(accountSecret),
|
||||
new ControlledAgent(accountSecret),
|
||||
newRandomSessionID(accountID)
|
||||
);
|
||||
|
||||
@@ -194,9 +202,10 @@ export class LocalNode {
|
||||
const profile = await node.load(profileID);
|
||||
|
||||
if (migration) {
|
||||
migration(
|
||||
await migration(
|
||||
controlledAccount as ControlledAccount<P, R, Meta>,
|
||||
profile as P
|
||||
profile as P,
|
||||
node
|
||||
);
|
||||
node.account = new ControlledAccount(
|
||||
controlledAccount.core,
|
||||
@@ -370,14 +379,14 @@ export class LocalNode {
|
||||
const groupAsInvite = expectGroup(
|
||||
group.core
|
||||
.testWithDifferentAccount(
|
||||
new AnonymousControlledAccount(inviteAgentSecret),
|
||||
new ControlledAgent(inviteAgentSecret),
|
||||
newRandomSessionID(inviteAgentID)
|
||||
)
|
||||
.getCurrentContent()
|
||||
);
|
||||
|
||||
groupAsInvite.addMemberInternal(
|
||||
this.account.id,
|
||||
this.account,
|
||||
inviteRole === "adminInvite"
|
||||
? "admin"
|
||||
: inviteRole === "writerInvite"
|
||||
@@ -439,7 +448,7 @@ export class LocalNode {
|
||||
let account = expectGroup(
|
||||
this.createCoValue(accountHeaderForInitialAgentSecret(agentSecret))
|
||||
.testWithDifferentAccount(
|
||||
new AnonymousControlledAccount(agentSecret),
|
||||
new ControlledAgent(agentSecret),
|
||||
newRandomSessionID(accountAgentID)
|
||||
)
|
||||
.getCurrentContent()
|
||||
@@ -518,7 +527,7 @@ export class LocalNode {
|
||||
);
|
||||
}
|
||||
|
||||
return new Account(coValue).getCurrentAgentID();
|
||||
return new Account(coValue).currentAgentID();
|
||||
}
|
||||
|
||||
async resolveAccountAgentAsync(
|
||||
@@ -553,7 +562,7 @@ export class LocalNode {
|
||||
);
|
||||
}
|
||||
|
||||
return new Account(coValue).getCurrentAgentID();
|
||||
return new Account(coValue).currentAgentID();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -596,7 +605,7 @@ export class LocalNode {
|
||||
|
||||
/** @internal */
|
||||
testWithDifferentAccount(
|
||||
account: GeneralizedControlledAccount,
|
||||
account: ControlledAccountOrAgent,
|
||||
currentSessionID: SessionID
|
||||
): LocalNode {
|
||||
const newNode = new LocalNode(account, currentSessionID);
|
||||
|
||||
@@ -249,7 +249,7 @@ export function determineValidTransactions(
|
||||
const effectiveTransactor =
|
||||
transactor === groupContent.id &&
|
||||
groupAtTime instanceof Account
|
||||
? groupAtTime.getCurrentAgentID()
|
||||
? groupAtTime.currentAgentID()
|
||||
: transactor;
|
||||
const transactorRoleAtTxTime =
|
||||
groupAtTime.get(effectiveTransactor) ||
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -9,7 +9,7 @@ beforeEach(async () => {
|
||||
|
||||
test("Can create a node while creating a new account with profile", async () => {
|
||||
const { node, accountID, accountSecret, sessionID } =
|
||||
LocalNode.withNewlyCreatedAccount({ name: "Hermes Puggington" });
|
||||
await LocalNode.withNewlyCreatedAccount({ name: "Hermes Puggington" });
|
||||
|
||||
expect(node).not.toBeNull();
|
||||
expect(accountID).not.toBeNull();
|
||||
@@ -22,7 +22,7 @@ test("Can create a node while creating a new account with profile", async () =>
|
||||
});
|
||||
|
||||
test("A node with an account can create groups and and objects within them", async () => {
|
||||
const { node, accountID } = LocalNode.withNewlyCreatedAccount({
|
||||
const { node, accountID } = await LocalNode.withNewlyCreatedAccount({
|
||||
name: "Hermes Puggington",
|
||||
});
|
||||
|
||||
@@ -42,7 +42,7 @@ test("A node with an account can create groups and and objects within them", asy
|
||||
|
||||
test("Can create account with one node, and then load it on another", async () => {
|
||||
const { node, accountID, accountSecret } =
|
||||
LocalNode.withNewlyCreatedAccount({ name: "Hermes Puggington" });
|
||||
await LocalNode.withNewlyCreatedAccount({ name: "Hermes Puggington" });
|
||||
|
||||
const group = await node.createGroup();
|
||||
expect(group).not.toBeNull();
|
||||
@@ -69,6 +69,7 @@ test("Can create account with one node, and then load it on another", async () =
|
||||
});
|
||||
|
||||
const map2 = await node2.load(map.id);
|
||||
if (map2 === "unavailable") throw new Error("Map unavailable");
|
||||
|
||||
expect(map2.get("foo")).toEqual("bar");
|
||||
});
|
||||
|
||||
@@ -17,7 +17,7 @@ import {
|
||||
groupWithTwoAdmins,
|
||||
groupWithTwoAdminsHighLevel,
|
||||
} from "./testUtils.js";
|
||||
import { AnonymousControlledAccount, cojsonReady } from "../index.js";
|
||||
import { ControlledAgent, cojsonReady } from "../index.js";
|
||||
import { expectGroup } from "../typeUtils/expectGroup.js";
|
||||
|
||||
beforeEach(async () => {
|
||||
@@ -70,7 +70,7 @@ test("Added adming can add a third admin to a group (high level)", () => {
|
||||
|
||||
const thirdAdmin = groupAsOtherAdmin.core.node.createAccount("thirdAdmin");
|
||||
|
||||
groupAsOtherAdmin = groupAsOtherAdmin.addMember(thirdAdmin.id, "admin");
|
||||
groupAsOtherAdmin = groupAsOtherAdmin.addMember(thirdAdmin, "admin");
|
||||
|
||||
expect(groupAsOtherAdmin.get(thirdAdmin.id)).toEqual("admin");
|
||||
});
|
||||
@@ -166,7 +166,7 @@ test("Admins an add writers to a group, who can't add admins, writers, or reader
|
||||
|
||||
const writer = node.createAccount("writer");
|
||||
|
||||
group = group.addMember(writer.id, "writer");
|
||||
group = group.addMember(writer, "writer");
|
||||
expect(group.get(writer.id)).toEqual("writer");
|
||||
|
||||
const groupAsWriter = expectGroup(
|
||||
@@ -179,13 +179,13 @@ test("Admins an add writers to a group, who can't add admins, writers, or reader
|
||||
|
||||
const otherAgent = groupAsWriter.core.node.createAccount("otherAgent");
|
||||
|
||||
expect(() => groupAsWriter.addMember(otherAgent.id, "admin")).toThrow(
|
||||
expect(() => groupAsWriter.addMember(otherAgent, "admin")).toThrow(
|
||||
"Failed to set role"
|
||||
);
|
||||
expect(() => groupAsWriter.addMember(otherAgent.id, "writer")).toThrow(
|
||||
expect(() => groupAsWriter.addMember(otherAgent, "writer")).toThrow(
|
||||
"Failed to set role"
|
||||
);
|
||||
expect(() => groupAsWriter.addMember(otherAgent.id, "reader")).toThrow(
|
||||
expect(() => groupAsWriter.addMember(otherAgent, "reader")).toThrow(
|
||||
"Failed to set role"
|
||||
);
|
||||
|
||||
@@ -235,7 +235,7 @@ test("Admins can add readers to a group, who can't add admins, writers, or reade
|
||||
|
||||
const reader = node.createAccount("reader");
|
||||
|
||||
group = group.addMember(reader.id, "reader");
|
||||
group = group.addMember(reader, "reader");
|
||||
expect(group.get(reader.id)).toEqual("reader");
|
||||
|
||||
const groupAsReader = expectGroup(
|
||||
@@ -248,13 +248,13 @@ test("Admins can add readers to a group, who can't add admins, writers, or reade
|
||||
|
||||
const otherAgent = groupAsReader.core.node.createAccount("otherAgent");
|
||||
|
||||
expect(() => groupAsReader.addMember(otherAgent.id, "admin")).toThrow(
|
||||
expect(() => groupAsReader.addMember(otherAgent, "admin")).toThrow(
|
||||
"Failed to set role"
|
||||
);
|
||||
expect(() => groupAsReader.addMember(otherAgent.id, "writer")).toThrow(
|
||||
expect(() => groupAsReader.addMember(otherAgent, "writer")).toThrow(
|
||||
"Failed to set role"
|
||||
);
|
||||
expect(() => groupAsReader.addMember(otherAgent.id, "reader")).toThrow(
|
||||
expect(() => groupAsReader.addMember(otherAgent, "reader")).toThrow(
|
||||
"Failed to set role"
|
||||
);
|
||||
|
||||
@@ -337,7 +337,7 @@ test("Writers can write to an object that is owned by their group (high level)",
|
||||
|
||||
const writer = node.createAccount("writer");
|
||||
|
||||
group.addMember(writer.id, "writer");
|
||||
group.addMember(writer, "writer");
|
||||
|
||||
const childObject = group.createMap();
|
||||
|
||||
@@ -396,7 +396,7 @@ test("Readers can not write to an object that is owned by their group (high leve
|
||||
|
||||
const reader = node.createAccount("reader");
|
||||
|
||||
group.addMember(reader.id, "reader");
|
||||
group.addMember(reader, "reader");
|
||||
|
||||
const childObject = group.createMap();
|
||||
|
||||
@@ -548,7 +548,7 @@ test("Admins can set group read key and then writers can use it to create and re
|
||||
|
||||
const writer = node.createAccount("writer");
|
||||
|
||||
group.addMember(writer.id, "writer");
|
||||
group.addMember(writer, "writer");
|
||||
|
||||
const childObject = group.createMap();
|
||||
|
||||
@@ -637,7 +637,7 @@ test("Admins can set group read key and then use it to create private transactio
|
||||
|
||||
const reader = node.createAccount("reader");
|
||||
|
||||
group.addMember(reader.id, "reader");
|
||||
group.addMember(reader, "reader");
|
||||
|
||||
let childObject = group.createMap();
|
||||
|
||||
@@ -757,7 +757,7 @@ test("Admins can set group read key and then use it to create private transactio
|
||||
|
||||
const reader2 = node.createAccount("reader2");
|
||||
|
||||
group.addMember(reader1.id, "reader");
|
||||
group.addMember(reader1, "reader");
|
||||
|
||||
let childObject = group.createMap();
|
||||
|
||||
@@ -774,7 +774,7 @@ test("Admins can set group read key and then use it to create private transactio
|
||||
|
||||
expect(childContentAsReader1.get("foo")).toEqual("bar");
|
||||
|
||||
group.addMember(reader2.id, "reader");
|
||||
group.addMember(reader2, "reader");
|
||||
|
||||
const childContentAsReader2 = expectMap(
|
||||
childObject.core
|
||||
@@ -1013,7 +1013,7 @@ test("Admins can set group read key, make a private transaction in an owned obje
|
||||
|
||||
const reader = node.createAccount("reader");
|
||||
|
||||
group.addMember(reader.id, "reader");
|
||||
group.addMember(reader, "reader");
|
||||
|
||||
childObject = childObject.edit((editable) => {
|
||||
editable.set("foo2", "bar2", "private");
|
||||
@@ -1210,8 +1210,8 @@ test("Admins can set group read rey, make a private transaction in an owned obje
|
||||
|
||||
const reader2 = node.createAccount("reader2");
|
||||
|
||||
group.addMember(reader.id, "reader");
|
||||
group.addMember(reader2.id, "reader");
|
||||
group.addMember(reader, "reader");
|
||||
group.addMember(reader2, "reader");
|
||||
|
||||
childObject = childObject.edit((editable) => {
|
||||
editable.set("foo2", "bar2", "private");
|
||||
@@ -1314,7 +1314,7 @@ test("Admins can create an adminInvite, which can add an admin", () => {
|
||||
const groupAsInvite = expectGroup(
|
||||
groupCore
|
||||
.testWithDifferentAccount(
|
||||
new AnonymousControlledAccount(inviteSecret),
|
||||
new ControlledAgent(inviteSecret),
|
||||
newRandomSessionID(inviteID)
|
||||
)
|
||||
.getCurrentContent()
|
||||
@@ -1363,7 +1363,7 @@ test("Admins can create an adminInvite, which can add an admin (high-level)", as
|
||||
const invitedAdminID = getAgentID(invitedAdminSecret);
|
||||
|
||||
const nodeAsInvitedAdmin = node.testWithDifferentAccount(
|
||||
new AnonymousControlledAccount(invitedAdminSecret),
|
||||
new ControlledAgent(invitedAdminSecret),
|
||||
newRandomSessionID(invitedAdminID)
|
||||
);
|
||||
|
||||
@@ -1433,7 +1433,7 @@ test("Admins can create a writerInvite, which can add a writer", () => {
|
||||
const groupAsInvite = expectGroup(
|
||||
groupCore
|
||||
.testWithDifferentAccount(
|
||||
new AnonymousControlledAccount(inviteSecret),
|
||||
new ControlledAgent(inviteSecret),
|
||||
newRandomSessionID(inviteID)
|
||||
)
|
||||
.getCurrentContent()
|
||||
@@ -1482,7 +1482,7 @@ test("Admins can create a writerInvite, which can add a writer (high-level)", as
|
||||
const invitedWriterID = getAgentID(invitedWriterSecret);
|
||||
|
||||
const nodeAsInvitedWriter = node.testWithDifferentAccount(
|
||||
new AnonymousControlledAccount(invitedWriterSecret),
|
||||
new ControlledAgent(invitedWriterSecret),
|
||||
newRandomSessionID(invitedWriterID)
|
||||
);
|
||||
|
||||
@@ -1542,7 +1542,7 @@ test("Admins can create a readerInvite, which can add a reader", () => {
|
||||
const groupAsInvite = expectGroup(
|
||||
groupCore
|
||||
.testWithDifferentAccount(
|
||||
new AnonymousControlledAccount(inviteSecret),
|
||||
new ControlledAgent(inviteSecret),
|
||||
newRandomSessionID(inviteID)
|
||||
)
|
||||
.getCurrentContent()
|
||||
@@ -1591,7 +1591,7 @@ test("Admins can create a readerInvite, which can add a reader (high-level)", as
|
||||
const invitedReaderID = getAgentID(invitedReaderSecret);
|
||||
|
||||
const nodeAsInvitedReader = node.testWithDifferentAccount(
|
||||
new AnonymousControlledAccount(invitedReaderSecret),
|
||||
new ControlledAgent(invitedReaderSecret),
|
||||
newRandomSessionID(invitedReaderID)
|
||||
);
|
||||
|
||||
@@ -1651,7 +1651,7 @@ test("WriterInvites can not invite admins", () => {
|
||||
const groupAsInvite = expectGroup(
|
||||
groupCore
|
||||
.testWithDifferentAccount(
|
||||
new AnonymousControlledAccount(inviteSecret),
|
||||
new ControlledAgent(inviteSecret),
|
||||
newRandomSessionID(inviteID)
|
||||
)
|
||||
.getCurrentContent()
|
||||
@@ -1711,7 +1711,7 @@ test("ReaderInvites can not invite admins", () => {
|
||||
const groupAsInvite = expectGroup(
|
||||
groupCore
|
||||
.testWithDifferentAccount(
|
||||
new AnonymousControlledAccount(inviteSecret),
|
||||
new ControlledAgent(inviteSecret),
|
||||
newRandomSessionID(inviteID)
|
||||
)
|
||||
.getCurrentContent()
|
||||
@@ -1771,7 +1771,7 @@ test("ReaderInvites can not invite writers", () => {
|
||||
const groupAsInvite = expectGroup(
|
||||
groupCore
|
||||
.testWithDifferentAccount(
|
||||
new AnonymousControlledAccount(inviteSecret),
|
||||
new ControlledAgent(inviteSecret),
|
||||
newRandomSessionID(inviteID)
|
||||
)
|
||||
.getCurrentContent()
|
||||
@@ -1812,7 +1812,7 @@ test("Can give read permission to 'everyone'", () => {
|
||||
expect(editable.get("foo")).toEqual("bar");
|
||||
});
|
||||
|
||||
const newAccount = new AnonymousControlledAccount(newRandomAgentSecret());
|
||||
const newAccount = new ControlledAgent(newRandomAgentSecret());
|
||||
|
||||
const childContent2 = expectMap(
|
||||
childObject
|
||||
@@ -1840,12 +1840,12 @@ test("Can give read permissions to 'everyone' (high-level)", async () => {
|
||||
expect(editable.get("foo")).toEqual("bar");
|
||||
});
|
||||
|
||||
const newAccount = new AnonymousControlledAccount(newRandomAgentSecret());
|
||||
const newAccount = new ControlledAgent(newRandomAgentSecret());
|
||||
|
||||
const childContent2 = expectMap(
|
||||
childObject.core
|
||||
.testWithDifferentAccount(
|
||||
new AnonymousControlledAccount(newRandomAgentSecret()),
|
||||
new ControlledAgent(newRandomAgentSecret()),
|
||||
newRandomSessionID(newAccount.currentAgentID())
|
||||
)
|
||||
.getCurrentContent()
|
||||
@@ -1880,7 +1880,7 @@ test("Can give write permission to 'everyone'", () => {
|
||||
expect(editable.get("foo")).toEqual("bar");
|
||||
});
|
||||
|
||||
const newAccount = new AnonymousControlledAccount(newRandomAgentSecret());
|
||||
const newAccount = new ControlledAgent(newRandomAgentSecret());
|
||||
|
||||
const childContent2 = expectMap(
|
||||
childObject
|
||||
@@ -1913,7 +1913,7 @@ test("Can give write permissions to 'everyone' (high-level)", async () => {
|
||||
expect(editable.get("foo")).toEqual("bar");
|
||||
});
|
||||
|
||||
const newAccount = new AnonymousControlledAccount(newRandomAgentSecret());
|
||||
const newAccount = new ControlledAgent(newRandomAgentSecret());
|
||||
|
||||
const childContent2 = expectMap(
|
||||
childObject.core
|
||||
|
||||
@@ -2,17 +2,17 @@ import { AgentSecret, createdNowUnique, getAgentID, newRandomAgentSecret } from
|
||||
import { newRandomSessionID } from "../coValueCore.js";
|
||||
import { LocalNode } from "../localNode.js";
|
||||
import { expectGroup } from "../typeUtils/expectGroup.js";
|
||||
import { AnonymousControlledAccount } from "../coValues/account.js";
|
||||
import { ControlledAgent } from "../coValues/account.js";
|
||||
import { SessionID } from "../ids.js";
|
||||
// @ts-ignore
|
||||
import { expect } from "bun:test";
|
||||
|
||||
export function randomAnonymousAccountAndSessionID(): [AnonymousControlledAccount, SessionID] {
|
||||
export function randomAnonymousAccountAndSessionID(): [ControlledAgent, SessionID] {
|
||||
const agentSecret = newRandomAgentSecret();
|
||||
|
||||
const sessionID = newRandomSessionID(getAgentID(agentSecret));
|
||||
|
||||
return [new AnonymousControlledAccount(agentSecret), sessionID];
|
||||
return [new ControlledAgent(agentSecret), sessionID];
|
||||
}
|
||||
|
||||
export function newGroup() {
|
||||
@@ -73,7 +73,7 @@ export function groupWithTwoAdminsHighLevel() {
|
||||
|
||||
const otherAdmin = node.createAccount("otherAdmin");
|
||||
|
||||
group = group.addMember(otherAdmin.id, "admin");
|
||||
group = group.addMember(otherAdmin, "admin");
|
||||
|
||||
return { admin, node, group, otherAdmin };
|
||||
}
|
||||
|
||||
@@ -1,5 +1,16 @@
|
||||
# jazz-autosub
|
||||
|
||||
## 0.6.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- Make addMember and removeMember take loaded Accounts instead of just IDs
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- cojson@0.6.0
|
||||
|
||||
## 0.5.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
@@ -5,9 +5,9 @@
|
||||
"types": "src/index.ts",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"version": "0.5.0",
|
||||
"version": "0.6.0",
|
||||
"dependencies": {
|
||||
"cojson": "^0.5.0"
|
||||
"cojson": "^0.6.0"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "jest",
|
||||
|
||||
@@ -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, [], "");
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import {
|
||||
AccountID,
|
||||
Account,
|
||||
BinaryCoStream,
|
||||
CoID,
|
||||
CoList,
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
Role,
|
||||
} from "cojson";
|
||||
import { AutoSubContext, ValueOrResolvedRef } from "../autoSub.js";
|
||||
import { ControlledAccountOrAgent } from "cojson/src/coValues/account.js";
|
||||
|
||||
export class ResolvedGroupMeta<G extends Group> {
|
||||
coValue!: G;
|
||||
@@ -55,12 +56,12 @@ export class ResolvedGroup<G extends Group = Group> {
|
||||
);
|
||||
}
|
||||
|
||||
addMember(accountID: AccountID | Everyone, role: Role): G {
|
||||
addMember(accountID: Account | ControlledAccountOrAgent | Everyone, role: Role): G {
|
||||
return this.meta.group.addMember(accountID, role);
|
||||
}
|
||||
|
||||
removeMember(accountID: AccountID): G {
|
||||
return this.meta.group.removeMember(accountID);
|
||||
removeMember(account: Account | ControlledAccountOrAgent | Everyone): G {
|
||||
return this.meta.group.removeMember(account);
|
||||
}
|
||||
|
||||
createInvite(role: "reader" | "writer" | "admin"): InviteSecret {
|
||||
|
||||
@@ -1,5 +1,24 @@
|
||||
# jazz-browser-auth-local
|
||||
|
||||
## 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
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"name": "jazz-browser-auth-local",
|
||||
"version": "0.4.16",
|
||||
"version": "0.5.0",
|
||||
"main": "dist/index.js",
|
||||
"types": "src/index.ts",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"jazz-browser": "^0.5.0",
|
||||
"jazz-browser": "^0.6.0",
|
||||
"typescript": "^5.1.6"
|
||||
},
|
||||
"scripts": {
|
||||
|
||||
@@ -110,7 +110,7 @@ async function signUp(
|
||||
const secretSeed = cojsonInternals.newRandomSecretSeed();
|
||||
|
||||
const { node, accountID, accountSecret } =
|
||||
LocalNode.withNewlyCreatedAccount({
|
||||
await LocalNode.withNewlyCreatedAccount({
|
||||
name: username,
|
||||
initialAgentSecret: agentSecretFromSecretSeed(secretSeed),
|
||||
migration,
|
||||
|
||||
21
packages/jazz-browser-auth-passphrase/.eslintrc.cjs
Normal file
21
packages/jazz-browser-auth-passphrase/.eslintrc.cjs
Normal 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-auth-passphrase/.gitignore
vendored
Normal file
171
packages/jazz-browser-auth-passphrase/.gitignore
vendored
Normal 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
|
||||
2
packages/jazz-browser-auth-passphrase/.npmignore
Normal file
2
packages/jazz-browser-auth-passphrase/.npmignore
Normal file
@@ -0,0 +1,2 @@
|
||||
coverage
|
||||
node_modules
|
||||
33
packages/jazz-browser-auth-passphrase/CHANGELOG.md
Normal file
33
packages/jazz-browser-auth-passphrase/CHANGELOG.md
Normal 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
|
||||
18
packages/jazz-browser-auth-passphrase/package.json
Normal file
18
packages/jazz-browser-auth-passphrase/package.json
Normal 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"
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user