Compare commits

..

57 Commits

Author SHA1 Message Date
Anselm Eickhoff
61b346a948 Merge pull request #691 from gardencmp/changeset-release/main
Version Packages
2024-11-05 09:38:34 +00:00
github-actions[bot]
c2dfe6a0dd Version Packages 2024-11-05 09:35:46 +00:00
Anselm Eickhoff
81e8910519 Merge pull request #676 from gardencmp/benjamin-jazz-452
Store clerk authentication locally
2024-11-05 09:34:29 +00:00
Benjamin S. Leveritt
cce679b031 Split changesets 2024-11-05 09:32:29 +00:00
Anselm Eickhoff
c2dbf45f26 Merge pull request #689 from gardencmp/changeset-release/main 2024-11-05 09:13:32 +00:00
github-actions[bot]
04d3a8c677 Version Packages 2024-11-05 09:05:45 +00:00
Anselm Eickhoff
91019f3f85 Merge pull request #687 from gardencmp/optimize-account-load-2
perf: optimize _loadedAs in CoList and CoValueBase
2024-11-05 09:04:20 +00:00
Benjamin S. Leveritt
06278c3d97 Update saveCredentials mock 2024-11-05 06:53:32 +00:00
Guido D'Orsi
36273b3114 chore: changeset 2024-11-04 23:09:34 +01:00
Guido D'Orsi
51a0a9f23f perf: optimize _loadedAs in CoList and CoValueBase 2024-11-04 23:07:51 +01:00
Anselm Eickhoff
fc24a65264 Merge pull request #685 from gardencmp/changeset-release/main
Version Packages
2024-11-04 20:10:10 +00:00
github-actions[bot]
fa0b9003dc Version Packages 2024-11-04 20:05:27 +00:00
Anselm Eickhoff
8a0da655e8 Merge pull request #686 from gardencmp/optimize-account-loadas
perf: cache the account refs resolution
2024-11-04 20:04:15 +00:00
Guido D'Orsi
fff11daca7 test: add tests for cache 2024-11-04 20:41:36 +01:00
Guido D'Orsi
fd011d76f4 chore: changeset 2024-11-04 20:21:32 +01:00
Guido D'Orsi
0681a8301e perf: cache the account refs resolution 2024-11-04 20:09:34 +01:00
Benjamin S. Leveritt
221c58f1f7 Add changeset 2024-11-04 18:51:03 +00:00
Benjamin S. Leveritt
ebca75bf70 Add check for existance and await 2024-11-04 18:43:01 +00:00
Benjamin S. Leveritt
b5e516f974 Add tests
Particularly covering log out and checking for storing creds
2024-11-04 17:37:47 +00:00
Benjamin S. Leveritt
8fc5a49ca8 Refactor setItem into fn 2024-11-04 17:37:40 +00:00
Benjamin S. Leveritt
00054aa9a5 Export Credential type from jazz-tools 2024-11-04 17:11:05 +00:00
Benjamin S. Leveritt
6f3c98f704 Make saveCredentials optional 2024-11-04 17:00:28 +00:00
Anselm Eickhoff
8823957c6e Merge pull request #677 from gardencmp/feature/websocker-unique-name
feat(browser): refactor the WebSocket reconnection to assign the peer as id
2024-11-04 16:58:17 +00:00
Benjamin S. Leveritt
47ec0cd8fe Rename type 2024-11-04 16:53:42 +00:00
Benjamin S. Leveritt
685031c39c Remove check for user before passing to BrowserClerkAuth 2024-11-04 16:53:22 +00:00
Benjamin S. Leveritt
e4fd89dbbb Add saveCredentials to existing Clerk accounts authentications 2024-11-04 16:32:37 +00:00
Guido D'Orsi
e0dd0065fb chore: changeset 2024-11-04 15:06:33 +01:00
Guido D'Orsi
98d7d402eb feat(browser): refactor the WebSocket reconnection to assign the peer as id 2024-11-04 14:28:11 +01:00
Benjamin S. Leveritt
57deec451a Check for local credentials before Clerk checks 2024-11-04 10:47:42 +00:00
Benjamin S. Leveritt
bc8ea23e6d Store clerk authentication locally
Introduce LocallyStoredCredentials for storing and validating local storage values
2024-11-04 09:39:46 +00:00
Anselm
d2ad2db930 Fix anselm's bluesky 2024-11-01 15:17:55 +00:00
Anselm Eickhoff
44497fd058 Merge pull request #668 from gardencmp/changeset-release/main
Version Packages
2024-11-01 10:20:07 +00:00
github-actions[bot]
a18c187818 Version Packages 2024-11-01 10:04:27 +00:00
Anselm Eickhoff
f49949cc25 Merge pull request #653 from gardencmp/feature/react-provider-dev-mode
fix: ensure that the react provider doesn't run twice on dev
2024-11-01 10:03:09 +00:00
Anselm
3cc6aee7e6 Add changeset 2024-11-01 09:54:11 +00:00
Anselm Eickhoff
c80763100e Merge pull request #651 from gardencmp/gudorsi-jazz-248-storage-peer
feat: introduce the storage peer role
2024-11-01 09:50:14 +00:00
Anselm
6ed75ebb35 Add changeset 2024-11-01 09:49:30 +00:00
Anselm Eickhoff
30d7734124 Merge pull request #663 from gardencmp/trishalim-jazz-443
Close mobile nav after clicking a link
2024-11-01 09:41:10 +00:00
Anselm Eickhoff
bca8b44189 Merge pull request #664 from gardencmp/trishalim-jazz-445
Docs: create separate pages for nextjs and react native
2024-11-01 09:31:11 +00:00
Anselm Eickhoff
36a069c90f Merge pull request #666 from gardencmp/feature/jazz-run-integration-test
chore(jazz-run): extract commands business logic and add tests
2024-11-01 09:29:12 +00:00
Guido D'Orsi
757b37e8ed feat: introduce the storage peer role 2024-10-31 23:18:47 +01:00
Guido D'Orsi
46b1163058 chore(jazz-run): split commands and core logic and add tests 2024-10-31 23:09:42 +01:00
Trisha Lim
8c261b0409 Create separate pages for nextjs and react native 2024-10-31 19:36:19 +00:00
Trisha Lim
fdacde57dd Lint fix 2024-10-31 19:10:38 +00:00
Trisha Lim
817dd7dde1 Close mobile nav after clicking a link 2024-10-31 19:07:11 +00:00
Anselm Eickhoff
6279dd1467 Merge pull request #662 from gardencmp/trishalim-jazz-446
Add yellow dot as legend for docs that are WIP
2024-10-31 17:30:18 +00:00
Trisha Lim
abd4b94392 Add legend in side nav to indicate WIP docs 2024-10-31 17:24:03 +00:00
Trisha Lim
68c6ee77c0 Remove % progress in docs, add '/coming-soon' page 2024-10-31 17:18:33 +00:00
Anselm Eickhoff
d2b2812428 Merge pull request #658 from gardencmp/fix/sticky-nav
Fix nav is not sticking to top
2024-10-31 16:29:52 +00:00
Anselm Eickhoff
fe214cc3c2 Merge pull request #657 from gardencmp/fix/h4-style
Fix styling for h4 in prose
2024-10-31 16:29:39 +00:00
Anselm Eickhoff
2cca9506ad Merge pull request #656 from gardencmp/react-native-docs
React native docs
2024-10-31 16:29:06 +00:00
Trisha Lim
17e69aff8f Add space between main content and footer 2024-10-31 14:22:33 +00:00
Trisha Lim
c45331e645 Fix nav is not sticking to top 2024-10-31 14:21:13 +00:00
Trisha Lim
4337001526 Fix styling for h4 in prose 2024-10-31 13:52:23 +00:00
pax-k
9eef1ec031 fix(docs): layout centering 2024-10-31 15:33:54 +02:00
pax-k
9d31bbc3aa chore(docs): added react native docs 2024-10-31 15:20:01 +02:00
Guido D'Orsi
911add3dea fix: ensure that the react provider doesn't run twice on dev 2024-10-31 11:19:48 +01:00
116 changed files with 2583 additions and 856 deletions

View File

@@ -1,5 +1,39 @@
# @jazz-e2e/binarycostream
## 0.0.93
### Patch Changes
- Updated dependencies [cce679b]
- jazz-tools@0.8.15
- jazz-react@0.8.15
## 0.0.92
### Patch Changes
- Updated dependencies [36273b3]
- jazz-tools@0.8.14
- jazz-react@0.8.14
## 0.0.91
### Patch Changes
- Updated dependencies [fd011d7]
- jazz-tools@0.8.13
- jazz-react@0.8.13
## 0.0.90
### Patch Changes
- Updated dependencies [6ed75eb]
- Updated dependencies [3cc6aee]
- cojson@0.8.12
- jazz-react@0.8.12
- jazz-tools@0.8.12
## 0.0.89
### Patch Changes

View File

@@ -1,7 +1,7 @@
{
"name": "@jazz-e2e/binarycostream",
"private": true,
"version": "0.0.89",
"version": "0.0.93",
"type": "module",
"scripts": {
"dev": "vite",
@@ -14,11 +14,11 @@
"*.{js,jsx,mdx,json}": "prettier --write"
},
"dependencies": {
"cojson": "workspace:0.8.11",
"cojson": "workspace:0.8.12",
"hash-slash": "workspace:0.2.1",
"is-ci": "^3.0.1",
"jazz-react": "workspace:0.8.11",
"jazz-tools": "workspace:0.8.11",
"jazz-react": "workspace:0.8.15",
"jazz-tools": "workspace:0.8.15",
"react": "^18.2.0",
"react-dom": "^18.2.0"
},

View File

@@ -27,10 +27,11 @@ export default defineConfig({
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
use: {
/* Base URL to use in actions like `await page.goto('/')`. */
baseURL: isCI ? "http://localhost:4173/" : "http://localhost:5173",
baseURL: "http://localhost:5173/",
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
trace: "on-first-retry",
permissions: ["clipboard-read", "clipboard-write"],
},
/* Configure projects for major browsers */
@@ -42,8 +43,11 @@ export default defineConfig({
],
/* Run your local dev server before starting the tests */
webServer: isCI ? {
command: "pnpm preview",
url: "http://localhost:4173/",
} : undefined,
webServer: [
{
command: "pnpm preview --port 5173",
url: "http://localhost:5173/",
reuseExistingServer: !isCI,
},
],
});

View File

@@ -1,5 +1,39 @@
# @jazz-e2e/covalues
## 0.0.92
### Patch Changes
- Updated dependencies [cce679b]
- jazz-tools@0.8.15
- jazz-react@0.8.15
## 0.0.91
### Patch Changes
- Updated dependencies [36273b3]
- jazz-tools@0.8.14
- jazz-react@0.8.14
## 0.0.90
### Patch Changes
- Updated dependencies [fd011d7]
- jazz-tools@0.8.13
- jazz-react@0.8.13
## 0.0.89
### Patch Changes
- Updated dependencies [6ed75eb]
- Updated dependencies [3cc6aee]
- cojson@0.8.12
- jazz-react@0.8.12
- jazz-tools@0.8.12
## 0.0.88
### Patch Changes

View File

@@ -1,7 +1,7 @@
{
"name": "@jazz-e2e/covalues",
"private": true,
"version": "0.0.88",
"version": "0.0.92",
"type": "module",
"scripts": {
"dev": "vite",

View File

@@ -1,5 +1,41 @@
# jazz-example-book-shelf
## 0.1.8
### Patch Changes
- Updated dependencies [cce679b]
- jazz-tools@0.8.15
- jazz-browser-media-images@0.8.15
- jazz-react@0.8.15
## 0.1.7
### Patch Changes
- Updated dependencies [36273b3]
- jazz-tools@0.8.14
- jazz-browser-media-images@0.8.14
- jazz-react@0.8.14
## 0.1.6
### Patch Changes
- Updated dependencies [fd011d7]
- jazz-tools@0.8.13
- jazz-browser-media-images@0.8.13
- jazz-react@0.8.13
## 0.1.5
### Patch Changes
- Updated dependencies [3cc6aee]
- jazz-react@0.8.12
- jazz-tools@0.8.12
- jazz-browser-media-images@0.8.12
## 0.1.4
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "jazz-example-book-shelf",
"version": "0.1.4",
"version": "0.1.8",
"private": true,
"scripts": {
"dev": "next dev",
@@ -11,9 +11,9 @@
},
"dependencies": {
"clsx": "^2.0.0",
"jazz-browser-media-images": "workspace:0.8.11",
"jazz-react": "workspace:0.8.11",
"jazz-tools": "workspace:0.8.11",
"jazz-browser-media-images": "workspace:0.8.15",
"jazz-react": "workspace:0.8.15",
"jazz-tools": "workspace:0.8.15",
"next": "14.2.5",
"react": "^18.2.0",
"react-dom": "^18.2.0"

View File

@@ -1,5 +1,44 @@
# jazz-example-chat
## 0.0.92
### Patch Changes
- Updated dependencies [cce679b]
- Updated dependencies [221c58f]
- jazz-tools@0.8.15
- jazz-react-auth-clerk@0.8.15
- jazz-react@0.8.15
## 0.0.91
### Patch Changes
- Updated dependencies [36273b3]
- jazz-tools@0.8.14
- jazz-react@0.8.14
- jazz-react-auth-clerk@0.8.14
## 0.0.90
### Patch Changes
- Updated dependencies [fd011d7]
- jazz-tools@0.8.13
- jazz-react@0.8.13
- jazz-react-auth-clerk@0.8.13
## 0.0.89
### Patch Changes
- Updated dependencies [6ed75eb]
- Updated dependencies [3cc6aee]
- cojson@0.8.12
- jazz-react@0.8.12
- jazz-react-auth-clerk@0.8.12
- jazz-tools@0.8.12
## 0.0.88
### Patch Changes

View File

@@ -1,7 +1,7 @@
{
"name": "jazz-example-chat-clerk",
"private": true,
"version": "0.0.88",
"version": "0.0.92",
"type": "module",
"scripts": {
"dev": "vite",
@@ -21,11 +21,11 @@
"@radix-ui/react-toast": "^1.1.4",
"class-variance-authority": "^0.7.0",
"clsx": "^2.0.0",
"cojson": "workspace:0.8.11",
"cojson": "workspace:0.8.12",
"hash-slash": "workspace:0.2.1",
"jazz-react": "workspace:0.8.11",
"jazz-react-auth-clerk": "workspace:0.8.11",
"jazz-tools": "workspace:0.8.11",
"jazz-react": "workspace:0.8.15",
"jazz-react-auth-clerk": "workspace:0.8.15",
"jazz-tools": "workspace:0.8.15",
"lucide-react": "^0.274.0",
"qrcode": "^1.5.3",
"react": "^18.2.0",

View File

@@ -1,5 +1,45 @@
# chat-rn-clerk
## 1.0.8
### Patch Changes
- Updated dependencies [cce679b]
- Updated dependencies [221c58f]
- jazz-tools@0.8.15
- jazz-react-auth-clerk@0.8.15
- jazz-react-native@0.8.15
- jazz-react-native-media-images@0.8.11
## 1.0.7
### Patch Changes
- Updated dependencies [36273b3]
- jazz-tools@0.8.14
- jazz-react-auth-clerk@0.8.14
- jazz-react-native@0.8.14
- jazz-react-native-media-images@0.8.10
## 1.0.6
### Patch Changes
- Updated dependencies [fd011d7]
- jazz-tools@0.8.13
- jazz-react-auth-clerk@0.8.13
- jazz-react-native@0.8.13
- jazz-react-native-media-images@0.8.9
## 1.0.5
### Patch Changes
- jazz-react-auth-clerk@0.8.12
- jazz-react-native@0.8.12
- jazz-tools@0.8.12
- jazz-react-native-media-images@0.8.8
## 1.0.4
### Patch Changes

View File

@@ -1,7 +1,7 @@
{
"name": "chat-rn-clerk",
"main": "index.js",
"version": "1.0.4",
"version": "1.0.8",
"scripts": {
"build": "expo export -p ios",
"start": "expo start",

View File

@@ -1,5 +1,36 @@
# chat-rn
## 1.0.10
### Patch Changes
- Updated dependencies [cce679b]
- jazz-tools@0.8.15
- jazz-react-native@0.8.15
## 1.0.9
### Patch Changes
- Updated dependencies [36273b3]
- jazz-tools@0.8.14
- jazz-react-native@0.8.14
## 1.0.8
### Patch Changes
- Updated dependencies [fd011d7]
- jazz-tools@0.8.13
- jazz-react-native@0.8.13
## 1.0.7
### Patch Changes
- jazz-react-native@0.8.12
- jazz-tools@0.8.12
## 1.0.6
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "chat-rn",
"version": "1.0.6",
"version": "1.0.10",
"main": "index.js",
"scripts": {
"build": "expo export -p ios",

View File

@@ -1,5 +1,39 @@
# jazz-example-chat
## 0.0.94
### Patch Changes
- Updated dependencies [cce679b]
- jazz-tools@0.8.15
- jazz-react@0.8.15
## 0.0.93
### Patch Changes
- Updated dependencies [36273b3]
- jazz-tools@0.8.14
- jazz-react@0.8.14
## 0.0.92
### Patch Changes
- Updated dependencies [fd011d7]
- jazz-tools@0.8.13
- jazz-react@0.8.13
## 0.0.91
### Patch Changes
- Updated dependencies [6ed75eb]
- Updated dependencies [3cc6aee]
- cojson@0.8.12
- jazz-react@0.8.12
- jazz-tools@0.8.12
## 0.0.90
### Patch Changes

View File

@@ -1,7 +1,7 @@
{
"name": "jazz-example-chat",
"private": true,
"version": "0.0.90",
"version": "0.0.94",
"type": "module",
"scripts": {
"dev": "vite",
@@ -22,10 +22,10 @@
"@radix-ui/react-toast": "^1.1.4",
"class-variance-authority": "^0.7.0",
"clsx": "^2.0.0",
"cojson": "workspace:0.8.11",
"cojson": "workspace:0.8.12",
"hash-slash": "workspace:0.2.1",
"jazz-react": "workspace:0.8.11",
"jazz-tools": "workspace:0.8.11",
"jazz-react": "workspace:0.8.15",
"jazz-tools": "workspace:0.8.15",
"lucide-react": "^0.274.0",
"qrcode": "^1.5.3",
"react": "^18.2.0",

View File

@@ -27,7 +27,7 @@ export default defineConfig({
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
use: {
/* Base URL to use in actions like `await page.goto('/')`. */
baseURL: isCI ? "http://localhost:4173/" : "http://localhost:5173",
baseURL: "http://localhost:5173/",
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
trace: "on-first-retry",
@@ -43,8 +43,11 @@ export default defineConfig({
],
/* Run your local dev server before starting the tests */
webServer: isCI ? {
command: "pnpm preview",
url: "http://localhost:4173/",
} : undefined,
webServer: [
{
command: "pnpm preview --port 5173",
url: "http://localhost:5173/",
reuseExistingServer: !isCI,
},
],
});

View File

@@ -1,5 +1,20 @@
# jazz-example-inspector
## 0.0.68
### Patch Changes
- Updated dependencies [e0dd006]
- cojson-transport-ws@0.8.13
## 0.0.67
### Patch Changes
- Updated dependencies [6ed75eb]
- cojson@0.8.12
- cojson-transport-ws@0.8.12
## 0.0.66
### Patch Changes

View File

@@ -1,7 +1,7 @@
{
"name": "jazz-inspector",
"private": true,
"version": "0.0.66",
"version": "0.0.68",
"type": "module",
"scripts": {
"dev": "vite",
@@ -15,8 +15,8 @@
"@radix-ui/react-toast": "^1.1.4",
"class-variance-authority": "^0.7.0",
"clsx": "^2.0.0",
"cojson": "workspace:0.8.11",
"cojson-transport-ws": "workspace:0.8.11",
"cojson": "workspace:0.8.12",
"cojson-transport-ws": "workspace:0.8.13",
"hash-slash": "workspace:0.2.1",
"lucide-react": "^0.274.0",
"qrcode": "^1.5.3",

View File

@@ -1,5 +1,37 @@
# jazz-example-musicplayer
## 0.0.14
### Patch Changes
- Updated dependencies [cce679b]
- jazz-tools@0.8.15
- jazz-react@0.8.15
## 0.0.13
### Patch Changes
- Updated dependencies [36273b3]
- jazz-tools@0.8.14
- jazz-react@0.8.14
## 0.0.12
### Patch Changes
- Updated dependencies [fd011d7]
- jazz-tools@0.8.13
- jazz-react@0.8.13
## 0.0.11
### Patch Changes
- Updated dependencies [3cc6aee]
- jazz-react@0.8.12
- jazz-tools@0.8.12
## 0.0.10
### Patch Changes

View File

@@ -1,7 +1,7 @@
{
"name": "jazz-example-music-player",
"private": true,
"version": "0.0.10",
"version": "0.0.14",
"type": "module",
"scripts": {
"dev": "vite",
@@ -22,8 +22,8 @@
"@radix-ui/react-toast": "^1.1.4",
"class-variance-authority": "^0.7.0",
"clsx": "^2.0.0",
"jazz-react": "workspace:0.8.11",
"jazz-tools": "workspace:0.8.11",
"jazz-react": "workspace:0.8.15",
"jazz-tools": "workspace:0.8.15",
"lucide-react": "^0.274.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",

View File

@@ -1,5 +1,37 @@
# jazz-password-manager
## 0.0.13
### Patch Changes
- Updated dependencies [cce679b]
- jazz-tools@0.8.15
- jazz-react@0.8.15
## 0.0.12
### Patch Changes
- Updated dependencies [36273b3]
- jazz-tools@0.8.14
- jazz-react@0.8.14
## 0.0.11
### Patch Changes
- Updated dependencies [fd011d7]
- jazz-tools@0.8.13
- jazz-react@0.8.13
## 0.0.10
### Patch Changes
- Updated dependencies [3cc6aee]
- jazz-react@0.8.12
- jazz-tools@0.8.12
## 0.0.9
### Patch Changes

View File

@@ -1,7 +1,7 @@
{
"name": "jazz-password-manager",
"private": true,
"version": "0.0.9",
"version": "0.0.13",
"type": "module",
"scripts": {
"dev": "vite",
@@ -11,8 +11,8 @@
"clean-install": "rm -rf node_modules pnpm-lock.yaml && pnpm install"
},
"dependencies": {
"jazz-react": "workspace:0.8.11",
"jazz-tools": "workspace:0.8.11",
"jazz-react": "workspace:0.8.15",
"jazz-tools": "workspace:0.8.15",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-hook-form": "^7.41.5",

View File

@@ -1,5 +1,41 @@
# jazz-example-pets
## 0.0.111
### Patch Changes
- Updated dependencies [cce679b]
- jazz-tools@0.8.15
- jazz-browser-media-images@0.8.15
- jazz-react@0.8.15
## 0.0.110
### Patch Changes
- Updated dependencies [36273b3]
- jazz-tools@0.8.14
- jazz-browser-media-images@0.8.14
- jazz-react@0.8.14
## 0.0.109
### Patch Changes
- Updated dependencies [fd011d7]
- jazz-tools@0.8.13
- jazz-browser-media-images@0.8.13
- jazz-react@0.8.13
## 0.0.108
### Patch Changes
- Updated dependencies [3cc6aee]
- jazz-react@0.8.12
- jazz-tools@0.8.12
- jazz-browser-media-images@0.8.12
## 0.0.107
### Patch Changes

View File

@@ -1,7 +1,7 @@
{
"name": "jazz-example-pets",
"private": true,
"version": "0.0.107",
"version": "0.0.111",
"type": "module",
"scripts": {
"dev": "vite",
@@ -23,9 +23,9 @@
"@radix-ui/react-toast": "^1.1.4",
"class-variance-authority": "^0.7.0",
"clsx": "^2.0.0",
"jazz-browser-media-images": "workspace:0.8.11",
"jazz-react": "workspace:0.8.11",
"jazz-tools": "workspace:0.8.11",
"jazz-browser-media-images": "workspace:0.8.15",
"jazz-react": "workspace:0.8.15",
"jazz-tools": "workspace:0.8.15",
"lucide-react": "^0.274.0",
"qrcode": "^1.5.3",
"react": "^18.2.0",
@@ -50,7 +50,7 @@
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-refresh": "^0.4.3",
"is-ci": "^3.0.1",
"jazz-run": "workspace:0.8.11",
"jazz-run": "workspace:0.8.15",
"postcss": "^8.4.27",
"tailwindcss": "3.3.2",
"typescript": "^5.3.3",

View File

@@ -1,5 +1,37 @@
# jazz-example-todo
## 0.0.110
### Patch Changes
- Updated dependencies [cce679b]
- jazz-tools@0.8.15
- jazz-react@0.8.15
## 0.0.109
### Patch Changes
- Updated dependencies [36273b3]
- jazz-tools@0.8.14
- jazz-react@0.8.14
## 0.0.108
### Patch Changes
- Updated dependencies [fd011d7]
- jazz-tools@0.8.13
- jazz-react@0.8.13
## 0.0.107
### Patch Changes
- Updated dependencies [3cc6aee]
- jazz-react@0.8.12
- jazz-tools@0.8.12
## 0.0.106
### Patch Changes

View File

@@ -1,7 +1,7 @@
{
"name": "jazz-example-todo",
"private": true,
"version": "0.0.106",
"version": "0.0.110",
"type": "module",
"scripts": {
"dev": "vite",
@@ -20,8 +20,8 @@
"@radix-ui/react-toast": "^1.1.4",
"class-variance-authority": "^0.7.0",
"clsx": "^2.0.0",
"jazz-react": "workspace:0.8.11",
"jazz-tools": "workspace:0.8.11",
"jazz-react": "workspace:0.8.15",
"jazz-tools": "workspace:0.8.15",
"lucide-react": "^0.274.0",
"qrcode": "^1.5.3",
"react": "^18.2.0",

View File

@@ -1,5 +1,11 @@
import clsx from "clsx";
export function P({ children, className }: { children: React.ReactNode, className?: string }) {
export function P({
children,
className,
}: {
children: React.ReactNode;
className?: string;
}) {
return <p className={clsx(className, "mb-4")}>{children}</p>;
}

View File

@@ -22,7 +22,7 @@ export function ThemeToggle({ className }: { className?: string }) {
"md:p-2 md:rounded-full md:border",
"text-stone-400 hover:text-stone-900 dark:text-stone-400 dark:hover:text-white",
"md:hover:bg-stone-200 md:dark:hover:bg-stone-900",
"transition-colors"
"transition-colors",
)}
aria-label={
mounted ? `Switch to ${otherTheme} theme` : "Toggle theme"

View File

@@ -37,7 +37,7 @@ function Copyright({
export function Footer({ logo, companyName, sections }: FooterProps) {
return (
<footer className="w-full border-t bg-stone-100 dark:bg-stone-925">
<footer className="w-full border-t bg-stone-100 mt-12 md:mt-20 dark:bg-stone-925">
<div className="container py-8 md:py-16 grid gap-y-8 grid-cols-12">
<div className="flex flex-col justify-between col-span-full md:col-span-4">
{logo}

View File

@@ -2,7 +2,7 @@
import { MenuIcon, XIcon } from "lucide-react";
import { usePathname } from "next/navigation";
import { ReactNode, useLayoutEffect, useRef, useState } from "react";
import { ReactNode, useEffect, useLayoutEffect, useRef, useState } from "react";
import { BreadCrumb } from "../molecules/Breadcrumb";
import clsx from "clsx";
import Link from "next/link";
@@ -35,6 +35,10 @@ export function Nav({
const pathname = usePathname();
useEffect(() => {
setMenuOpen(false);
}, [pathname]);
return (
<>
<nav

View File

@@ -135,7 +135,7 @@ const config = {
"--tw-prose-invert-code": stonePalette[50],
"--tw-prose-links": theme("colors.blue.DEFAULT"),
"--tw-prose-invert-links": theme("colors.blue.500"),
maxWidth: theme("screens.4xl"),
maxWidth: null,
strong: {
color: "var(--tw-prose-bold)",
fontWeight: theme("fontWeight.medium"),
@@ -167,10 +167,10 @@ const config = {
fontSize: theme("fontSize.2xl"),
},
h4: {
textTransform: "uppercase",
letterSpacing: theme("letterSpacing.widest"),
fontWeight: theme("fontWeight.medium"),
fontSize: theme("fontSize.sm"),
fontFamily: theme("fontFamily.display"),
letterSpacing: theme("letterSpacing.tight"),
fontWeight: theme("fontWeight.semibold"),
fontSize: theme("fontSize.xl"),
},
'code::before': {
content: 'none',

View File

@@ -83,7 +83,7 @@ export default function RootLayout({
manrope.variable,
commitMono.variable,
inter.className,
"h-full flex flex-col items-center",
"min-h-full flex flex-col items-center",
"bg-white text-stone-700 dark:text-stone-400 dark:bg-stone-950",
].join(" ")}
>
@@ -96,10 +96,10 @@ export default function RootLayout({
disableTransitionOnChange
>
<GcmpNav />
<main className="flex flex-1 flex-col w-full">
<main className="flex-1 w-full">
{children}
</main>
<footer className="py-8 md:py-16 text-sm flex justify-between gap-3 w-full container">
<footer className="py-8 text-sm flex justify-between gap-3 w-full container mt-12 md:mt-20">
<p>©2024 Garden Computing, Inc.</p>
<ThemeToggle className="hidden md:block"/>

View File

@@ -31,7 +31,7 @@ const team: Array<TeamMember> = [
x: "anselm_io",
github: "aeplay",
website: "http://anselm.io",
bluesky: "anselm-io",
bluesky: "anselm.io",
},
{
name: "Andrei Popa",
@@ -141,7 +141,7 @@ function Person({ person }: { person: TeamMember }) {
)}
{person.bluesky && (
<SocialLink
link={`https://bsky.app/profile/${person.bluesky}.bsky.social`}
link={`https://bsky.app/profile/${person.bluesky}`}
icon={SiBluesky}
label="Bluesky profile"
/>

View File

@@ -210,7 +210,7 @@ export default function Home() {
<BeforeAfterJazz />
<div className="container flex flex-col gap-12 py-12 lg:gap-20 lg:py-20">
<div className="container flex flex-col gap-12 mt-12 lg:gap-20 lg:mt-20">
<HowItWorks />
<Testimonial name="Serious Adopter #4" role="Technical Founder">

View File

@@ -88,11 +88,17 @@ export default function Page() {
</CardHeading>
<CardBody>
Get help from our{" "}
<Link href="https://discord.gg/utDMjHYg42" className="underline">
<Link
href="https://discord.gg/utDMjHYg42"
className="underline"
>
Discord
</Link>
, or open an issue on{" "}
<Link href="https://github.com/gardencmp/jazz" className="underline">
<Link
href="https://github.com/gardencmp/jazz"
className="underline"
>
GitHub
</Link>
.

View File

@@ -0,0 +1,9 @@
# The documentation for this feature is not yet available
Grayed out pages on the sidebar indicate that the documentation is not yet available.
This feature has already been released, but the documentation is still a work in progress.
If you don't find what you're looking for, [please ask for help in the Discord](https://discord.gg/utDMjHYg42).
We have a whole team of developers who are ready to help you get started.

View File

@@ -5,25 +5,59 @@ export default function Page() {
<Prose>
<h1>Welcome to the Jazz documentation.</h1>
<p>
The Jazz docs are currently heavily work in progress, sorry about that!
The Jazz docs are currently heavily work in progress, sorry
about that!
</p>
<p>The best ways to get started are:</p>
<ul>
<li>Quickstart (incomplete)<ol>
<li><a href="/docs/sync-and-storage">Sync & Storage Setup</a></li>
<li><a href="/docs/project-setup/react">React Project Setup</a></li>
<li><a href="/docs/schemas/covalues">CoValue Basics & Schema Definition</a></li>
<li><span className="opacity-50">Creating Covalues</span></li>
<li><span className="opacity-50">Using Covalues</span></li>
</ol></li>
<li>The step-by-step <a href="/docs/guide">Guide</a> (incomplete)</li>
<li>
Quickstart (incomplete)
<ol>
<li>
<a href="/docs/sync-and-storage">
Sync & Storage Setup
</a>
</li>
<li>
<a href="/docs/project-setup/react">
React Project Setup
</a>
</li>
<li>
<a href="/docs/schemas/covalues">
CoValue Basics & Schema Definition
</a>
</li>
<li>
<span className="opacity-50">
Creating Covalues
</span>
</li>
<li>
<span className="opacity-50">Using Covalues</span>
</li>
</ol>
</li>
<li>
The step-by-step <a href="/docs/guide">Guide</a>{" "}
(incomplete)
</li>
</ul>
<p>Also make sure to:</p>
<ul>
<li>Find an <a href="/docs/examples">example app with code</a> most similar to what you want to build</li>
<li>Check out the <a href="/docs/api-reference">API Reference</a> (incomplete)</li>
<li>
Find an <a href="/docs/examples">example app with code</a>{" "}
most similar to what you want to build
</li>
<li>
Check out the{" "}
<a href="/docs/api-reference">API Reference</a> (incomplete)
</li>
</ul>
<p>And the best way to get help is to join the <a href="https://discord.gg/utDMjHYg42">Discord</a>!</p>
<p>
And the best way to get help is to join the{" "}
<a href="https://discord.gg/utDMjHYg42">Discord</a>!
</p>
</Prose>
);
}

View File

@@ -77,7 +77,7 @@ export default function Page() {
<div
className={clsx(
"col-span-12 md:col-span-8 lg:col-span-9",
"lg:flex lg:gap-5",
"flex justify-center lg:gap-5",
)}
>
<Prose className="overflow-x-hidden lg:flex-1">

View File

@@ -0,0 +1,75 @@
import { CodeGroup } from "@/components/forMdx";
# Next.js
## <span id="next-csr">Client-side only</span>
The easiest way to use Jazz with Next.JS is to only use it on the client side. You can ensure this by:
- marking the `jazz.tsx` file as `"use client"`
<CodeGroup>
{/* prettier-ignore */}
```tsx
"use client"
import { createJazzReactApp } from "jazz-react";// old
const Jazz = createJazzReactApp();// old
export const { useAccount, useCoState } = Jazz;// old
import { PasskeyAuthBasicUI, usePasskeyAuth } from "jazz-react";// old
function JazzAndAuth({ children }: { children: React.ReactNode }) {// old
const [passkeyAuth, passKeyState] = usePasskeyAuth({ appName });// old
return (// old
<>// old
<Jazz.Provider// old
auth={passkeyAuth}// old
peer="wss://mesh.jazz.tools/?key=you@example.com"// old
>// old
{children}// old
</Jazz.Provider>// old
<PasskeyAuthBasicUI state={passKeyState} />// old
</>// old
);// old
}// old
```
</CodeGroup>
- marking any file with components where you use Jazz hooks (such as `useAccount` or `useCoState`) as `"use client"`
## <span id="next-ssr">SSR use (experimental)</span>
Pure SSR use of Jazz is basically just using jazz-nodejs (see [Node.JS / Server Workers](/docs/project-setup/server-side)) inside Server Components.
Instead of using hooks as you would on the client, you await promises returned by `CoValue.load(...)` inside your Server Components.
TODO: code example
This should work well for cases like rendering publicly-readable information, since the worker account will be able to load them.
In the future, it will be possible to use trusted auth methods (such as Clerk, Auth0, etc.) that let you act as the same Jazz user both on the client and on the server, letting you use SSR even for data private to that user.
## <span id="next-ssr-plus-csr">SSR + client-side (experimental)</span>
You can combine the two approaches by creating
1. A pure "rendering" component that renders an already-loaded CoValue (in JSON-ified form)
TODO: code example
2. A "hydrating" component (with `"use client"`) that
- expects a pre-loaded CoValue as a prop (in JSON-ified form)
- uses one of the client-side Jazz hooks (such as `useAccount` or `useCoState`) to subscribe to that same CoValue
- passing the client-side subscribed state to the "rendering" component, with the pre-loaded CoValue as a fallback until the client receives the first subscribed state
TODO: code example
3. A "pre-loading" Server Component that
- pre-loads the CoValue by awaiting it's `load(...)` method (as described above)
- renders the "hydrating" component, passing the pre-loaded CoValue as a prop
TODO: code example

View File

@@ -0,0 +1,35 @@
import NextGuide from "./next.mdx";
import { TableOfContents } from "@/components/docs/TableOfContents";
import { Prose } from "gcmp-design-system/src/app/components/molecules/Prose";
import { clsx } from "clsx";
const navItems = [
{
name: "Client-side only",
href: "/docs/project-setup/react#next-csr",
},
{
name: "SSR use 🧪",
href: "/docs/project-setup/react#next-ssr",
},
{
name: "SSR + client-side 🧪",
href: "/docs/project-setup/react#next-ssr-plus-csr",
},
];
export default function Page() {
return (
<div
className={clsx(
"col-span-12 md:col-span-8 lg:col-span-9",
"flex justify-center lg:gap-5",
)}
>
<Prose className="overflow-x-hidden lg:flex-1">
<NextGuide />
</Prose>
<TableOfContents className="w-48 shrink-0" items={navItems} />
</div>
);
}

View File

@@ -0,0 +1,30 @@
import ReactNativeGuide from "./react-native.mdx";
import { TableOfContents } from "@/components/docs/TableOfContents";
import { Prose } from "gcmp-design-system/src/app/components/molecules/Prose";
import { clsx } from "clsx";
const navItems = [
{
name: "Setup",
href: "/docs/project-setup/react#react-native-setup",
},
{
name: "Using Jazz",
href: "/docs/project-setup/react#react-native-using-jazz",
},
];
export default function Page() {
return (
<div
className={clsx(
"col-span-12 md:col-span-8 lg:col-span-9",
"flex justify-center lg:gap-5",
)}
>
<Prose className="overflow-x-hidden lg:flex-1">
<ReactNativeGuide />
</Prose>
<TableOfContents className="w-48 shrink-0" items={navItems} />
</div>
);
}

View File

@@ -0,0 +1,267 @@
import { CodeGroup } from "@/components/forMdx";
# <span id="react-native">React Native</span>
Jazz requires an [Expo development build](https://docs.expo.dev/develop/development-builds/introduction/) using [Expo Prebuild](https://docs.expo.dev/workflow/prebuild/) for native code. It is **not compatible** with Expo Go. Jazz also supports the [New Architecture](https://docs.expo.dev/guides/new-architecture/).
Tested with:
<CodeGroup>
```json
"expo": "~51.0.0",
"react-native": "~0.74.5",
"react": "^18.2.0",
```
</CodeGroup>
## <span id="react-native-setup">Setup</span>
### Create a New Project
(skip this step if you already have one)
<CodeGroup>
```bash
npx create-expo-app -e with-router-tailwind my-jazz-app
cd my-jazz-app
npx expo prebuild
```
</CodeGroup>
### Install dependencies
<CodeGroup>
```bash
npx expo install expo-linking expo-secure-store expo-file-system @react-native-community/netinfo @bam.tech/react-native-image-resizer
npm i -S react-native-polyfill-globals react-native-url-polyfill web-streams-polyfill@3.2.1 base-64 text-encoding react-native-fetch-api react-native-get-random-values buffer
npm i -D @babel/plugin-transform-class-static-block
npm i -S jazz-tools jazz-react-native jazz-react-native-media-images
```
</CodeGroup>
### Fix Incompatible Dependencies
<CodeGroup>
```bash
npx expo install --fix
```
</CodeGroup>
### Install Pods
<CodeGroup>
```bash
npx pod-install
```
</CodeGroup>
### Configure Metro
#### Regular Repositories
If you are not working within a monorepo, create a new file metro.config.js in the root of your project with the following content:
<CodeGroup>
```ts
const { getDefaultConfig } = require("expo/metro-config");
const config = getDefaultConfig(projectRoot);
config.resolver.unstable_enablePackageExports = true; // important setting
config.resolver.sourceExts = ["mjs", "js", "json", "ts", "tsx"];
config.resolver.requireCycleIgnorePatterns = [/(^|\/|\\)node_modules($|\/|\\)/];
module.exports = config;
```
</CodeGroup>
If you created the project using the command `npx create-expo-app -e with-router-tailwind my-jazz-app`, then `metro.config.js` is already present. In that case, simply add this setting to the existing file:
<CodeGroup>
```ts
config.resolver.unstable_enablePackageExports = true
```
</CodeGroup>
#### Monorepos
For monorepos, use the following metro.config.js:
<CodeGroup>
```ts
const { getDefaultConfig } = require("expo/metro-config");
const { FileStore } = require("metro-cache");
const path = require("path");
// eslint-disable-next-line no-undef
const projectRoot = __dirname;
const workspaceRoot = path.resolve(projectRoot, "../..");
const config = getDefaultConfig(projectRoot);
config.watchFolders = [workspaceRoot];
config.resolver.nodeModulesPaths = [
path.resolve(projectRoot, "node_modules"),
path.resolve(workspaceRoot, "node_modules"),
];
config.resolver.sourceExts = ["mjs", "js", "json", "ts", "tsx"];
config.resolver.unstable_enablePackageExports = true;
config.resolver.requireCycleIgnorePatterns = [/(^|\/|\\)node_modules($|\/|\\)/];
config.cacheStores = [
new FileStore({
root: path.join(projectRoot, "node_modules", ".cache", "metro"),
}),
];
module.exports = config;
```
</CodeGroup>
### Additional Monorepo Configuration (for pnpm users)
- Add node-linker=hoisted to the root .npmrc (create this file if it doesnt exist).
- Add the following to the root package.json:
<CodeGroup>
```json
"pnpm": {
"peerDependencyRules": {
"ignoreMissing": [
"@babel/*",
"expo-modules-*",
"typescript"
]
}
}
```
</CodeGroup>
For more information, refer to [this](https://github.com/byCedric/expo-monorepo-example#pnpm-workarounds) Expo monorepo example.
### Configure Babel
Add `@babel/plugin-transform-class-static-block` to the array of Babel plugins inside `babel.config.js`:
<CodeGroup>
```ts
module.exports = function (api) {
api.cache(true);
return {
presets: ["babel-preset-expo"],
plugins: [
"nativewind/babel",
"@babel/plugin-transform-class-static-block",
],
};
};
```
</CodeGroup>
### Add Polyfills
Create a file `polyfills.js` at the project root with the following content:
<CodeGroup>
```ts
import "react-native-polyfill-globals/auto";
import "@azure/core-asynciterator-polyfill";
import { ReadableStream } from "web-streams-polyfill/ponyfill/es6";
import { polyfillGlobal } from "react-native/Libraries/Utilities/PolyfillFunctions";
import { Buffer } from "buffer";
polyfillGlobal("Buffer", () => Buffer);
polyfillGlobal("ReadableStream", () => ReadableStream);
```
</CodeGroup>
Update `index.js` based on whether you are using expo-router or not:
#### If using `expo-router`
<CodeGroup>
```ts
import "./polyfills";
import "expo-router/entry";
```
</CodeGroup>
#### Without `expo-router`
<CodeGroup>
```ts
import "./polyfills";
import { registerRootComponent } from "expo";
import App from "./src/App";
registerRootComponent(App);
```
</CodeGroup>
Lastly, ensure that the `"main"` field in your `package.json` points to `index.js`:
<CodeGroup>
```json
"main": "index.js",
```
</CodeGroup>
## <span id="react-native-using-jazz">Using Jazz</span>
### `createJazzRNApp()`
Create a file `jazz.tsx` with the following contents:
<CodeGroup>
```tsx
import { createJazzRNApp } from "jazz-react-native";
export const Jazz = createJazzRNApp();
export const { useAccount, useCoState, useAcceptInvite } = Jazz;
```
</CodeGroup>
You can optionally pass a custom `kvStore` and `AccountSchema` to `createJazzRNApp()`, otherwise, it defaults to `ExpoSecureStoreAdapter` and `Account`.
### Choosing an Auth Method
Refer to the Jazz + React Native demo projects for implementing authentication:
- [DemoAuth Example](https://github.com/gardencmp/jazz/tree/main/examples/chat-rn)
- [ClerkAuth Example](https://github.com/gardencmp/jazz/tree/main/examples/chat-rn-clerk)
In the demos, you'll find details on:
- Using Jazz.Provider with your chosen authentication method
- Defining a Jazz schema
- Creating and subscribing to covalues
- Handling invites
### Working with Images
To work with images in Jazz, import the `createImage` function from [`jazz-react-native-media-images`](https://github.com/gardencmp/jazz/tree/main/packages/jazz-react-native-media-images).
<CodeGroup>
```tsx
import { createImage } from "jazz-react-native-media-images";
const base64ImageDataURI = "data:image/png;base64,...";
const image = await createImage(base64ImageDataURI, {
owner: newPetPost._owner,
maxSize: 2048, // optional: specify maximum image size
});
someCovalue.image = image;
```
</CodeGroup>
For a complete implementation, please refer to [this](https://github.com/gardencmp/jazz/blob/main/examples/pets/src/3_NewPetPostForm.tsx) demo.
### Running Your App
<CodeGroup>
```bash
npx expo run:ios
npx expo run:android
```
</CodeGroup>

View File

@@ -1,50 +1,18 @@
import ReactGuide from "./react.mdx"
import { TableOfContents } from "@/components/docs/TableOfContents";
import ReactGuide from "./react.mdx";
import { Prose } from "gcmp-design-system/src/app/components/molecules/Prose";
import { clsx } from "clsx";
const navItems = [
{
name: "React",
href: "/docs/project-setup/react#react",
},
{
name: "Next.JS",
href: "/docs/project-setup/react#next",
items: [
{
name: "Client-side only",
href: "/docs/project-setup/react#next-csr",
},
{
name: "SSR use 🧪",
href: "/docs/project-setup/react#next-ssr",
},
{
name: "SSR + client-side 🧪",
href: "/docs/project-setup/react#next-ssr-plus-csr",
},
]
},
{
name: "React Native",
href: "/docs/project-setup/react#react-native"
}
]
export default function Page() {
return (
<div
className={clsx(
"col-span-12 md:col-span-8 lg:col-span-9",
"lg:flex lg:gap-5",
"flex justify-center lg:gap-5",
)}
>
<Prose className="overflow-x-hidden lg:flex-1">
<ReactGuide />
</Prose>
<TableOfContents className="w-48 shrink-0" items={navItems} />
</div>
);
}

View File

@@ -1,6 +1,6 @@
import { CodeGroup } from "@/components/forMdx";
# React
# <span id="react">React</span>
Currently, the recommended pattern to set up a React app with Jazz is to create a separate file (for example, called `jazz.tsx`) in which:
@@ -80,82 +80,3 @@ ReactDOM.createRoot(document.getElementById("root")!).render( // old
);// old
```
</CodeGroup>
# Next.JS
## Client-side only
The easiest way to use Jazz with Next.JS is to only use it on the client side. You can ensure this by:
- marking the `jazz.tsx` file as `"use client"`
<CodeGroup>
{/* prettier-ignore */}
```tsx
"use client"
import { createJazzReactApp } from "jazz-react";// old
const Jazz = createJazzReactApp();// old
export const { useAccount, useCoState } = Jazz;// old
import { PasskeyAuthBasicUI, usePasskeyAuth } from "jazz-react";// old
function JazzAndAuth({ children }: { children: React.ReactNode }) {// old
const [passkeyAuth, passKeyState] = usePasskeyAuth({ appName });// old
return (// old
<>// old
<Jazz.Provider// old
auth={passkeyAuth}// old
peer="wss://mesh.jazz.tools/?key=you@example.com"// old
>// old
{children}// old
</Jazz.Provider>// old
<PasskeyAuthBasicUI state={passKeyState} />// old
</>// old
);// old
}// old
```
</CodeGroup>
- marking any file with components where you use Jazz hooks (such as `useAccount` or `useCoState`) as `"use client"`
## SSR use (experimental)
Pure SSR use of Jazz is basically just using jazz-nodejs (see [Node.JS / Server Workers](/docs/project-setup/server-side)) inside Server Components.
Instead of using hooks as you would on the client, you await promises returned by `CoValue.load(...)` inside your Server Components.
TODO: code example
This should work well for cases like rendering publicly-readable information, since the worker account will be able to load them.
In the future, it will be possible to use trusted auth methods (such as Clerk, Auth0, etc.) that let you act as the same Jazz user both on the client and on the server, letting you use SSR even for data private to that user.
## SSR + client-side (experimental)
You can combine the two approaches by creating
1. A pure "rendering" component that renders an already-loaded CoValue (in JSON-ified form)
TODO: code example
2. A "hydrating" component (with `"use client"`) that
- expects a pre-loaded CoValue as a prop (in JSON-ified form)
- uses one of the client-side Jazz hooks (such as `useAccount` or `useCoState`) to subscribe to that same CoValue
- passing the client-side subscribed state to the "rendering" component, with the pre-loaded CoValue as a fallback until the client receives the first subscribed state
TODO: code example
3. A "pre-loading" Server Component that
- pre-loads the CoValue by awaiting it's `load(...)` method (as described above)
- renders the "hydrating" component, passing the pre-loaded CoValue as a prop
TODO: code example
# React Native
TODO

View File

@@ -1,4 +1,4 @@
import ServerGuide from "./server-side.mdx"
import ServerGuide from "./server-side.mdx";
import { TableOfContents } from "@/components/docs/TableOfContents";
import { Prose } from "gcmp-design-system/src/app/components/molecules/Prose";
import { clsx } from "clsx";
@@ -19,16 +19,15 @@ const navItems = [
{
name: "Using CoValues instead of Requests",
href: "/docs/project-setup/server-side#covalues-instead-of-requests",
}
]
},
];
export default function Page() {
return (
<div
className={clsx(
"col-span-12 md:col-span-8 lg:col-span-9",
"lg:flex lg:gap-5",
"flex justify-center lg:gap-5",
)}
>
<Prose className="overflow-x-hidden lg:flex-1">

View File

@@ -1,4 +1,4 @@
import CoValuesGuide from "./covalues.mdx"
import CoValuesGuide from "./covalues.mdx";
import { TableOfContents } from "@/components/docs/TableOfContents";
import { Prose } from "gcmp-design-system/src/app/components/molecules/Prose";
import { clsx } from "clsx";
@@ -26,9 +26,9 @@ const navItems = [
},
{
name: "Computed Fields, Methods & Constructors ",
href: "/docs/schemas/covalues#custom-fields"
}
]
href: "/docs/schemas/covalues#custom-fields",
},
],
},
{
name: "CoMaps",
@@ -42,7 +42,7 @@ const navItems = [
name: "Dict/Record-like CoMaps",
href: "/docs/schemas/covalues#comaps-dict-like",
},
]
],
},
{
name: "CoLists",
@@ -55,16 +55,15 @@ const navItems = [
{
name: "BinaryCoStreams",
href: "/docs/schemas/covalues#binarycostreams",
}
]
},
];
export default function Page() {
return (
<div
className={clsx(
"col-span-12 md:col-span-8 lg:col-span-9",
"lg:flex lg:gap-5",
"flex justify-center lg:gap-5",
)}
>
<Prose className="overflow-x-hidden lg:flex-1">

View File

@@ -1,4 +1,4 @@
import SyncAndStorage from "./sync-and-storage.mdx"
import SyncAndStorage from "./sync-and-storage.mdx";
import { TableOfContents } from "@/components/docs/TableOfContents";
import { Prose } from "gcmp-design-system/src/app/components/molecules/Prose";
import { clsx } from "clsx";
@@ -12,7 +12,7 @@ const navItems = [
name: "Free Public Alpha",
href: "/docs/sync-and-storage#free-public-alpha",
},
]
],
},
{
name: "Running your own sync server",
@@ -26,17 +26,16 @@ const navItems = [
name: "Source code",
href: "/docs/sync-and-storage#source-code",
},
]
}
]
],
},
];
export default function Page() {
return (
<div
className={clsx(
"col-span-12 md:col-span-8 lg:col-span-9",
"lg:flex lg:gap-5",
"flex justify-center lg:gap-5",
)}
>
<Prose className="overflow-x-hidden lg:flex-1">

View File

@@ -83,7 +83,7 @@ export default function RootLayout({
manrope.variable,
commitMono.variable,
inter.className,
"h-full flex flex-col items-center [&_*]:scroll-mt-[5rem]",
"min-h-full flex flex-col items-center [&_*]:scroll-mt-[5rem]",
"bg-white text-stone-700 dark:text-stone-400 dark:bg-stone-950",
].join(" ")}
>
@@ -96,7 +96,7 @@ export default function RootLayout({
disableTransitionOnChange
>
<JazzNav />
<main className="flex flex-col w-full">{children}</main>
<main className="flex-1 w-full">{children}</main>
<JazzFooter />
</ThemeProvider>
</body>

View File

@@ -20,6 +20,11 @@ export function SideNav({
}) {
return (
<div className={clsx(className, "text-sm space-y-5")}>
<div className="flex items-center gap-2">
<span className="inline-block size-2 rounded-full bg-yellow-400"></span>{" "}
Documentation coming soon
</div>
{items.map(({ name, href, items }) => (
<div key={name}>
<SideNavHeader href={href}>{name}</SideNavHeader>
@@ -27,23 +32,29 @@ export function SideNav({
items.map(({ name, href, items, done }) => (
<ul key={name}>
<li>
<SideNavItem href={done === 0 ? undefined : href}>
<SideNavItem
href={
done === 0
? "/docs/coming-soon"
: href
}
>
{done == 0 && (
<span className="mr-1.5 inline-block size-2 rounded-full bg-yellow-400"></span>
)}
<span
className={
done === 0 ? "opacity-50" : ""
done === 0
? "text-stone-400 dark:text-stone-600"
: ""
}
>
{name}
</span>{" "}
{done === undefined ? (
""
) : (
<span className="text-xs opacity-50">
({done}%)
</span>
)}
</span>
</SideNavItem>
</li>
{items && items?.length > 0 && (
<ul className="pl-4">
{items.map(({ name, href }) => (

View File

@@ -11,7 +11,10 @@ export function SideNavItem({
children: ReactNode;
className?: string;
}) {
const classes = clsx(className, "py-1 block hover:transition-colors");
const classes = clsx(
className,
"py-1 flex items-center hover:transition-colors",
);
if (href) {
return (

View File

@@ -14,6 +14,8 @@ export function TableOfContents({
items: NavItem[];
className?: string;
}) {
if (!items.length) return null;
return (
<div
className={clsx(

View File

@@ -13,12 +13,14 @@ export function DocNav({ className }: { className?: string }) {
return (
<SideNav
items={docNavigationItems}
className={clsx(twMerge(
"pr-3 md:col-span-4 lg:col-span-3",
"sticky align-start top-[4.75rem] h-[calc(100vh-8rem)] overflow-y-auto overflow-x-hidden",
"hidden md:block",
className
))}
className={clsx(
twMerge(
"pr-3 md:col-span-4 lg:col-span-3",
"sticky align-start top-[4.75rem] h-[calc(100vh-8rem)] overflow-y-auto overflow-x-hidden",
"hidden md:block",
className,
),
)}
>
<div>
<SideNavHeader href="/docs/api-reference">

View File

@@ -1,7 +1,4 @@
import {
ArrowDownIcon,
ArrowRightIcon,
} from "lucide-react";
import { ArrowDownIcon, ArrowRightIcon } from "lucide-react";
import { DiagramBeforeJazz } from "@/components/DiagramBeforeJazz";
import { DiagramAfterJazz } from "@/components/DiagramAfterJazz";
import { SectionHeader } from "gcmp-design-system/src/app/components/molecules/SectionHeader";
@@ -33,10 +30,10 @@ export default function BeforeAfterJazz() {
The sad truth is...
</p>
<p>
<strong>
Every stack reinvents how users and machines
share state.
</strong>
<strong>
Every stack reinvents how users and machines
share state.
</strong>
</p>
</Prose>
<div className="relative flex items-center flex-1">

View File

@@ -48,7 +48,7 @@ export function JazzNav() {
icon: <SiTwitter className="w-5" />,
},
]}
docNav={<DocNav className="block h-auto"/>}
docNav={<DocNav className="block h-auto" />}
/>
);
}

View File

@@ -12,7 +12,7 @@ export const docNavigationItems = [
{
name: "Guide",
href: "/docs/guide",
done: 50
done: 50,
},
],
},
@@ -26,10 +26,18 @@ export const docNavigationItems = [
done: 100,
},
{
name: "React / Next.js / React Native",
name: "React",
href: "/docs/project-setup/react",
done: 80,
},
{
name: "React Native",
href: "/docs/project-setup/react-native",
},
{
name: "Next.js",
href: "/docs/project-setup/next",
},
{
name: "Node.JS / Server Workers",
href: "/docs/project-setup/server-side",
@@ -43,12 +51,12 @@ export const docNavigationItems = [
{
name: "CoValues",
href: "/docs/schemas/covalues",
done: 20
done: 20,
},
{
name: "Accounts & Migrations",
href: "/docs/schemas/accounts",
done: 0
done: 0,
},
],
},
@@ -58,22 +66,22 @@ export const docNavigationItems = [
{
name: "Creation & Ownership",
href: "/docs/covalues/creation",
done: 0
done: 0,
},
{
name: "Subscribing & Deep Loading",
href: "/docs/covalues/reading",
done: 0
done: 0,
},
{
name: "Updating & Deleting",
href: "/docs/covalues/updating",
done: 0
done: 0,
},
{
name: "Metadata & Time-travel",
href: "/docs/covalues/metadata",
done: 0
done: 0,
},
],
},
@@ -83,17 +91,17 @@ export const docNavigationItems = [
{
name: "Groups as Permission Scopes",
href: "/docs/groups/intro",
done: 0
done: 0,
},
{
name: "Public Sharing & Invites",
href: "/docs/groups/sharing",
done: 0
done: 0,
},
{
name: "Group Inheritance",
href: "/docs/groups/inheritance",
done: 0
done: 0,
},
],
},
@@ -103,12 +111,12 @@ export const docNavigationItems = [
{
name: "Auth Methods Overview",
href: "/docs/authentication/auth-methods",
done: 0
done: 0,
},
{
name: "Writing Your Own",
href: "/docs/authentication/writing-your-own",
done: 0
done: 0,
},
],
},
@@ -118,35 +126,12 @@ export const docNavigationItems = [
{
name: "Example apps",
href: "/docs/examples",
done: 30
done: 30,
},
{
name: "Jazz under the hood",
href: "/docs/jazz-under-the-hood",
done: 0
},
],
},
];
export const docNavigationItemsOld = [
{
// welcome to jazz
name: "Getting started",
href: "/docs/guide",
items: [
{
name: "Guide",
href: "/docs/guide",
},
],
},
{
name: "Resources",
items: [
{
name: "Example apps",
href: "/docs/examples",
done: 0,
},
],
},

View File

@@ -29,7 +29,7 @@ const config = {
function highlightPlugin() {
return async function transformer(tree) {
const highlighter = await getHighlighter({
langs: ["typescript", "bash", "tsx"],
langs: ["typescript", "bash", "tsx", "json"],
theme: "css-variables", // use the theme
});
@@ -47,7 +47,7 @@ function highlightPlugin() {
node.type = "html";
node.value = `<code class="not-prose py-2 flex flex-col leading-relaxed">${lines
.map((line) => {
let lineClassName = ''
let lineClassName = "";
const isSubduedLine = line.some((token) =>
token.content.includes("// old"),
@@ -58,19 +58,17 @@ function highlightPlugin() {
if (!isBinnedLine) {
lineNo++;
}
if (isBinnedLine) {
lineClassName = 'bg-red-100 dark:bg-red-800'
lineClassName = "bg-red-100 dark:bg-red-800";
}
return (
`<span class="block px-3 min-h-[1em] ${lineClassName}" style="${isBinnedLine ? "user-select: none" : ""}">` +
line
.map(
(token) => {
return `<span style="color: ${token.color};${isSubduedLine ? "opacity: 0.4;" : ""}">${escape(token.content.replace("// old", "").replace("// *bin*", ""))}</span>`
}
)
.map((token) => {
return `<span style="color: ${token.color};${isSubduedLine ? "opacity: 0.4;" : ""}">${escape(token.content.replace("// old", "").replace("// *bin*", ""))}</span>`;
})
.join("") +
"</span>"
);

View File

@@ -12,6 +12,7 @@
"@changesets/cli": "^2.27.3",
"@vitest/coverage-istanbul": "1.5.3",
"@vitest/ui": "1.5.3",
"happy-dom": "^15.8.3",
"prettier": "^3.1.1",
"ts-node": "^10.9.1",
"turbo": "^1.11.2",

View File

@@ -1,5 +1,13 @@
# cojson-storage-indexeddb
## 0.8.12
### Patch Changes
- 6ed75eb: Introduce "storage" peer role
- Updated dependencies [6ed75eb]
- cojson@0.8.12
## 0.8.11
### Patch Changes

View File

@@ -1,12 +1,12 @@
{
"name": "cojson-storage-indexeddb",
"version": "0.8.11",
"version": "0.8.12",
"main": "dist/index.js",
"type": "module",
"types": "src/index.ts",
"license": "MIT",
"dependencies": {
"cojson": "workspace:0.8.11",
"cojson": "workspace:0.8.12",
"typescript": "^5.3.3"
},
"devDependencies": {

View File

@@ -94,7 +94,7 @@ export class IDBStorage {
"storage",
{
peer1role: "client",
peer2role: "server",
peer2role: "storage",
trace,
crashOnClose: true,
},

View File

@@ -1,5 +1,13 @@
# cojson-storage-sqlite
## 0.8.12
### Patch Changes
- 6ed75eb: Introduce "storage" peer role
- Updated dependencies [6ed75eb]
- cojson@0.8.12
## 0.8.11
### Patch Changes

View File

@@ -1,13 +1,13 @@
{
"name": "cojson-storage-sqlite",
"type": "module",
"version": "0.8.11",
"version": "0.8.12",
"main": "dist/index.js",
"types": "src/index.ts",
"license": "MIT",
"dependencies": {
"better-sqlite3": "^8.5.2",
"cojson": "workspace:0.8.11",
"cojson": "workspace:0.8.12",
"typescript": "^5.3.3"
},
"devDependencies": {

View File

@@ -94,7 +94,7 @@ export class SQLiteStorage {
const [localNodeAsPeer, storageAsPeer] = cojsonInternals.connectedPeers(
localNodeName,
"storage",
{ peer1role: "client", peer2role: "server", trace, crashOnClose: true },
{ peer1role: "client", peer2role: "storage", trace, crashOnClose: true },
);
await SQLiteStorage.open(

View File

@@ -1,5 +1,18 @@
# cojson-transport-nodejs-ws
## 0.8.13
### Patch Changes
- e0dd006: Add the onClose handler and improve the closing operation
## 0.8.12
### Patch Changes
- Updated dependencies [6ed75eb]
- cojson@0.8.12
## 0.8.11
### Patch Changes

View File

@@ -1,12 +1,12 @@
{
"name": "cojson-transport-ws",
"type": "module",
"version": "0.8.11",
"version": "0.8.13",
"main": "dist/index.js",
"types": "src/index.ts",
"license": "MIT",
"dependencies": {
"cojson": "workspace:0.8.11",
"cojson": "workspace:0.8.12",
"typescript": "^5.3.3"
},
"scripts": {

View File

@@ -18,87 +18,54 @@ export type CreateWebSocketPeerOpts = {
role: Peer["role"];
expectPings?: boolean;
batchingByDefault?: boolean;
onClose?: () => void;
};
export function createWebSocketPeer({
id,
websocket,
role,
expectPings = true,
batchingByDefault = true,
}: CreateWebSocketPeerOpts): Peer {
const incoming = new cojsonInternals.Channel<
SyncMessage | DisconnectedError | PingTimeoutError
>();
websocket.addEventListener("close", function handleClose() {
incoming
.push("Disconnected")
.catch((e) =>
console.error("Error while pushing disconnect msg", e),
);
});
function createPingTimeoutListener(enabled: boolean, callback: () => void) {
if (!enabled) {
return {
reset() {},
clear() {}
}
}
let pingTimeout: ReturnType<typeof setTimeout> | null = null;
let supportsBatching = batchingByDefault;
websocket.addEventListener("message", function handleIncomingMsg(event) {
const result = deserializeMessages(event.data as string);
if (!result.ok) {
console.error("Error while deserializing messages", event.data, result.error);
return;
}
const { messages } = result;
if (!supportsBatching && messages.length > 1) {
// If more than one message is received, the other peer supports batching
supportsBatching = true;
}
if (expectPings) {
return {
reset() {
pingTimeout && clearTimeout(pingTimeout);
pingTimeout = setTimeout(() => {
incoming
.push("PingTimeout")
.catch((e) =>
console.error("Error while pushing ping timeout", e),
);
callback();
}, 10_000);
},
clear() {
pingTimeout && clearTimeout(pingTimeout);
}
};
}
for (const msg of messages) {
if (msg && "action" in msg) {
incoming
.push(msg)
.catch((e) =>
console.error("Error while pushing incoming msg", e),
);
}
}
});
const websocketOpen = new Promise<void>((resolve) => {
function waitForWebSocketOpen(websocket: AnyWebSocket) {
return new Promise<void>((resolve) => {
if (websocket.readyState === 1) {
resolve();
} else {
websocket.addEventListener("open", resolve, { once: true });
}
});
}
function createOutgoingMessagesManager(websocket: AnyWebSocket, batchingByDefault: boolean) {
const outgoingMessages = new BatchedOutgoingMessages((messages) => {
if (websocket.readyState === 1) {
websocket.send(
messages,
);
websocket.send(messages);
}
});
async function pushMessage(msg: SyncMessage) {
let batchingEnabled = batchingByDefault;
async function sendMessage(msg: SyncMessage) {
if (websocket.readyState !== 1) {
await websocketOpen;
await waitForWebSocketOpen(websocket);
}
while (
@@ -114,23 +81,114 @@ export function createWebSocketPeer({
return;
}
if (!supportsBatching) {
if (!batchingEnabled) {
websocket.send(JSON.stringify(msg));
} else {
outgoingMessages.push(msg);
}
}
return {
sendMessage,
setBatchingEnabled(enabled: boolean) {
batchingEnabled = enabled;
},
close() {
outgoingMessages.close();
},
};
}
function createClosedEventEmitter(callback = () => {}) {
let disconnected = false;
return () => {
if (disconnected) return;
disconnected = true;
callback();
}
}
export function createWebSocketPeer({
id,
websocket,
role,
expectPings = true,
batchingByDefault = true,
onClose,
}: CreateWebSocketPeerOpts): Peer {
const incoming = new cojsonInternals.Channel<
SyncMessage | DisconnectedError | PingTimeoutError
>();
const emitClosedEvent = createClosedEventEmitter(onClose);
function handleClose() {
incoming
.push("Disconnected")
.catch((e) =>
console.error("Error while pushing disconnect msg", e),
);
emitClosedEvent();
}
websocket.addEventListener("close", handleClose);
const pingTimeout = createPingTimeoutListener(expectPings, () => {
incoming
.push("PingTimeout")
.catch((e) => console.error("Error while pushing ping timeout", e));
emitClosedEvent();
});
const outgoingMessages = createOutgoingMessagesManager(websocket, batchingByDefault);
function handleIncomingMsg(event: { data: unknown }) {
const result = deserializeMessages(event.data);
if (!result.ok) {
console.error(
"Error while deserializing messages",
event.data,
result.error,
);
return;
}
const { messages } = result;
if (messages.length > 1) {
// If more than one message is received, the other peer supports batching
outgoingMessages.setBatchingEnabled(true);
}
pingTimeout.reset();
for (const msg of messages) {
if (msg && "action" in msg) {
incoming
.push(msg)
.catch((e) =>
console.error("Error while pushing incoming msg", e),
);
}
}
}
websocket.addEventListener("message", handleIncomingMsg);
return {
id,
incoming,
outgoing: {
push: pushMessage,
push: outgoingMessages.sendMessage,
close() {
console.log("Trying to close", id, websocket.readyState);
if (supportsBatching) {
outgoingMessages.close();
}
outgoingMessages.close();
websocket.removeEventListener("message", handleIncomingMsg);
websocket.removeEventListener("close", handleClose);
pingTimeout.clear();
emitClosedEvent();
if (websocket.readyState === 0) {
websocket.addEventListener(

View File

@@ -8,7 +8,14 @@ export function addMessageToBacklog(backlog: string, message: SyncMessage) {
return `${backlog}\n${JSON.stringify(message)}`;
}
export function deserializeMessages(messages: string) {
export function deserializeMessages(messages: unknown) {
if (typeof messages !== "string") {
return {
ok: false,
error: new Error("Expected a string"),
} as const;
}
try {
return {
ok: true,

View File

@@ -18,6 +18,9 @@ function setup(opts: Partial<CreateWebSocketPeerOpts> = {}) {
addEventListener: vi.fn().mockImplementation((type, listener) => {
listeners.set(type, listener);
}),
removeEventListener: vi.fn().mockImplementation((type) => {
listeners.delete(type);
}),
close: vi.fn(),
send: vi.fn(),
} as unknown as Mocked<AnyWebSocket>;

View File

@@ -1,5 +1,11 @@
# cojson
## 0.8.12
### Patch Changes
- 6ed75eb: Introduce "storage" peer role
## 0.8.11
### Patch Changes

View File

@@ -19,7 +19,7 @@
},
"type": "module",
"license": "MIT",
"version": "0.8.11",
"version": "0.8.12",
"devDependencies": {
"@types/jest": "^29.5.3",
"@typescript-eslint/eslint-plugin": "^6.2.1",

View File

@@ -29,6 +29,10 @@ export class PeerState {
return this.peer.crashOnClose;
}
isServerOrStoragePeer() {
return this.peer.role === "server" || this.peer.role === "storage";
}
/**
* We set as default priority HIGH to handle all the messages without a
* priority property as HIGH priority.

View File

@@ -281,7 +281,7 @@ export class LocalNode {
if (!entry) {
const peersToWaitFor = new Set(
Object.values(this.syncManager.peers)
.filter((peer) => peer.role === "server")
.filter((peer) => peer.isServerOrStoragePeer())
.map((peer) => peer.id),
);
if (options.dontWaitFor) peersToWaitFor.delete(options.dontWaitFor);

View File

@@ -560,7 +560,7 @@ export class LSMStorage<WH, RH, FS extends FileSystem<WH, RH>> {
"storage",
{
peer1role: "client",
peer2role: "server",
peer2role: "storage",
trace,
crashOnClose: true,
},

View File

@@ -75,7 +75,7 @@ export interface Peer {
id: PeerID;
incoming: IncomingSyncStream;
outgoing: OutgoingSyncQueue;
role: "peer" | "server" | "client";
role: "peer" | "server" | "client" | "storage";
priority?: number;
crashOnClose: boolean;
}
@@ -129,7 +129,7 @@ export class SyncManager {
async loadFromPeers(id: RawCoID, forPeer?: PeerID) {
const eligiblePeers = this.peersInPriorityOrder().filter(
(peer) => peer.id !== forPeer && peer.role === "server",
(peer) => peer.id !== forPeer && peer.isServerOrStoragePeer(),
);
const coValueEntry = this.local.coValues[id];
@@ -305,7 +305,7 @@ export class SyncManager {
const peerState = new PeerState(peer);
this.peers[peer.id] = peerState;
if (peer.role === "server") {
if (peerState.isServerOrStoragePeer()) {
const initialSync = async () => {
for (const id of Object.keys(
this.local.coValues,
@@ -393,7 +393,7 @@ export class SyncManager {
// special case: we should be able to solve this much more neatly
// with an explicit state machine in the future
const eligiblePeers = this.peersInPriorityOrder().filter(
(other) => other.id !== peer.id && other.role === "server",
(other) => other.id !== peer.id && other.isServerOrStoragePeer(),
);
if (eligiblePeers.length === 0) {
if (msg.header || Object.keys(msg.sessions).length > 0) {
@@ -698,7 +698,7 @@ export class SyncManager {
coValue.id,
peer,
);
} else if (peer.role === "server") {
} else if (peer.isServerOrStoragePeer()) {
await this.subscribeToIncludingDependencies(coValue.id, peer);
await this.sendNewContentIncludingDependencies(
coValue.id,

View File

@@ -1,5 +1,40 @@
# jazz-browser-media-images
## 0.8.15
### Patch Changes
- 221c58f: Store clerk credentials in localStorage, and use if available
- Updated dependencies [cce679b]
- jazz-tools@0.8.15
- jazz-browser@0.8.15
## 0.8.14
### Patch Changes
- Updated dependencies [36273b3]
- jazz-tools@0.8.14
- jazz-browser@0.8.14
## 0.8.13
### Patch Changes
- Updated dependencies [fd011d7]
- Updated dependencies [e0dd006]
- jazz-tools@0.8.13
- jazz-browser@0.8.13
## 0.8.12
### Patch Changes
- Updated dependencies [6ed75eb]
- cojson@0.8.12
- jazz-browser@0.8.12
- jazz-tools@0.8.12
## 0.8.11
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "jazz-browser-auth-clerk",
"version": "0.8.11",
"version": "0.8.15",
"type": "module",
"main": "dist/index.js",
"types": "src/index.ts",
@@ -10,15 +10,17 @@
"*.{js,jsx,mdx,json}": "prettier --write"
},
"dependencies": {
"cojson": "workspace:0.8.11",
"jazz-browser": "workspace:0.8.11",
"jazz-tools": "workspace:0.8.11"
"cojson": "workspace:0.8.12",
"jazz-browser": "workspace:0.8.15",
"jazz-tools": "workspace:0.8.15"
},
"scripts": {
"lint": "eslint . --ext ts,tsx",
"format": "prettier --write './src/**/*.{ts,tsx}'",
"build": "npm run lint && rm -rf ./dist && tsc --sourceMap --outDir dist",
"prepublishOnly": "npm run build"
"prepublishOnly": "npm run build",
"test": "vitest --run --root ../../ --project jazz-browser-auth-clerk",
"test:watch": "vitest --watch --root ../../ --project jazz-browser-auth-clerk"
},
"devDependencies": {
"typescript": "^5.3.3"

View File

@@ -1,5 +1,7 @@
import { Account, AuthMethod, AuthResult, ID } from "jazz-tools";
import { AgentSecret } from "cojson";
import { Account, AuthMethod, AuthResult, Credentials, ID } from "jazz-tools";
const localStorageKey = "jazz-clerk-auth";
export type MinimalClerkClient = {
user: {
@@ -16,6 +18,16 @@ export type MinimalClerkClient = {
signOut: () => Promise<void>;
}
function saveCredentialsToLocalStorage(credentials: Credentials) {
localStorage.setItem(
localStorageKey,
JSON.stringify({
accountID: credentials.accountID,
secret: credentials.secret,
}),
);
}
export class BrowserClerkAuth implements AuthMethod {
constructor(
public driver: BrowserClerkAuth.Driver,
@@ -23,7 +35,34 @@ export class BrowserClerkAuth implements AuthMethod {
) {}
async start(): Promise<AuthResult> {
// Check local storage for credentials
const locallyStoredCredentials = localStorage.getItem(localStorageKey);
if (locallyStoredCredentials) {
try {
const credentials = JSON.parse(
locallyStoredCredentials,
) as Credentials;
return {
type: "existing",
credentials,
saveCredentials: async () => {}, // No need to save credentials when recovering from local storage
onSuccess: () => {},
onError: (error: string | Error) => {
this.driver.onError(error);
},
logOut: () => {
localStorage.removeItem(localStorageKey);
void this.clerkClient.signOut();
},
};
} catch (e) {
console.error("Error parsing local storage credentials", e);
}
}
if (this.clerkClient.user) {
// Check clerk user metadata for credentials
const storedCredentials = this.clerkClient.user.unsafeMetadata;
if (storedCredentials.jazzAccountID) {
if (!storedCredentials.jazzAccountSecret) {
@@ -36,6 +75,15 @@ export class BrowserClerkAuth implements AuthMethod {
storedCredentials.jazzAccountID as ID<Account>,
secret: storedCredentials.jazzAccountSecret as AgentSecret,
},
saveCredentials: async ({
accountID,
secret,
}: Credentials) => {
saveCredentialsToLocalStorage({
accountID,
secret,
});
},
onSuccess: () => {},
onError: (error: string | Error) => {
this.driver.onError(error);
@@ -45,6 +93,7 @@ export class BrowserClerkAuth implements AuthMethod {
},
};
} else {
// No credentials found, so we need to create new credentials
return {
type: "new",
creationProps: {
@@ -53,14 +102,18 @@ export class BrowserClerkAuth implements AuthMethod {
this.clerkClient.user.username ||
this.clerkClient.user.id,
},
saveCredentials: async (credentials: {
accountID: ID<Account>;
secret: AgentSecret;
}) => {
saveCredentials: async ({
accountID,
secret,
}: Credentials) => {
saveCredentialsToLocalStorage({
accountID,
secret,
});
await this.clerkClient.user?.update({
unsafeMetadata: {
jazzAccountID: credentials.accountID,
jazzAccountSecret: credentials.secret,
jazzAccountID: accountID,
jazzAccountSecret: secret,
},
});
},
@@ -74,6 +127,7 @@ export class BrowserClerkAuth implements AuthMethod {
};
}
} else {
// Clerk user not found, so we can't authenticate
throw new Error("Not signed in");
}
}

View File

@@ -0,0 +1,119 @@
import { AgentSecret } from "cojson";
import { Account, ID } from "jazz-tools";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { BrowserClerkAuth, MinimalClerkClient } from "../index.js";
describe("BrowserClerkAuth", () => {
let mockLocalStorage: { [key: string]: string };
let mockClerkClient: MinimalClerkClient;
let mockDriver: BrowserClerkAuth.Driver;
beforeEach(() => {
// Mock localStorage
mockLocalStorage = {};
global.localStorage = {
getItem: vi.fn((key: string) => mockLocalStorage[key] || null),
setItem: vi.fn((key: string, value: string) => {
mockLocalStorage[key] = value;
}),
removeItem: vi.fn((key: string) => {
delete mockLocalStorage[key];
}),
clear: vi.fn(),
length: 0,
key: vi.fn(),
};
// Mock Clerk client
mockClerkClient = {
user: {
unsafeMetadata: {},
fullName: "Test User",
username: "testuser",
id: "test-id",
update: vi.fn(),
},
signOut: vi.fn(),
};
// Mock driver
mockDriver = {
onError: vi.fn(),
};
});
describe("clerk credentials in localStorage", () => {
it("should get credentials from localStorage when clerk user is not signed in", async () => {
mockLocalStorage["jazz-clerk-auth"] = JSON.stringify({
accountID: "test-account-id",
accountSecret: "test-secret",
});
const auth = new BrowserClerkAuth(mockDriver, {
...mockClerkClient,
user: null,
});
const result = await auth.start();
expect(result.type).toBe("existing");
});
});
describe("clerk credentials not in localStorage", () => {
it("should return new credentials when clerk user signs up", async () => {
const auth = new BrowserClerkAuth(mockDriver, mockClerkClient);
const result = await auth.start();
expect(result.type).toBe("new");
});
it("should return existing credentials when clerk user is signed in", async () => {
mockClerkClient = {
user: {
unsafeMetadata: {
jazzAccountID: "test-account-id",
jazzAccountSecret: "test-secret",
},
fullName: "Test User",
username: "testuser",
id: "test-id",
update: vi.fn(),
},
signOut: vi.fn(),
};
const auth = new BrowserClerkAuth(mockDriver, mockClerkClient);
const result = await auth.start();
expect(result.type).toBe("existing");
});
it("should throw error when not signed in", async () => {
const auth = new BrowserClerkAuth(mockDriver, {
...mockClerkClient,
user: null,
});
await expect(auth.start()).rejects.toThrow("Not signed in");
});
it("should save credentials to localStorage", async () => {
const auth = new BrowserClerkAuth(mockDriver, mockClerkClient);
const result = await auth.start();
if (result.saveCredentials) {
await result.saveCredentials({
accountID: "test-account-id" as ID<Account>,
secret: "test-secret" as AgentSecret,
});
}
expect(mockLocalStorage["jazz-clerk-auth"]).toBeDefined();
});
it("should call clerk signOut when logging out", async () => {
const auth = new BrowserClerkAuth(mockDriver, mockClerkClient);
const result = await auth.start();
result.logOut();
expect(mockClerkClient.signOut).toHaveBeenCalled();
});
});
});

View File

@@ -1,5 +1,37 @@
# jazz-browser-media-images
## 0.8.15
### Patch Changes
- Updated dependencies [cce679b]
- jazz-tools@0.8.15
- jazz-browser@0.8.15
## 0.8.14
### Patch Changes
- Updated dependencies [36273b3]
- jazz-tools@0.8.14
- jazz-browser@0.8.14
## 0.8.13
### Patch Changes
- Updated dependencies [fd011d7]
- Updated dependencies [e0dd006]
- jazz-tools@0.8.13
- jazz-browser@0.8.13
## 0.8.12
### Patch Changes
- jazz-browser@0.8.12
- jazz-tools@0.8.12
## 0.8.11
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "jazz-browser-media-images",
"version": "0.8.11",
"version": "0.8.15",
"type": "module",
"main": "dist/index.js",
"types": "src/index.ts",
@@ -8,8 +8,8 @@
"dependencies": {
"@types/image-blob-reduce": "^4.1.1",
"image-blob-reduce": "^4.1.0",
"jazz-browser": "workspace:0.8.11",
"jazz-tools": "workspace:0.8.11",
"jazz-browser": "workspace:0.8.15",
"jazz-tools": "workspace:0.8.15",
"pica": "^9.0.1",
"typescript": "^5.3.3"
},

View File

@@ -1,5 +1,39 @@
# jazz-browser
## 0.8.15
### Patch Changes
- Updated dependencies [cce679b]
- jazz-tools@0.8.15
## 0.8.14
### Patch Changes
- Updated dependencies [36273b3]
- jazz-tools@0.8.14
## 0.8.13
### Patch Changes
- e0dd006: Improve the WebSocket reconnect logic
- Updated dependencies [fd011d7]
- Updated dependencies [e0dd006]
- jazz-tools@0.8.13
- cojson-transport-ws@0.8.13
## 0.8.12
### Patch Changes
- Updated dependencies [6ed75eb]
- cojson-storage-indexeddb@0.8.12
- cojson@0.8.12
- cojson-transport-ws@0.8.12
- jazz-tools@0.8.12
## 0.8.11
### Patch Changes

View File

@@ -1,16 +1,16 @@
{
"name": "jazz-browser",
"version": "0.8.11",
"version": "0.8.15",
"type": "module",
"main": "dist/index.js",
"types": "src/index.ts",
"license": "MIT",
"dependencies": {
"@scure/bip39": "^1.3.0",
"cojson": "workspace:0.8.11",
"cojson-storage-indexeddb": "workspace:0.8.11",
"cojson-transport-ws": "workspace:0.8.11",
"jazz-tools": "workspace:0.8.11",
"cojson": "workspace:0.8.12",
"cojson-storage-indexeddb": "workspace:0.8.12",
"cojson-transport-ws": "workspace:0.8.13",
"jazz-tools": "workspace:0.8.15",
"typescript": "^5.3.3"
},
"scripts": {

View File

@@ -0,0 +1,69 @@
import { Peer } from "cojson";
import { createWebSocketPeer } from "cojson-transport-ws";
export function createWebSocketPeerWithReconnection(peer: string, reconnectionTimeout: number | undefined, addPeer: (peer: Peer) => void) {
const firstWsPeer = createWebSocketPeer({
websocket: new WebSocket(peer),
id: peer,
role: "server",
onClose: reconnectWebSocket,
});
let shouldTryToReconnect = true;
let currentReconnectionTimeout = reconnectionTimeout || 500;
function onOnline() {
console.log("Online, resetting reconnection timeout");
currentReconnectionTimeout = reconnectionTimeout || 500;
}
window.addEventListener("online", onOnline);
async function reconnectWebSocket() {
if (!shouldTryToReconnect) return;
console.log(
"Websocket disconnected, trying to reconnect in " +
currentReconnectionTimeout +
"ms"
);
currentReconnectionTimeout = Math.min(
currentReconnectionTimeout * 2,
30000
);
await waitForOnline(currentReconnectionTimeout);
if (!shouldTryToReconnect) return;
addPeer(
createWebSocketPeer({
websocket: new WebSocket(peer),
id: peer,
role: "server",
onClose: reconnectWebSocket,
})
);
}
return {
peer: firstWsPeer,
done: () => {
shouldTryToReconnect = false;
window.removeEventListener("online", onOnline);
}
};
}
function waitForOnline(timeout: number) {
return new Promise<void>((resolve) => {
function handleTimeoutOrOnline() {
clearTimeout(timer);
window.removeEventListener('online', handleTimeoutOrOnline);
resolve();
}
const timer = setTimeout(handleTimeoutOrOnline, timeout);
window.addEventListener('online', handleTimeoutOrOnline);
});
}

View File

@@ -16,7 +16,7 @@ import {
import { RawAccountID, LSMStorage } from "cojson";
import { OPFSFilesystem } from "./OPFSFilesystem.js";
import { IDBStorage } from "cojson-storage-indexeddb";
import { createWebSocketPeer } from "cojson-transport-ws";
import { createWebSocketPeerWithReconnection } from "./createWebSocketPeerWithReconnection.js";
export { BrowserDemoAuth } from "./auth/DemoAuth.js";
export { BrowserPasskeyAuth } from "./auth/PasskeyAuth.js";
export { BrowserPassphraseAuth } from "./auth/PassphraseAuth.js";
@@ -64,21 +64,9 @@ export async function createJazzBrowserContext<Acc extends Account>(
): Promise<BrowserContext<Acc> | BrowserGuestContext> {
const crypto = options.crypto || (await WasmCrypto.create());
const firstWsPeer = createWebSocketPeer({
websocket: new WebSocket(options.peer),
id: options.peer + "@" + new Date().toISOString(),
role: "server",
const wsPeer = createWebSocketPeerWithReconnection(options.peer, options.reconnectionTimeout, (peer) => {
node.syncManager.addPeer(peer);
});
let shouldTryToReconnect = true;
let currentReconnectionTimeout = options.reconnectionTimeout || 500;
function onOnline() {
console.log("Online, resetting reconnection timeout");
currentReconnectionTimeout = options.reconnectionTimeout || 500;
}
window.addEventListener("online", onOnline);
const context =
"auth" in options
@@ -93,7 +81,7 @@ export async function createJazzBrowserContext<Acc extends Account>(
// trace: true,
})
: await IDBStorage.asPeer(),
firstWsPeer,
wsPeer.peer,
],
sessionProvider: provideBrowserLockSession,
})
@@ -106,7 +94,7 @@ export async function createJazzBrowserContext<Acc extends Account>(
// trace: true,
})
: await IDBStorage.asPeer(),
firstWsPeer,
wsPeer.peer,
],
});
@@ -115,58 +103,11 @@ export async function createJazzBrowserContext<Acc extends Account>(
? context.account._raw.core.node
: context.agent.node;
async function websocketReconnectLoop() {
while (shouldTryToReconnect) {
if (
Object.keys(node.syncManager.peers).some((peerId) =>
peerId.includes(options.peer),
)
) {
// TODO: this might drain battery, use listeners instead
await new Promise((resolve) => setTimeout(resolve, 100));
} else {
console.log(
"Websocket disconnected, trying to reconnect in " +
currentReconnectionTimeout +
"ms",
);
currentReconnectionTimeout = Math.min(
currentReconnectionTimeout * 2,
30000,
);
await new Promise<void>((resolve) => {
setTimeout(resolve, currentReconnectionTimeout);
window.addEventListener(
"online",
() => {
console.log(
"Online, trying to reconnect immediately",
);
resolve();
},
{ once: true },
);
});
node.syncManager.addPeer(
createWebSocketPeer({
websocket: new WebSocket(options.peer),
id: options.peer + "@" + new Date().toISOString(),
role: "server",
}),
);
}
}
}
void websocketReconnectLoop();
return "account" in context
? {
me: context.account,
done: () => {
shouldTryToReconnect = false;
window.removeEventListener("online", onOnline);
wsPeer.done();
context.done();
},
logOut: () => {
@@ -176,8 +117,7 @@ export async function createJazzBrowserContext<Acc extends Account>(
: {
guest: context.agent,
done: () => {
shouldTryToReconnect = false;
window.removeEventListener("online", onOnline);
wsPeer.done();
context.done();
},
logOut: () => {
@@ -360,3 +300,4 @@ export function consumeInviteLinkFromWindowLocation<V extends CoValue>({
}
});
}

View File

@@ -0,0 +1,148 @@
// @vitest-environment happy-dom
import { describe, test, expect, vi, beforeEach, afterEach } from "vitest";
import { createWebSocketPeer } from "cojson-transport-ws";
import { createWebSocketPeerWithReconnection } from "../createWebSocketPeerWithReconnection.js";
// Mock WebSocket
class MockWebSocket {
addEventListener = vi.fn();
removeEventListener = vi.fn();
close = vi.fn();
readyState = 1;
}
vi.stubGlobal("WebSocket", MockWebSocket);
vi.stubGlobal("window", global);
vi.stubGlobal("navigator", {
onLine: true,
});
vi.mock("cojson-transport-ws", () => ({
createWebSocketPeer: vi.fn().mockImplementation(({ onClose }) => ({
id: "test-peer",
incoming: { push: vi.fn() },
outgoing: { push: vi.fn(), close: vi.fn() },
onClose,
})),
}));
describe("createWebSocketPeerWithReconnection", () => {
beforeEach(() => {
vi.clearAllMocks();
vi.stubGlobal("WebSocket", MockWebSocket);
vi.stubGlobal("removeEventListener", vi.fn());
});
afterEach(() => {
vi.useRealTimers();
});
test("should reset reconnection timeout when coming online", async () => {
vi.useFakeTimers();
const addPeerMock = vi.fn();
const { done } = createWebSocketPeerWithReconnection(
"ws://localhost:8080",
500,
addPeerMock,
);
// Simulate multiple disconnections to increase timeout
const initialPeer =
vi.mocked(createWebSocketPeer).mock.results[0]!.value;
initialPeer.onClose();
await vi.advanceTimersByTimeAsync(1000);
expect(addPeerMock).toHaveBeenCalledTimes(1);
vi.mocked(createWebSocketPeer).mock.results[1]!.value.onClose();
await vi.advanceTimersByTimeAsync(2000);
expect(addPeerMock).toHaveBeenCalledTimes(2);
// Resets the timeout to initial value
window.dispatchEvent(new Event("online"));
// Next reconnection should use initial timeout
vi.mocked(createWebSocketPeer).mock.results[2]!.value.onClose();
await vi.advanceTimersByTimeAsync(1000);
expect(addPeerMock).toHaveBeenCalledTimes(3);
done();
});
test("should wait for online event or timeout before reconnecting", async () => {
vi.useFakeTimers();
const addPeerMock = vi.fn();
const { done } = createWebSocketPeerWithReconnection(
"ws://localhost:8080",
500,
addPeerMock,
);
const initialPeer =
vi.mocked(createWebSocketPeer).mock.results[0]!.value;
// Simulate offline state
vi.stubGlobal("navigator", { onLine: false });
initialPeer.onClose();
// Advance timer but not enough to trigger reconnection
await vi.advanceTimersByTimeAsync(500);
expect(createWebSocketPeer).toHaveBeenCalledTimes(1);
// Simulate coming back online
window.dispatchEvent(new Event("online"));
// Wait for event loop to settle
await Promise.resolve().then();
// Should reconnect immediately after coming online
expect(createWebSocketPeer).toHaveBeenCalledTimes(2);
done();
});
test("should clean up event listeners when done", () => {
const addPeerMock = vi.fn();
const { done } = createWebSocketPeerWithReconnection(
"ws://localhost:8080",
1000,
addPeerMock,
);
done();
expect(window.removeEventListener).toHaveBeenCalledWith(
"online",
expect.any(Function),
);
});
test("should not attempt reconnection after done is called", async () => {
vi.useFakeTimers();
const addPeerMock = vi.fn();
const { done } = createWebSocketPeerWithReconnection(
"ws://localhost:8080",
500,
addPeerMock,
);
const initialPeer =
vi.mocked(createWebSocketPeer).mock.results[0]!.value;
done();
initialPeer.onClose();
await vi.advanceTimersByTimeAsync(1000);
expect(createWebSocketPeer).toHaveBeenCalledTimes(1);
});
});

View File

@@ -1,5 +1,37 @@
# jazz-autosub
## 0.8.15
### Patch Changes
- Updated dependencies [cce679b]
- jazz-tools@0.8.15
## 0.8.14
### Patch Changes
- Updated dependencies [36273b3]
- jazz-tools@0.8.14
## 0.8.13
### Patch Changes
- Updated dependencies [fd011d7]
- Updated dependencies [e0dd006]
- jazz-tools@0.8.13
- cojson-transport-ws@0.8.13
## 0.8.12
### Patch Changes
- Updated dependencies [6ed75eb]
- cojson@0.8.12
- cojson-transport-ws@0.8.12
- jazz-tools@0.8.12
## 0.8.11
### Patch Changes

View File

@@ -5,11 +5,11 @@
"types": "src/index.ts",
"type": "module",
"license": "MIT",
"version": "0.8.11",
"version": "0.8.15",
"dependencies": {
"cojson": "workspace:0.8.11",
"cojson-transport-ws": "workspace:0.8.11",
"jazz-tools": "workspace:0.8.11",
"cojson": "workspace:0.8.12",
"cojson-transport-ws": "workspace:0.8.13",
"jazz-tools": "workspace:0.8.15",
"ws": "^8.14.2"
},
"devDependencies": {

View File

@@ -1,5 +1,45 @@
# jazz-browser-media-images
## 0.8.15
### Patch Changes
- 221c58f: Store clerk credentials in localStorage, and use if available
- Updated dependencies [cce679b]
- Updated dependencies [221c58f]
- jazz-tools@0.8.15
- jazz-browser-auth-clerk@0.8.15
- jazz-react@0.8.15
## 0.8.14
### Patch Changes
- Updated dependencies [36273b3]
- jazz-tools@0.8.14
- jazz-browser-auth-clerk@0.8.14
- jazz-react@0.8.14
## 0.8.13
### Patch Changes
- Updated dependencies [fd011d7]
- jazz-tools@0.8.13
- jazz-browser-auth-clerk@0.8.13
- jazz-react@0.8.13
## 0.8.12
### Patch Changes
- Updated dependencies [6ed75eb]
- Updated dependencies [3cc6aee]
- cojson@0.8.12
- jazz-react@0.8.12
- jazz-browser-auth-clerk@0.8.12
- jazz-tools@0.8.12
## 0.8.11
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "jazz-react-auth-clerk",
"version": "0.8.11",
"version": "0.8.15",
"type": "module",
"main": "dist/index.js",
"types": "src/index.tsx",
@@ -10,10 +10,10 @@
"*.{js,jsx,mdx,json}": "prettier --write"
},
"dependencies": {
"cojson": "workspace:0.8.11",
"jazz-browser-auth-clerk": "workspace:0.8.11",
"jazz-react": "workspace:0.8.11",
"jazz-tools": "workspace:0.8.11"
"cojson": "workspace:0.8.12",
"jazz-browser-auth-clerk": "workspace:0.8.15",
"jazz-react": "workspace:0.8.15",
"jazz-tools": "workspace:0.8.15"
},
"peerDependencies": {
"react": "^18.2.0"

View File

@@ -7,22 +7,18 @@ export function useJazzClerkAuth(clerk: MinimalClerkClient & {
const [state, setState] = useState<{ errors: string[] }>({ errors: [] });
const authMethod = useMemo(() => {
if (clerk.user) {
return new BrowserClerkAuth(
{
onError: (error) => {
void clerk.signOut();
setState((state) => ({
...state,
errors: [...state.errors, error.toString()],
}));
},
return new BrowserClerkAuth(
{
onError: (error) => {
void clerk.signOut();
setState((state) => ({
...state,
errors: [...state.errors, error.toString()],
}));
},
clerk,
);
} else {
return undefined;
}
},
clerk,
);
}, [clerk.user]);
return [authMethod, state] as const;

View File

@@ -1,5 +1,32 @@
# jazz-browser-media-images
## 0.8.11
### Patch Changes
- Updated dependencies [cce679b]
- jazz-tools@0.8.15
## 0.8.10
### Patch Changes
- Updated dependencies [36273b3]
- jazz-tools@0.8.14
## 0.8.9
### Patch Changes
- Updated dependencies [fd011d7]
- jazz-tools@0.8.13
## 0.8.8
### Patch Changes
- jazz-tools@0.8.12
## 0.8.7
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "jazz-react-native-media-images",
"version": "0.8.7",
"version": "0.8.11",
"type": "module",
"main": "dist/index.js",
"types": "src/index.ts",

View File

@@ -1,5 +1,37 @@
# jazz-browser
## 0.8.15
### Patch Changes
- Updated dependencies [cce679b]
- jazz-tools@0.8.15
## 0.8.14
### Patch Changes
- Updated dependencies [36273b3]
- jazz-tools@0.8.14
## 0.8.13
### Patch Changes
- Updated dependencies [fd011d7]
- Updated dependencies [e0dd006]
- jazz-tools@0.8.13
- cojson-transport-ws@0.8.13
## 0.8.12
### Patch Changes
- Updated dependencies [6ed75eb]
- cojson@0.8.12
- cojson-transport-ws@0.8.12
- jazz-tools@0.8.12
## 0.8.11
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "jazz-react-native",
"version": "0.8.11",
"version": "0.8.15",
"type": "module",
"main": "./dist/index.js",
"module": "./dist/index.js",

View File

@@ -1,5 +1,40 @@
# jazz-react
## 0.8.15
### Patch Changes
- Updated dependencies [cce679b]
- jazz-tools@0.8.15
- jazz-browser@0.8.15
## 0.8.14
### Patch Changes
- Updated dependencies [36273b3]
- jazz-tools@0.8.14
- jazz-browser@0.8.14
## 0.8.13
### Patch Changes
- Updated dependencies [fd011d7]
- Updated dependencies [e0dd006]
- jazz-tools@0.8.13
- jazz-browser@0.8.13
## 0.8.12
### Patch Changes
- 3cc6aee: Temporary fix to prevent double creation of Jazz context in React StrictMode
- Updated dependencies [6ed75eb]
- cojson@0.8.12
- jazz-browser@0.8.12
- jazz-tools@0.8.12
## 0.8.11
### Patch Changes

View File

@@ -1,15 +1,15 @@
{
"name": "jazz-react",
"version": "0.8.11",
"version": "0.8.15",
"type": "module",
"main": "dist/index.js",
"types": "src/index.ts",
"license": "MIT",
"dependencies": {
"@scure/bip39": "^1.3.0",
"cojson": "workspace:0.8.11",
"jazz-browser": "workspace:0.8.11",
"jazz-tools": "workspace:0.8.11",
"cojson": "workspace:0.8.12",
"jazz-browser": "workspace:0.8.15",
"jazz-tools": "workspace:0.8.15",
"typescript": "^5.3.3"
},
"devDependencies": {

View File

@@ -1,4 +1,4 @@
import React, { useEffect, useState } from "react";
import React, { useEffect, useRef, useState } from "react";
import {
BrowserContext,
BrowserGuestContext,
@@ -46,34 +46,74 @@ export function createJazzReactApp<Acc extends Account>({
const [sessionCount, setSessionCount] = useState(0);
const effectExecuted = useRef(false);
effectExecuted.current = false;
useEffect(() => {
const promiseWithDoneCallback = createJazzBrowserContext<Acc>(
auth === "guest"
? {
peer,
storage,
}
: {
AccountSchema,
auth: auth,
peer,
storage,
},
).then((context) => {
// Avoid double execution of the effect in development mode for easier debugging.
if (process.env.NODE_ENV === "development") {
if (effectExecuted.current) {
return;
}
effectExecuted.current = true;
// In development mode we don't return a cleanup function because otherwise
// the double effect execution would mark the context as done immediately.
//
// So we mark it as done in the subsequent execution.
const previousContext = ctx;
if (previousContext) {
previousContext.done();
}
}
async function createContext() {
const currentContext = await createJazzBrowserContext<Acc>(
auth === "guest"
? {
peer,
storage,
}
: {
AccountSchema,
auth,
peer,
storage,
},
);
const logOut = () => {
currentContext.logOut();
setCtx(undefined);
setSessionCount(sessionCount + 1);
if (process.env.NODE_ENV === "development") {
// In development mode we don't return a cleanup function
// so we mark the context as done here.
currentContext.done();
}
};
setCtx({
...context,
logOut: () => {
context.logOut();
setCtx(undefined);
setSessionCount(sessionCount + 1);
},
...currentContext,
logOut,
});
return context.done;
});
return currentContext;
}
const promise = createContext();
// In development mode we don't return a cleanup function because otherwise
// the double effect execution would mark the context as done immediately.
if (process.env.NODE_ENV === "development") {
return;
}
return () => {
void promiseWithDoneCallback.then((done) => done());
};
void promise.then((context) => context.done());
}
}, [AccountSchema, auth, peer, storage, sessionCount]);
return (

View File

@@ -1,5 +1,38 @@
# jazz-run
## 0.8.15
### Patch Changes
- Updated dependencies [cce679b]
- jazz-tools@0.8.15
## 0.8.14
### Patch Changes
- Updated dependencies [36273b3]
- jazz-tools@0.8.14
## 0.8.13
### Patch Changes
- Updated dependencies [fd011d7]
- Updated dependencies [e0dd006]
- jazz-tools@0.8.13
- cojson-transport-ws@0.8.13
## 0.8.12
### Patch Changes
- Updated dependencies [6ed75eb]
- cojson-storage-sqlite@0.8.12
- cojson@0.8.12
- cojson-transport-ws@0.8.12
- jazz-tools@0.8.12
## 0.8.11
### Patch Changes

View File

@@ -3,7 +3,7 @@
"bin": "./dist/index.js",
"type": "module",
"license": "MIT",
"version": "0.8.11",
"version": "0.8.15",
"scripts": {
"lint": "eslint . --ext ts,tsx",
"format": "prettier --write './src/**/*.{ts,tsx}'",
@@ -17,11 +17,11 @@
"@effect/printer-ansi": "^0.34.5",
"@effect/schema": "^0.71.1",
"@effect/typeclass": "^0.25.5",
"cojson": "workspace:0.8.11",
"cojson-storage-sqlite": "workspace:0.8.11",
"cojson-transport-ws": "workspace:0.8.11",
"cojson": "workspace:0.8.12",
"cojson-storage-sqlite": "workspace:0.8.12",
"cojson-transport-ws": "workspace:0.8.13",
"effect": "^3.6.5",
"jazz-tools": "workspace:0.8.11",
"jazz-tools": "workspace:0.8.15",
"ws": "^8.14.2"
},
"devDependencies": {

View File

@@ -1,125 +0,0 @@
import { Command, Options } from "@effect/cli";
import { Console, Effect } from "effect";
import { createWebSocketPeer } from "cojson-transport-ws";
import { WebSocket } from "ws";
import {
Account,
Peer,
WasmCrypto,
createJazzContext,
isControlledAccount,
} from "jazz-tools";
import { fixedCredentialsAuth, randomSessionProvider } from "jazz-tools";
import { CoValueCore } from "cojson";
const name = Options.text("name").pipe(Options.withAlias("n"));
const peer = Options.text("peer")
.pipe(Options.withAlias("p"))
.pipe(Options.withDefault("wss://cloud.jazz.tools"));
const accountCreate = Command.make(
"create",
{ name, peer },
({ name, peer: peerAddr }) => {
return Effect.gen(function* () {
const crypto = yield* Effect.promise(() => WasmCrypto.create());
const peer = createWebSocketPeer({
id: "upstream",
websocket: new WebSocket(peerAddr),
role: "server",
});
const account: Account = yield* Effect.promise(async () =>
Account.create({
creationProps: { name },
peersToLoadFrom: [peer],
crypto,
}),
);
if (!isControlledAccount(account)) {
throw new Error("account is not a controlled account");
}
const accountCoValue = account._raw.core;
const accountProfileCoValue = account.profile!._raw.core;
const syncManager = account._raw.core.node.syncManager;
yield* Effect.promise(() =>
syncManager.syncCoValue(accountCoValue),
);
yield* Effect.promise(() =>
syncManager.syncCoValue(accountProfileCoValue),
);
yield* Effect.promise(() => Promise.all([
waitForSync(account, peer, accountCoValue),
waitForSync(account, peer, accountProfileCoValue),
]));
// Spawn a second peer to double check that the account is fully synced
const peer2 = createWebSocketPeer({
id: "upstream2",
websocket: new WebSocket(peerAddr),
role: "server",
});
yield* Effect.promise(async () =>
createJazzContext({
auth: fixedCredentialsAuth({
accountID: account.id,
secret: account._raw.agentSecret,
}),
sessionProvider: randomSessionProvider,
peersToLoadFrom: [peer2],
crypto,
}),
);
yield* Console.log(`# Credentials for Jazz account "${name}":
JAZZ_WORKER_ACCOUNT=${account.id}
JAZZ_WORKER_SECRET=${account._raw.agentSecret}
`);
});
},
);
const accountBase = Command.make("account");
export const account = accountBase.pipe(Command.withSubcommands([accountCreate]));
function waitForSync(account: Account, peer: Peer, coValue: CoValueCore) {
const syncManager = account._raw.core.node.syncManager;
const peerState = syncManager.peers[peer.id];
return new Promise((resolve) => {
const unsubscribe = peerState?.optimisticKnownStates.subscribe((id, peerKnownState) => {
if (id !== coValue.id) return;
const knownState = coValue.knownState();
const synced = isEqualSession(knownState.sessions, peerKnownState.sessions);
if (synced) {
resolve(true);
unsubscribe?.();
}
});
});
}
function isEqualSession(a: Record<string, number>, b: Record<string, number>) {
const keysA = Object.keys(a);
const keysB = Object.keys(b);
if (keysA.length !== keysB.length) {
return false;
}
for (const sessionId of keysA) {
if (a[sessionId] !== b[sessionId]) {
return false;
}
}
return true;
}

Some files were not shown because too many files have changed in this diff Show More