Compare commits
57 Commits
cojson-sto
...
jazz-tools
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
61b346a948 | ||
|
|
c2dfe6a0dd | ||
|
|
81e8910519 | ||
|
|
cce679b031 | ||
|
|
c2dbf45f26 | ||
|
|
04d3a8c677 | ||
|
|
91019f3f85 | ||
|
|
06278c3d97 | ||
|
|
36273b3114 | ||
|
|
51a0a9f23f | ||
|
|
fc24a65264 | ||
|
|
fa0b9003dc | ||
|
|
8a0da655e8 | ||
|
|
fff11daca7 | ||
|
|
fd011d76f4 | ||
|
|
0681a8301e | ||
|
|
221c58f1f7 | ||
|
|
ebca75bf70 | ||
|
|
b5e516f974 | ||
|
|
8fc5a49ca8 | ||
|
|
00054aa9a5 | ||
|
|
6f3c98f704 | ||
|
|
8823957c6e | ||
|
|
47ec0cd8fe | ||
|
|
685031c39c | ||
|
|
e4fd89dbbb | ||
|
|
e0dd0065fb | ||
|
|
98d7d402eb | ||
|
|
57deec451a | ||
|
|
bc8ea23e6d | ||
|
|
d2ad2db930 | ||
|
|
44497fd058 | ||
|
|
a18c187818 | ||
|
|
f49949cc25 | ||
|
|
3cc6aee7e6 | ||
|
|
c80763100e | ||
|
|
6ed75ebb35 | ||
|
|
30d7734124 | ||
|
|
bca8b44189 | ||
|
|
36a069c90f | ||
|
|
757b37e8ed | ||
|
|
46b1163058 | ||
|
|
8c261b0409 | ||
|
|
fdacde57dd | ||
|
|
817dd7dde1 | ||
|
|
6279dd1467 | ||
|
|
abd4b94392 | ||
|
|
68c6ee77c0 | ||
|
|
d2b2812428 | ||
|
|
fe214cc3c2 | ||
|
|
2cca9506ad | ||
|
|
17e69aff8f | ||
|
|
c45331e645 | ||
|
|
4337001526 | ||
|
|
9eef1ec031 | ||
|
|
9d31bbc3aa | ||
|
|
911add3dea |
@@ -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
|
||||
|
||||
@@ -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"
|
||||
},
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@jazz-e2e/covalues",
|
||||
"private": true,
|
||||
"version": "0.0.88",
|
||||
"version": "0.0.92",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "chat-rn",
|
||||
"version": "1.0.6",
|
||||
"version": "1.0.10",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"build": "expo export -p ios",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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>;
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
@@ -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"
|
||||
/>
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
.
|
||||
|
||||
9
homepage/homepage/app/docs/(docs)/coming-soon/page.mdx
Normal file
9
homepage/homepage/app/docs/(docs)/coming-soon/page.mdx
Normal 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.
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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">
|
||||
|
||||
75
homepage/homepage/app/docs/project-setup/next/next.mdx
Normal file
75
homepage/homepage/app/docs/project-setup/next/next.mdx
Normal 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
|
||||
35
homepage/homepage/app/docs/project-setup/next/page.tsx
Normal file
35
homepage/homepage/app/docs/project-setup/next/page.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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 doesn’t 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>
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -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">
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 }) => (
|
||||
|
||||
@@ -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 (
|
||||
|
||||
@@ -14,6 +14,8 @@ export function TableOfContents({
|
||||
items: NavItem[];
|
||||
className?: string;
|
||||
}) {
|
||||
if (!items.length) return null;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={clsx(
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -48,7 +48,7 @@ export function JazzNav() {
|
||||
icon: <SiTwitter className="w-5" />,
|
||||
},
|
||||
]}
|
||||
docNav={<DocNav className="block h-auto"/>}
|
||||
docNav={<DocNav className="block h-auto" />}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
@@ -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>"
|
||||
);
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -94,7 +94,7 @@ export class IDBStorage {
|
||||
"storage",
|
||||
{
|
||||
peer1role: "client",
|
||||
peer2role: "server",
|
||||
peer2role: "storage",
|
||||
trace,
|
||||
crashOnClose: true,
|
||||
},
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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>;
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# cojson
|
||||
|
||||
## 0.8.12
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 6ed75eb: Introduce "storage" peer role
|
||||
|
||||
## 0.8.11
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -560,7 +560,7 @@ export class LSMStorage<WH, RH, FS extends FileSystem<WH, RH>> {
|
||||
"storage",
|
||||
{
|
||||
peer1role: "client",
|
||||
peer2role: "server",
|
||||
peer2role: "storage",
|
||||
trace,
|
||||
crashOnClose: true,
|
||||
},
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
},
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
@@ -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>({
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
@@ -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
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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 (
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user