Compare commits

...

25 Commits

Author SHA1 Message Date
Guido D'Orsi
22f6db8141 test: cover CoMap public methods with tests 2024-12-07 19:40:44 +01:00
Guido D'Orsi
3d9f12e9ea chore: changeset 2024-12-07 19:28:12 +01:00
Guido D'Orsi
68620a3df9 perf(CoMap): optimize the atTime processing 2024-12-07 19:07:52 +01:00
Anselm Eickhoff
1767f024d9 Merge pull request #964 from garden-co/jazz-567-speed-up-latency-map-rendering
Fix caching for latency map
2024-12-07 14:06:46 +00:00
Anselm
c55924a04a Fix caching 2024-12-07 13:56:48 +00:00
Anselm Eickhoff
31e00a96ae Merge pull request #963 from garden-co/jazz-567-speed-up-latency-map-rendering
Speed up latency map rendering
2024-12-07 12:59:44 +00:00
Anselm
7bb834f399 Lint & format 2024-12-07 12:54:37 +00:00
pax
667f36e1cf Merge pull request #957 from garden-co/rn-examples-new-arch
fix: upgraded RN examples to Expo SDK 52 with new arch
2024-12-07 14:50:50 +02:00
pax-k
07669923ad Merge branch 'main' into rn-examples-new-arch 2024-12-07 14:48:28 +02:00
Anselm
9082a099ee Merge branch 'main' into jazz-567-speed-up-latency-map-rendering 2024-12-07 12:31:58 +00:00
Anselm
aba059db28 Try strongarming vercel into using the right pnpm version 2024-12-07 12:23:15 +00:00
Anselm
ae6b9c8dd2 Speed up latency map rendering 2024-12-06 18:59:21 +00:00
pax-k
7b9f96bf1a chore: pnpm lock 2024-12-06 18:52:25 +02:00
pax-k
8df49546fe Merge branch 'main' into rn-examples-new-arch 2024-12-06 18:52:08 +02:00
pax-k
bb4460f422 fix: upgraded RN examples to Expo SDK 52 with new arch 2024-12-06 18:51:35 +02:00
Anselm Eickhoff
2099099afc Merge pull request #956 from nikitavoloboev/patch-1
fix 404 urls in docs
2024-12-06 16:47:19 +00:00
Nikita
06b0758d7c fix urls 2024-12-06 17:43:06 +01:00
Nikita
6135250e57 fix 404 url 2024-12-06 17:38:32 +01:00
Anselm Eickhoff
aacd03bbdd Merge pull request #945 from garden-co/changeset-release/main
Version Packages
2024-12-06 15:39:02 +00:00
github-actions[bot]
ca7f250d47 Version Packages 2024-12-06 14:46:52 +00:00
pax
83ad506b94 Merge pull request #954 from garden-co/jazz-react-native-auth-clerk
jazz-react-native-auth-clerk package
2024-12-06 16:45:42 +02:00
pax-k
e9751f5b69 Merge branch 'main' into jazz-react-native-auth-clerk 2024-12-06 16:36:14 +02:00
pax-k
c84764acd5 chore: changeset 2024-12-06 16:30:35 +02:00
pax-k
c2a805bffa chore: added expo-font 2024-12-06 16:27:27 +02:00
pax-k
8728dde42b feat: added package jazz-react-native-auth-clerk 2024-12-06 15:13:45 +02:00
103 changed files with 3692 additions and 1657 deletions

View File

@@ -1,6 +0,0 @@
---
"cojson": patch
"jazz-tools": patch
---
Optimise large record-like CoMaps for access of latest value

View File

@@ -0,0 +1,5 @@
---
"cojson": patch
---
Optimize the atTime processing on CoMap

View File

@@ -1,7 +0,0 @@
---
"cojson-storage-indexeddb": patch
"cojson-storage-sqlite": patch
"cojson-storage": patch
---
Refactor the SQLite and IndexedDB storage packages to extract common synchronization functionality into newly created cojson-storage package.

View File

@@ -1,5 +1,14 @@
# jazz-example-book-shelf
## 0.1.28
### Patch Changes
- Updated dependencies [441fe27]
- jazz-tools@0.8.36
- jazz-react@0.8.36
- jazz-browser-media-images@0.8.36
## 0.1.27
### Patch Changes

View File

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

View File

@@ -1 +1 @@
EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_ZXZpZGVudC1kYW5lLTg5LmNsZXJrLmFjY291bnRzLmRldiQ
EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_ZXZpZGVudC1kYW5lLTg5LmNsZXJrLmFjY291bnRzLmRldiQ

View File

@@ -1,5 +1,17 @@
# chat-rn-clerk
## 1.0.28
### Patch Changes
- c84764a: feat: added jazz-react-native-auth-clerk package
- Updated dependencies [c84764a]
- Updated dependencies [441fe27]
- jazz-react-native-auth-clerk@0.8.36
- jazz-react-native@0.8.36
- jazz-tools@0.8.36
- jazz-react-native-media-images@0.8.27
## 1.0.27
### Patch Changes

View File

@@ -35,7 +35,9 @@
}
}
],
"expo-secure-store"
"expo-secure-store",
"expo-font",
"expo-router"
],
"extra": {
"eas": {

View File

@@ -4,7 +4,7 @@ import { Platform } from "react-native";
export interface TokenCache {
getToken: (key: string) => Promise<string | undefined | null>;
saveToken: (key: string, token: string) => Promise<void>;
clearToken?: (key: string) => void;
clearToken: (key: string) => void;
}
const createTokenCache = (): TokenCache => {
@@ -27,6 +27,9 @@ const createTokenCache = (): TokenCache => {
saveToken: (key: string, token: string) => {
return SecureStore.setItemAsync(key, token);
},
clearToken: (key: string) => {
return SecureStore.deleteItemAsync(key);
},
};
};

View File

@@ -19,7 +19,10 @@ config.resolver.nodeModulesPaths = [
];
config.resolver.sourceExts = ["mjs", "js", "json", "ts", "tsx"];
config.resolver.unstable_enablePackageExports = true;
config.resolver.requireCycleIgnorePatterns = [/(^|\/|\\)node_modules($|\/|\\)/];
config.resolver.requireCycleIgnorePatterns = [
/(^|\/|\\)node_modules($|\/|\\)/,
/(^|\/|\\)packages($|\/|\\)/,
];
// Use turborepo to restore the cache when possible
config.cacheStores = [

View File

@@ -1,7 +1,7 @@
{
"name": "chat-rn-clerk",
"main": "index.js",
"version": "1.0.27",
"version": "1.0.28",
"scripts": {
"build": "expo export -p ios",
"start": "expo start",
@@ -17,59 +17,58 @@
},
"dependencies": {
"@azure/core-asynciterator-polyfill": "^1.0.2",
"@bam.tech/react-native-image-resizer": "^3.0.10",
"@bam.tech/react-native-image-resizer": "^3.0.11",
"@clerk/clerk-expo": "^2.2.21",
"@expo/vector-icons": "^14.0.2",
"@react-native-community/netinfo": "^11.3.1",
"@react-navigation/native": "^6.1.18",
"@react-navigation/native-stack": "^6.11.0",
"@react-native-community/netinfo": "^11.4.1",
"@react-navigation/native": "^7.0.13",
"@react-navigation/native-stack": "^7.1.14",
"base-64": "^1.0.0",
"buffer": "^6.0.3",
"clsx": "^2.0.0",
"expo": "~51.0.37",
"expo-build-properties": "~0.12.5",
"expo-clipboard": "~6.0.3",
"expo-constants": "~16.0.2",
"expo-crypto": "~13.0.2",
"expo-dev-client": "~4.0.28",
"expo-file-system": "^17.0.1",
"expo-font": "~12.0.4",
"expo-linking": "~6.3.1",
"expo-router": "~3.5.23",
"expo-secure-store": "~13.0.2",
"expo-splash-screen": "~0.27.5",
"expo-status-bar": "~1.12.1",
"expo-system-ui": "~3.0.7",
"expo-web-browser": "~13.0.3",
"jazz-react-auth-clerk": "workspace:*",
"expo": "^52.0.0",
"expo-build-properties": "~0.13.1",
"expo-clipboard": "~7.0.0",
"expo-constants": "~17.0.3",
"expo-crypto": "~14.0.1",
"expo-dev-client": "~5.0.5",
"expo-file-system": "^18.0.4",
"expo-font": "~13.0.1",
"expo-linking": "~7.0.3",
"expo-router": "~4.0.11",
"expo-secure-store": "~14.0.0",
"expo-splash-screen": "~0.29.16",
"expo-status-bar": "~2.0.0",
"expo-system-ui": "~4.0.5",
"expo-web-browser": "~14.0.1",
"jazz-react-native": "workspace:*",
"jazz-react-native-auth-clerk": "workspace:*",
"jazz-react-native-media-images": "workspace:*",
"jazz-tools": "workspace:*",
"nativewind": "^2.0.11",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-native": "~0.74.5",
"react-native": "~0.76.3",
"react-native-fetch-api": "^3.0.0",
"react-native-gesture-handler": "~2.16.1",
"react-native-gesture-handler": "~2.20.2",
"react-native-get-random-values": "^1.11.0",
"react-native-mmkv": "3.0.1",
"react-native-polyfill-globals": "^3.1.0",
"react-native-quick-base64": "^2.1.2",
"react-native-reanimated": "~3.10.1",
"react-native-safe-area-context": "4.10.5",
"react-native-screens": "3.31.1",
"react-native-reanimated": "~3.16.3",
"react-native-safe-area-context": "4.12.0",
"react-native-screens": "4.1.0",
"react-native-url-polyfill": "^2.0.0",
"react-native-web": "~0.19.10",
"react-native-web": "~0.19.13",
"text-encoding": "^0.7.0",
"web-streams-polyfill": "^3.2.1"
},
"devDependencies": {
"@babel/core": "^7.20.0",
"@types/jest": "^29.5.3",
"@types/react": "^18.2.19",
"@types/react": "^18.3.12",
"@types/react-test-renderer": "^18.0.7",
"jest": "^29.2.1",
"jest-expo": "~51.0.3",
"jest-expo": "~52.0.2",
"react-test-renderer": "18.2.0",
"tailwindcss": "3.3.2",
"typescript": "^5.3.3"

View File

@@ -1,5 +1,5 @@
import { useClerk, useUser } from "@clerk/clerk-expo";
import { useJazzClerkAuth } from "jazz-react-auth-clerk";
import { useJazzClerkAuth } from "jazz-react-native-auth-clerk";
import React, {
createContext,
PropsWithChildren,
@@ -8,7 +8,7 @@ import React, {
useState,
} from "react";
import { Text, View } from "react-native";
import { Jazz } from "./jazz";
import { Jazz, kvStore } from "./jazz";
const AuthContext = createContext<{
isAuthenticated: boolean;
@@ -25,7 +25,7 @@ export function useAuth() {
export function JazzAndAuth({ children }: PropsWithChildren) {
const { isSignedIn, isLoaded: isClerkLoaded } = useUser();
const clerk = useClerk();
const [auth, state] = useJazzClerkAuth(clerk);
const [auth, state] = useJazzClerkAuth(clerk, kvStore);
const [isAuthenticated, setIsAuthenticated] = useState(false);
useEffect(() => {
@@ -46,7 +46,7 @@ export function JazzAndAuth({ children }: PropsWithChildren) {
<Text style={{ color: "red" }}>{error}</Text>
</View>
))}
{auth ? (
{auth && clerk.user ? (
<Jazz.Provider
auth={auth}
peer="wss://cloud.jazz.tools/?key=chat-rn-clerk-example-jazz@garden.co"

View File

@@ -1,4 +1,4 @@
import { createJazzRNApp } from "jazz-react-native";
export const Jazz = createJazzRNApp();
export const { useAccount, useCoState, useAcceptInvite } = Jazz;
export const { useAccount, useCoState, useAcceptInvite, kvStore } = Jazz;

View File

@@ -1,5 +1,14 @@
# chat-rn
## 1.0.26
### Patch Changes
- Updated dependencies [c84764a]
- Updated dependencies [441fe27]
- jazz-react-native@0.8.36
- jazz-tools@0.8.36
## 1.0.25
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "chat-rn",
"version": "1.0.25",
"version": "1.0.26",
"main": "index.js",
"scripts": {
"build": "expo export -p ios",
@@ -13,39 +13,38 @@
},
"dependencies": {
"@azure/core-asynciterator-polyfill": "^1.0.2",
"@react-native-community/netinfo": "^11.3.1",
"@react-native-community/netinfo": "^11.4.1",
"@react-navigation/native": "^6.1.18",
"@react-navigation/native-stack": "^6.11.0",
"base-64": "^1.0.0",
"clsx": "^2.0.0",
"expo": "~51.0.37",
"expo-build-properties": "~0.12.5",
"expo-clipboard": "~6.0.3",
"expo-constants": "~16.0.2",
"expo-crypto": "~13.0.2",
"expo-dev-client": "~4.0.28",
"expo-linking": "~6.3.1",
"expo-secure-store": "~13.0.2",
"expo-status-bar": "~1.12.1",
"expo-web-browser": "~13.0.3",
"expo": "^52.0.0",
"expo-build-properties": "~0.13.1",
"expo-clipboard": "~7.0.0",
"expo-constants": "~17.0.3",
"expo-crypto": "~14.0.1",
"expo-dev-client": "~5.0.5",
"expo-linking": "~7.0.3",
"expo-secure-store": "~14.0.0",
"expo-status-bar": "~2.0.0",
"expo-web-browser": "~14.0.1",
"jazz-react-native": "workspace:*",
"jazz-tools": "workspace:*",
"nativewind": "^2.0.11",
"react": "^18.2.0",
"react-native": "~0.74.5",
"react": "18.2.0",
"react-native": "~0.76.3",
"react-native-fetch-api": "^3.0.0",
"react-native-get-random-values": "^1.11.0",
"react-native-mmkv": "3.0.1",
"react-native-polyfill-globals": "^3.1.0",
"react-native-safe-area-context": "4.10.5",
"react-native-screens": "3.31.1",
"react-native-safe-area-context": "4.12.0",
"react-native-screens": "4.1.0",
"react-native-url-polyfill": "^2.0.0",
"text-encoding": "^0.7.0",
"web-streams-polyfill": "^3.2.1"
},
"devDependencies": {
"@babel/core": "^7.20.0",
"@types/react": "^18.2.19",
"@types/react": "^18.3.12",
"tailwindcss": "3.3.2",
"typescript": "^5.3.3"
},

View File

@@ -1,5 +1,14 @@
# chat-vue
## 0.0.19
### Patch Changes
- Updated dependencies [441fe27]
- jazz-tools@0.8.36
- jazz-browser@0.8.36
- jazz-vue@0.8.24
## 0.0.18
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "chat-vue",
"version": "0.0.18",
"version": "0.0.19",
"private": true,
"type": "module",
"scripts": {

View File

@@ -1,5 +1,14 @@
# jazz-example-chat
## 0.0.114
### Patch Changes
- Updated dependencies [441fe27]
- cojson@0.8.36
- jazz-tools@0.8.36
- jazz-react@0.8.36
## 0.0.113
### Patch Changes

View File

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

View File

@@ -1,5 +1,14 @@
# minimal-auth-clerk
## 0.0.13
### Patch Changes
- Updated dependencies [441fe27]
- jazz-tools@0.8.36
- jazz-react@0.8.36
- jazz-react-auth-clerk@0.8.36
## 0.0.12
### Patch Changes

View File

@@ -1,7 +1,7 @@
{
"name": "clerk",
"private": true,
"version": "0.0.12",
"version": "0.0.13",
"type": "module",
"scripts": {
"dev": "vite",
@@ -14,7 +14,7 @@
"@clerk/clerk-react": "^5.4.1",
"jazz-tools": "workspace:*",
"jazz-react": "workspace:*",
"jazz-react-auth-clerk": "workspace:0.8.35",
"jazz-react-auth-clerk": "workspace:0.8.36",
"react": "^18.3.1",
"react-dom": "^18.3.1"
},

View File

@@ -1,5 +1,14 @@
# image-upload
## 0.0.11
### Patch Changes
- Updated dependencies [441fe27]
- jazz-tools@0.8.36
- jazz-react@0.8.36
- jazz-browser-media-images@0.8.36
## 0.0.10
### Patch Changes

View File

@@ -1,7 +1,7 @@
{
"name": "image-upload",
"private": true,
"version": "0.0.10",
"version": "0.0.11",
"type": "module",
"scripts": {
"dev": "vite",

View File

@@ -1,5 +1,13 @@
# jazz-example-inspector
## 0.0.83
### Patch Changes
- Updated dependencies [441fe27]
- cojson@0.8.36
- cojson-transport-ws@0.8.36
## 0.0.82
### Patch Changes

View File

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

View File

@@ -1,5 +1,13 @@
# jazz-example-musicplayer
## 0.0.34
### Patch Changes
- Updated dependencies [441fe27]
- jazz-tools@0.8.36
- jazz-react@0.8.36
## 0.0.33
### Patch Changes

View File

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

View File

@@ -1,5 +1,14 @@
# jazz-example-onboarding
## 0.0.15
### Patch Changes
- Updated dependencies [441fe27]
- jazz-tools@0.8.36
- jazz-react@0.8.36
- jazz-browser-media-images@0.8.36
## 0.0.14
### Patch Changes

View File

@@ -1,7 +1,7 @@
{
"name": "jazz-example-onboarding",
"private": true,
"version": "0.0.14",
"version": "0.0.15",
"type": "module",
"scripts": {
"dev": "vite",

View File

@@ -1,5 +1,11 @@
# passkey-svelte
## 0.0.3
### Patch Changes
- jazz-svelte@0.0.3
## 0.0.2
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "passkey-svelte",
"version": "0.0.2",
"version": "0.0.3",
"type": "module",
"scripts": {
"dev": "vite dev",

View File

@@ -1,5 +1,13 @@
# minimal-auth-passkey
## 0.0.12
### Patch Changes
- Updated dependencies [441fe27]
- jazz-tools@0.8.36
- jazz-react@0.8.36
## 0.0.11
### Patch Changes

View File

@@ -1,7 +1,7 @@
{
"name": "passkey",
"private": true,
"version": "0.0.11",
"version": "0.0.12",
"type": "module",
"scripts": {
"dev": "vite",

View File

@@ -1,5 +1,13 @@
# jazz-password-manager
## 0.0.33
### Patch Changes
- Updated dependencies [441fe27]
- jazz-tools@0.8.36
- jazz-react@0.8.36
## 0.0.32
### Patch Changes

View File

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

View File

@@ -1,5 +1,14 @@
# jazz-example-pets
## 0.0.131
### Patch Changes
- Updated dependencies [441fe27]
- jazz-tools@0.8.36
- jazz-react@0.8.36
- jazz-browser-media-images@0.8.36
## 0.0.130
### Patch Changes

View File

@@ -1,7 +1,7 @@
{
"name": "jazz-example-pets",
"private": true,
"version": "0.0.130",
"version": "0.0.131",
"type": "module",
"scripts": {
"dev": "vite",
@@ -19,9 +19,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.35",
"jazz-react": "workspace:0.8.35",
"jazz-tools": "workspace:0.8.35",
"jazz-browser-media-images": "workspace:0.8.36",
"jazz-react": "workspace:0.8.36",
"jazz-tools": "workspace:0.8.36",
"lucide-react": "^0.274.0",
"qrcode": "^1.5.3",
"react": "^18.2.0",
@@ -41,7 +41,7 @@
"@vitejs/plugin-react-swc": "^3.3.2",
"autoprefixer": "^10.4.14",
"is-ci": "^3.0.1",
"jazz-run": "workspace:0.8.35",
"jazz-run": "workspace:0.8.36",
"postcss": "^8.4.27",
"tailwindcss": "3.3.2",
"typescript": "^5.3.3",

View File

@@ -1,5 +1,14 @@
# reactions
## 0.0.11
### Patch Changes
- Updated dependencies [441fe27]
- jazz-tools@0.8.36
- jazz-react@0.8.36
- jazz-browser-media-images@0.8.36
## 0.0.10
### Patch Changes

View File

@@ -1,7 +1,7 @@
{
"name": "reactions",
"private": true,
"version": "0.0.10",
"version": "0.0.11",
"type": "module",
"scripts": {
"dev": "vite",

View File

@@ -1,5 +1,14 @@
# todo-vue
## 0.0.17
### Patch Changes
- Updated dependencies [441fe27]
- jazz-tools@0.8.36
- jazz-browser@0.8.36
- jazz-vue@0.8.24
## 0.0.16
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "todo-vue",
"version": "0.0.16",
"version": "0.0.17",
"private": true,
"type": "module",
"scripts": {

View File

@@ -1,5 +1,13 @@
# jazz-example-todo
## 0.0.130
### Patch Changes
- Updated dependencies [441fe27]
- jazz-tools@0.8.36
- jazz-react@0.8.36
## 0.0.129
### Patch Changes

View File

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

View File

@@ -0,0 +1,235 @@
import * as turf from "@turf/turf";
import type { FeatureCollection, Point, Position } from "geojson";
import { type NextRequest } from "next/server";
import land from "../../../components/cloud/ne_110m_land.json";
import { pingColorThresholds } from "../../../components/cloud/pingColorThresholds";
// generated with: globalping ping cloud.jazz.tools from world --limit 500 --packets 16 --json | jq "del(.results[].result.rawOutput)" > pings.json
import pings from "../../../components/cloud/pings.json";
export const revalidate = 2 * 60 * 60; // 2 hours
export async function GET(req: NextRequest) {
const spacing = parseFloat(req.nextUrl.searchParams.get("spacing") || "1.5");
const dark = req.nextUrl.searchParams.get("dark") === "true";
const addMouseScript = req.nextUrl.searchParams.get("mouse") === "true";
const serverLocations = [
{
city: "Los Angeles",
lat: 34.0522,
lng: -118.2437,
ip: "134.195.91.235",
color: "hsl(0, 50%, 50%)",
},
{
city: "New York",
lat: 40.7128,
lng: -74.006,
ip: "45.45.219.149",
color: "hsl(25, 50%, 50%)",
},
{
city: "London",
lat: 51.5074,
lng: -0.1278,
ip: "150.107.201.83",
color: "hsl(50, 50%, 50%)",
},
{
city: "Singapore",
lat: 1.3521,
lng: 103.8198,
ip: "103.214.23.227",
color: "hsl(250, 50%, 50%)",
},
{
city: "Sydney",
lat: -33.8688,
lng: 151.2153,
ip: "103.73.65.179",
color: "hsl(100, 50%, 50%)",
},
{
city: "Tokyo",
lat: 35.6895,
lng: 139.7014,
ip: "103.173.179.181",
color: "hsl(150, 50%, 50%)",
},
{
city: "Tel Aviv",
lat: 32.0853,
lng: 34.7818,
ip: "64.176.162.228",
color: "hsl(200, 50%, 50%)",
},
{
city: "Johannesburg",
lat: -26.2041,
lng: 28.0473,
ip: "139.84.228.42",
color: "hsl(225, 50%, 50%)",
},
{
city: "Vienna",
lat: 48.2085,
lng: 16.3721,
ip: "185.175.59.44",
color: "hsl(75, 50%, 50%)",
},
{
city: "Sao Paulo",
lat: -23.5505,
lng: -46.6333,
ip: "216.238.99.7",
color: "hsl(275, 50%, 50%)",
},
{
city: "Dallas",
lat: 32.7767,
lng: -96.797,
ip: "45.32.192.94",
color: "hsl(300, 50%, 50%)",
},
];
// create a grid of dots that are green if on land (contained in landOutlines) and blue if not
const extentX = 720;
const extentY = 160;
const grid = new Array(Math.round(extentX / spacing))
.fill(0)
.map((_, i) =>
new Array(Math.round(extentY / spacing))
.fill(0)
.map((_, j) => ({ x: i, y: j })),
);
// manually add Hawaii by lat/lng
grid.push([{ x: -155.844437, y: 19.8987 }]);
const dots = grid.flatMap((row) =>
row.map(({ x, y }) => ({
x: -450 + x * spacing + ((y % 2) * spacing) / 2,
y: -60 + y * spacing,
})),
);
const landPolygon = turf.multiPolygon(
land.geometries.map((g) => g.coordinates),
);
const dotsOnLand = turf.pointsWithinPolygon(
turf.points(dots.map((d) => [d.x, d.y])),
landPolygon,
) as FeatureCollection<Point>;
const scaleX = 3;
const scaleY = 3;
const offsetX = 600;
const offsetY = 260;
const svg = `<svg
viewBox="0 0 1200 440"
xmlns="http://www.w3.org/2000/svg"
>
<style>
circle {
transition: fill 0.2s ease-in-out;
}
</style>
${dotsOnLand.features
.map((dot, index) => {
const nearestMeasurement = pings.results.reduce(
(minDistance, ping) => {
if (
!ping.result.stats ||
ping.result.stats.rcv === 0 ||
ping.result.stats.avg === null
)
return minDistance;
const distance = turf.distance(
dot.geometry.coordinates,
[ping.probe.longitude, ping.probe.latitude],
{ units: "kilometers" },
);
const totalPing =
(2 * 1000 * distance) / (0.66 * 299_792) +
ping.result.stats.min;
if (distance < minDistance.dist) {
return {
city: ping.probe.city,
dist: distance,
ping: ping.result.stats.min,
totalPing,
resolvedAddress: ping.result.resolvedAddress,
};
}
return minDistance;
},
{
city: "",
dist: Infinity,
ping: Infinity,
totalPing: Infinity,
resolvedAddress: "",
},
);
return `<circle cx="${
dot.geometry.coordinates[0] * scaleX + offsetX
}" cy="${
-dot.geometry.coordinates[1] * scaleY + offsetY
}" r="${1.9 * spacing}" fill="${
pingColorThresholds.find(
(t) => nearestMeasurement.totalPing < t.ping,
)?.[dark ? "darkFill" : "fill"]
// serverLocations.find(
// (srv) => srv.ip == nearestMeasurement.resolvedAddress,
// )?.color
}" data-ping="${nearestMeasurement.totalPing.toFixed(
1,
)}ms" data-via="${nearestMeasurement.city + ""}" data-to="${
serverLocations.find(
(srv) => srv.ip == nearestMeasurement.resolvedAddress,
)?.city
}"/>`;
})
.join("\n")}
${
addMouseScript
? `<script>
document.addEventListener("mousemove", (e) => {
const target = e.target;
if (target?.nodeName === "circle") {
const x = target.cx.baseVal.value;
const y = target.cy.baseVal.value;
const ping = parseInt(target.dataset.ping || "0");
const via = target.dataset.via;
const to = target.dataset.to;
const text = \`\${ping}ms via \${via} to \${to}\`;
window.parent.postMessage({
type: "svgmouseover",
x,
y,
ping,
via,
to,
}, "*");
} else {
window.parent.postMessage({
type: "svgmouseout",
}, "*");
}
});
</script>`
: ""
}
</svg>`;
return new Response(svg, {
headers: {
"Content-Type": "image/svg+xml",
"Cache-Control": "public, s-maxage=7200, stale-while-revalidate=3600",
},
});
}

View File

@@ -36,7 +36,7 @@ Passkey authentication allows users to create a new account or log in with an ex
Passkey authentication is supported out of the box.
We have a [minimal example of a passkey authentication setup](https://github.com/garden-co/jazz/tree/main/examples/minimal-auth-passkey).
We have a [minimal example of a passkey authentication setup](https://github.com/garden-co/jazz/tree/main/examples/passkey).
### How to use
@@ -58,7 +58,7 @@ const [passkeyAuth, passkeyState] = usePasskeyAuth({ appName });
We have a React package `jazz-react-auth-clerk` to add Clerk authentication to your app.
We have a [minimal example of a Clerk authentication setup](https://github.com/garden-co/jazz/tree/main/examples/minimal-auth-clerk).
We have a [minimal example of a Clerk authentication setup](https://github.com/garden-co/jazz/tree/main/examples/clerk).
### How to use
@@ -108,4 +108,4 @@ createRoot(document.getElementById("root")!).render(
</StrictMode>,
);
```
</CodeGroup>
</CodeGroup>

View File

@@ -32,7 +32,7 @@ Tested with:
<CodeGroup>
```bash
npx expo install expo-linking expo-secure-store expo-file-system @react-native-community/netinfo @bam.tech/react-native-image-resizer
npx expo install expo-linking expo-secure-store expo-file-system @react-native-community/netinfo @bam.tech/react-native-image-resizer @azure/core-asynciterator-polyfill
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

View File

@@ -1,25 +1,14 @@
"use client";
import React, { memo } from "react";
import { usePingColorThresholds } from "@/components/cloud/usePingColorThresholds";
import * as turf from "@turf/turf";
import type { FeatureCollection, Point, Position } from "geojson";
import MapTooltip from "./mapTooltip";
import land from "./ne_110m_land.json";
import { clsx } from "clsx";
// generated with: globalping ping cloud.jazz.tools from world --limit 500 --packets 16 --json | jq "del(.results[].result.rawOutput)" > pings.json
import pings from "./pings.json";
import MapTooltip from "./mapTooltip";
import { pingColorThresholds } from "./pingColorThresholds";
export const LatencyMap = () => {
const pingColorThresholds = usePingColorThresholds();
return (
<div className="relative mb-10 sm:-mt-5 -left-4 lg:-left-8 rounded-lg">
<MapSVG />
<MapTooltip />
<div className="absolute bottom-0 left-4 lg:bottom-8 lg:left-8 flex flex-col md:gap-1">
<div className="mb-4 rounded-lg relative">
<div className="relative xl:-mx-[10%] xl:w-[120%] aspect-[12/4]">
<MapTooltip />
</div>
<div className="absolute bottom-0 left-0 lg:bottom-8 flex flex-col md:gap-1">
{pingColorThresholds.map((t, i) => (
<div
key={t.ping}
@@ -27,10 +16,7 @@ export const LatencyMap = () => {
"hidden sm:flex": i % 2 !== 0,
})}
>
<div
className="size-2 md:size-3 rounded-full"
style={{ background: t.color }}
></div>
<div className={"size-2 md:size-3 rounded-full " + t.bgClass}></div>
<div className="text-[9px] md:text-xs font-mono">
&lt;{t.ping}ms
</div>
@@ -40,187 +26,3 @@ export const LatencyMap = () => {
</div>
);
};
export const MapSVG = memo(({ spacing = 1.5 }: { spacing?: number }) => {
const pingColorThresholds = usePingColorThresholds();
// Define the data points with their latitudes, longitudes, and ping times
const serverLocations = [
{
city: "Los Angeles",
lat: 34.0522,
lng: -118.2437,
ip: "134.195.91.235",
color: "hsl(0, 50%, 50%)",
},
{
city: "New York",
lat: 40.7128,
lng: -74.006,
ip: "45.45.219.149",
color: "hsl(25, 50%, 50%)",
},
{
city: "London",
lat: 51.5074,
lng: -0.1278,
ip: "150.107.201.83",
color: "hsl(50, 50%, 50%)",
},
{
city: "Singapore",
lat: 1.3521,
lng: 103.8198,
ip: "103.214.23.227",
color: "hsl(250, 50%, 50%)",
},
{
city: "Sydney",
lat: -33.8688,
lng: 151.2153,
ip: "103.73.65.179",
color: "hsl(100, 50%, 50%)",
},
{
city: "Tokyo",
lat: 35.6895,
lng: 139.7014,
ip: "103.173.179.181",
color: "hsl(150, 50%, 50%)",
},
{
city: "Tel Aviv",
lat: 32.0853,
lng: 34.7818,
ip: "64.176.162.228",
color: "hsl(200, 50%, 50%)",
},
{
city: "Johannesburg",
lat: -26.2041,
lng: 28.0473,
ip: "139.84.228.42",
color: "hsl(225, 50%, 50%)",
},
{
city: "Vienna",
lat: 48.2085,
lng: 16.3721,
ip: "185.175.59.44",
color: "hsl(75, 50%, 50%)",
},
{
city: "Sao Paulo",
lat: -23.5505,
lng: -46.6333,
ip: "216.238.99.7",
color: "hsl(275, 50%, 50%)",
},
{
city: "Dallas",
lat: 32.7767,
lng: -96.797,
ip: "45.32.192.94",
color: "hsl(300, 50%, 50%)",
},
];
// create a grid of dots that are green if on land (contained in landOutlines) and blue if not
const extentX = 720;
const extentY = 160;
const grid = new Array(Math.round(extentX / spacing))
.fill(0)
.map((_, i) =>
new Array(Math.round(extentY / spacing))
.fill(0)
.map((_, j) => ({ x: i, y: j })),
);
// manually add Hawaii by lat/lng
grid.push([{ x: -155.844437, y: 19.8987 }]);
const dots = grid.flatMap((row) =>
row.map(({ x, y }) => ({
x: -450 + x * spacing + ((y % 2) * spacing) / 2,
y: -60 + y * spacing,
})),
);
const landPolygon = turf.multiPolygon(
land.geometries.map((g) => g.coordinates),
);
const dotsOnLand = turf.pointsWithinPolygon(
turf.points(dots.map((d) => [d.x, d.y])),
landPolygon,
) as FeatureCollection<Point>;
const scaleX = 3;
const scaleY = 3;
const offsetX = 600;
const offsetY = 260;
return (
<svg
viewBox="0 0 1200 440"
className="mx-auto"
dangerouslySetInnerHTML={{
__html: dotsOnLand.features
.map((dot, index) => {
const nearestMeasurement = pings.results.reduce(
(minDistance, ping) => {
if (
!ping.result.stats ||
ping.result.stats.rcv === 0 ||
ping.result.stats.avg === null
)
return minDistance;
const distance = turf.distance(
dot.geometry.coordinates,
[ping.probe.longitude, ping.probe.latitude],
{ units: "kilometers" },
);
const totalPing =
(2 * 1000 * distance) / (0.66 * 299_792) +
ping.result.stats.min;
if (distance < minDistance.dist) {
return {
city: ping.probe.city,
dist: distance,
ping: ping.result.stats.min,
totalPing,
resolvedAddress: ping.result.resolvedAddress,
};
}
return minDistance;
},
{
city: "",
dist: Infinity,
ping: Infinity,
totalPing: Infinity,
resolvedAddress: "",
},
);
return `<circle cx="${
dot.geometry.coordinates[0] * scaleX + offsetX
}" cy="${
-dot.geometry.coordinates[1] * scaleY + offsetY
}" r="${1.9 * spacing}" fill="${
pingColorThresholds.find(
(t) => nearestMeasurement.totalPing < t.ping,
)?.color
// serverLocations.find(
// (srv) => srv.ip == nearestMeasurement.resolvedAddress,
// )?.color
}" data-ping="${nearestMeasurement.totalPing.toFixed(
1,
)}ms" data-via="${nearestMeasurement.city + ""}" data-to="${
serverLocations.find(
(srv) => srv.ip == nearestMeasurement.resolvedAddress,
)?.city
}"/>`;
})
.join("\n"),
}}
/>
);
});

View File

@@ -1,52 +1,66 @@
"use client";
import { usePingColorThresholds } from "@/components/cloud/usePingColorThresholds";
import { useEffect } from "react";
import { clsx } from "clsx";
import { useLayoutEffect, useState } from "react";
import { pingColorThresholds } from "./pingColorThresholds";
export default function MapTooltip() {
const pingColorThresholds = usePingColorThresholds();
const [style, setStyle] = useState<React.CSSProperties>({});
const [circleClass, setCircleClass] = useState("");
const [text, setText] = useState("");
useEffect(() => {
// register callback for hovering, if we're over any circle, show the tooltip based on the data attributes
const onMouseMove = (e: MouseEvent) => {
const circ = e.target;
const el = document.querySelector(
".map-tooltip",
) as HTMLDivElement | null;
if (!el) return;
if (circ instanceof SVGCircleElement) {
const x = circ.cx.baseVal.value;
const y = circ.cy.baseVal.value;
const ping = parseInt(circ.dataset.ping || "0");
const via = circ.dataset.via;
const to = circ.dataset.to;
const text = `${ping}ms via ${via} to ${to}`;
el.style.display = "flex";
el.style.left = `calc(100% * ${x / 1400} + 30px)`;
el.style.top = `calc(100% * ${(y || 0) / 440} + 15px)`;
(el.children[0] as HTMLDivElement).style.backgroundColor =
pingColorThresholds.find((t) => t.ping >= ping)?.color || "";
(el.children[1] as HTMLDivElement).textContent = text;
} else {
el.style.display = "none";
useLayoutEffect(() => {
const onSvgMouseMove = (e: MessageEvent) => {
if (e.data.type === "svgmouseout") {
setStyle({ display: "none" });
return;
}
if (e.data.type !== "svgmouseover") return;
const x = e.data.x;
const y = e.data.y;
const ping = e.data.ping;
const via = e.data.via;
const to = e.data.to;
const text = `${ping}ms via ${via} to ${to}`;
setStyle({
display: "flex",
left: `calc(100% * ${x / 1400} + 30px)`,
top: `calc(100% * ${(y || 0) / 440} + 15px)`,
});
setCircleClass(
"w-3 h-3 rounded-full " +
(pingColorThresholds.find((t) => t.ping >= ping)?.bgClass || ""),
);
setText(text);
};
document.addEventListener("mousemove", onMouseMove);
window.addEventListener("message", onSvgMouseMove);
return () => {
document.removeEventListener("mousemove", onMouseMove);
window.removeEventListener("message", onSvgMouseMove);
};
}, []);
return (
<div className="map-tooltip absolute pointer-events-none text-xs bg-stone-925 text-stone-50 p-2 rounded-lg gap-1 items-center">
<div className="w-3 h-3 rounded-full"></div>
<div className="text-xs"></div>
</div>
<>
<iframe
className="w-full aspect-[12/4] dark:hidden"
src="/api/latencyMap?spacing=1.5&dark=false&mouse=true"
/>
<iframe
className="w-full aspect-[12/4] hidden dark:block"
src="/api/latencyMap?spacing=1.5&dark=true&mouse=true"
style={{ colorScheme: "light" }}
/>
<div
className="hidden map-tooltip absolute pointer-events-none text-xs bg-stone-925 text-stone-50 p-2 rounded-lg gap-1 items-center"
style={style}
>
<div className={clsx("w-3 h-3 rounded-full", circleClass)}></div>
<div className="text-xs">{text}</div>
</div>
</>
);
}

View File

@@ -0,0 +1,80 @@
export const pingColorThresholds = [
{
ping: 5,
bgClass: "dark:bg-[hsl(248,50%,100%)] bg-[hsl(260,100%,53%)]",
fill: "hsl(260,100%,53%)",
darkFill: "hsl(248,50%,100%)",
},
{
ping: 10,
bgClass: "dark:bg-[hsl(248,50%,80%)] bg-[hsl(258,95%,56%)]",
fill: "hsl(258,95%,56%)",
darkFill: "hsl(248,50%,80%)",
},
{
ping: 15,
bgClass: "dark:bg-[hsl(248,50%,72%)] bg-[hsl(256,93%,59%)]",
fill: "hsl(256,93%,59%)",
darkFill: "hsl(248,50%,72%)",
},
{
ping: 25,
bgClass: "dark:bg-[hsl(248,50%,62%)] bg-[hsl(252,90%,62%)]",
fill: "hsl(252,90%,62%)",
darkFill: "hsl(248,50%,62%)",
},
{
ping: 35,
bgClass: "dark:bg-[hsl(248,50%,54%)] bg-[hsl(250,88%,65%)]",
fill: "hsl(250,88%,65%)",
darkFill: "hsl(248,50%,54%)",
},
{
ping: 45,
bgClass: "dark:bg-[hsl(248,50%,49%)] bg-[hsl(245,87%,68%)]",
fill: "hsl(245,87%,68%)",
darkFill: "hsl(248,50%,49%)",
},
{
ping: 55,
bgClass: "dark:bg-[hsl(248,50%,43%)] bg-[hsl(240,86%,71%)]",
fill: "hsl(240,86%,71%)",
darkFill: "hsl(248,50%,43%)",
},
{
ping: 65,
bgClass: "dark:bg-[hsl(248,50%,39%)] bg-[hsl(238,84%,74%)]",
fill: "hsl(238,84%,74%)",
darkFill: "hsl(248,50%,39%)",
},
{
ping: 100,
bgClass: "dark:bg-[hsl(248,50%,35%)] bg-[hsl(235,80%,77%)]",
fill: "hsl(235,80%,77%)",
darkFill: "hsl(248,50%,35%)",
},
{
ping: 150,
bgClass: "dark:bg-[hsl(248,50%,28%)] bg-[hsl(232,73%,80%)]",
fill: "hsl(232,73%,80%)",
darkFill: "hsl(248,50%,28%)",
},
{
ping: 200,
bgClass: "dark:bg-[hsl(248,50%,23%)] bg-[hsl(230,69%,83%)]",
fill: "hsl(230,69%,83%)",
darkFill: "hsl(248,50%,23%)",
},
{
ping: 300,
bgClass: "dark:bg-[hsl(248,50%,20%)] bg-[hsl(230,65%,88%)]",
fill: "hsl(230,65%,88%)",
darkFill: "hsl(248,50%,20%)",
},
{
ping: 1000,
bgClass: "dark:bg-[hsl(248,50%,16%)] bg-[hsl(220,60%,92%)]",
fill: "hsl(220,60%,92%)",
darkFill: "hsl(248,50%,16%)",
},
];

View File

@@ -1,41 +0,0 @@
import { useTheme } from "next-themes";
export const pingColorThresholdsDark = [
{ ping: 5, color: "hsl(248, 50%, 100%)" },
{ ping: 10, color: "hsl(248, 50%, 80%)" },
{ ping: 15, color: "hsl(248, 50%, 72%)" },
{ ping: 25, color: "hsl(248, 50%, 62%)" },
{ ping: 35, color: "hsl(248, 50%, 54%)" },
{ ping: 45, color: "hsl(248, 50%, 49%)" },
{ ping: 55, color: "hsl(248, 50%, 43%)" },
{ ping: 65, color: "hsl(248, 50%, 39%)" },
{ ping: 100, color: "hsl(248, 50%, 35%)" },
{ ping: 150, color: "hsl(248, 50%, 28%)" },
{ ping: 200, color: "hsl(248, 50%, 23%)" },
{ ping: 300, color: "hsl(248, 50%, 20%)" },
{ ping: 1000, color: "hsl(248, 50%, 16%)" },
];
export const pingColorThresholdsLight = [
{ ping: 5, color: "hsl(260,100%,53%)" },
{ ping: 10, color: "hsl(258,95%,56%)" },
{ ping: 15, color: "hsl(256,93%,59%)" },
{ ping: 25, color: "hsl(252,90%,62%)" },
{ ping: 35, color: "hsl(250,88%,65%)" },
{ ping: 45, color: "hsl(245,87%,68%)" },
{ ping: 55, color: "hsl(240,86%,71%)" },
{ ping: 65, color: "hsl(238,84%,74%)" },
{ ping: 100, color: "hsl(235,80%,77%)" },
{ ping: 150, color: "hsl(232,73%,80%)" },
{ ping: 200, color: "hsl(230,69%,83%)" },
{ ping: 300, color: "hsl(230,65%,88%)" },
{ ping: 1000, color: "hsl(220,60%,92%)" },
];
export const usePingColorThresholds = () => {
const { resolvedTheme } = useTheme();
return resolvedTheme === "dark"
? pingColorThresholdsDark
: pingColorThresholdsLight;
};

View File

@@ -16,7 +16,6 @@ import {
UserIcon,
} from "lucide-react";
import Link from "next/link";
import { MapSVG } from "../cloud/latencyMap";
const features = [
{
@@ -141,7 +140,14 @@ export function FeaturesSection() {
<div className="relative border p-4 sm:p-8 shadow-sm rounded-xl col-span-2 sm:col-span-4 flex flex-col justify-end">
<div className="mb-3 sm:-right-3 sm:bottom-30 sm:absolute sm:left-[40%] sm:top-2 md:top-4 md:left-[23%]">
<MapSVG spacing={3} />
<img
src="/api/latencyMap?spacing=2&dark=false"
className="w-full dark:hidden"
/>
<img
src="/api/latencyMap?spacing=2&dark=true"
className="w-full hidden dark:block"
/>
</div>
<H3>Jazz Cloud</H3>

View File

@@ -11,7 +11,7 @@
"format-and-lint": "biome check .",
"format-and-lint:fix": "biome check . --write"
},
"packageManager": "pnpm@9.1.4",
"packageManager": "pnpm@9.14.0",
"dependencies": {
"@headlessui/react": "^2.2.0",
"@icons-pack/react-simple-icons": "^9.1.0",

View File

@@ -1,4 +1,4 @@
lockfileVersion: '6.0'
lockfileVersion: '9.0'
settings:
autoInstallPeers: true

View File

@@ -3,7 +3,7 @@
"private": true,
"type": "module",
"workspaces": ["packages/*", "examples/*"],
"packageManager": "pnpm@9.1.4",
"packageManager": "pnpm@9.14.0",
"devDependencies": {
"@biomejs/biome": "1.9.4",
"@changesets/cli": "^2.27.3",

View File

@@ -1,5 +1,15 @@
# cojson-storage-indexeddb
## 0.8.36
### Patch Changes
- 1afbd2c: Refactor the SQLite and IndexedDB storage packages to extract common synchronization functionality into newly created cojson-storage package.
- Updated dependencies [441fe27]
- Updated dependencies [1afbd2c]
- cojson@0.8.36
- cojson-storage@0.8.36
## 0.8.35
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "cojson-storage-indexeddb",
"version": "0.8.35",
"version": "0.8.36",
"main": "dist/index.js",
"type": "module",
"types": "src/index.ts",

View File

@@ -1,5 +1,15 @@
# cojson-storage-sqlite
## 0.8.36
### Patch Changes
- 1afbd2c: Refactor the SQLite and IndexedDB storage packages to extract common synchronization functionality into newly created cojson-storage package.
- Updated dependencies [441fe27]
- Updated dependencies [1afbd2c]
- cojson@0.8.36
- cojson-storage@0.8.36
## 0.8.35
### Patch Changes

View File

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

View File

@@ -0,0 +1,9 @@
# cojson-storage
## 0.8.36
### Patch Changes
- 1afbd2c: Refactor the SQLite and IndexedDB storage packages to extract common synchronization functionality into newly created cojson-storage package.
- Updated dependencies [441fe27]
- cojson@0.8.36

View File

@@ -1,6 +1,6 @@
{
"name": "cojson-storage",
"version": "0.8.35",
"version": "0.8.36",
"main": "dist/index.js",
"type": "module",
"types": "src/index.ts",

View File

@@ -1,5 +1,12 @@
# cojson-transport-nodejs-ws
## 0.8.36
### Patch Changes
- Updated dependencies [441fe27]
- cojson@0.8.36
## 0.8.35
### Patch Changes

View File

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

View File

@@ -1,5 +1,11 @@
# cojson
## 0.8.36
### Patch Changes
- 441fe27: Optimise large record-like CoMaps for access of latest value
## 0.8.35
### Patch Changes

View File

@@ -19,7 +19,7 @@
},
"type": "module",
"license": "MIT",
"version": "0.8.35",
"version": "0.8.36",
"devDependencies": {
"@types/jest": "^29.5.3",
"typescript": "^5.3.3",

View File

@@ -1,5 +1,5 @@
import { CoID, RawCoValue } from "../coValue.js";
import { CoValueCore } from "../coValueCore.js";
import { CoValueCore, DecryptedTransaction } from "../coValueCore.js";
import { AgentID, TransactionID } from "../ids.js";
import { JsonObject, JsonValue } from "../jsonValue.js";
import { accountOrAgentIDfromSessionID } from "../typeUtils/accountOrAgentIDfromSessionID.js";
@@ -49,6 +49,9 @@ export class RawCoMapView<
cachedOps?: {
[Key in keyof Shape & string]?: MapOp<Key, Shape[Key]>[];
};
/** @internal */
validSortedTransactions?: DecryptedTransaction[];
/** @internal */
options?: { ignorePrivateTransactions: boolean; atTime?: number };
/** @internal */
@@ -59,27 +62,58 @@ export class RawCoMapView<
/** @internal */
constructor(
core: CoValueCore,
options?: { ignorePrivateTransactions: boolean; atTime?: number },
options?: {
ignorePrivateTransactions: boolean;
atTime?: number;
validSortedTransactions?: DecryptedTransaction[];
cachedOps?: {
[Key in keyof Shape & string]?: MapOp<Key, Shape[Key]>[];
};
},
) {
this.id = core.id as CoID<this>;
this.core = core;
this.latest = {};
this.latestTxMadeAt = 0;
this.options = options;
this.cachedOps = options?.cachedOps;
this.validSortedTransactions = options?.validSortedTransactions;
this.processLatestTransactions();
}
processLatestTransactions() {
/** @internal */
private getValidSortedTransactions() {
if (this.validSortedTransactions) {
return this.validSortedTransactions;
}
const validSortedTransactions = this.core.getValidSortedTransactions({
ignorePrivateTransactions:
this.options?.ignorePrivateTransactions ?? false,
});
this.validSortedTransactions = validSortedTransactions;
return validSortedTransactions;
}
private resetCachedValues() {
this.validSortedTransactions = undefined;
this.cachedOps = undefined;
}
private processLatestTransactions() {
// Reset all internal state and cached values
this.latest = {};
this.latestTxMadeAt = 0;
const { core, options, latest } = this;
const { latest } = this;
for (const { txID, changes, madeAt } of core.getValidSortedTransactions({
ignorePrivateTransactions: options?.ignorePrivateTransactions ?? false,
})) {
if (options?.atTime && madeAt > options.atTime) {
const atTimeFilter = this.options?.atTime;
for (const { txID, changes, madeAt } of this.getValidSortedTransactions()) {
if (atTimeFilter && madeAt > atTimeFilter) {
continue;
}
@@ -111,6 +145,11 @@ export class RawCoMapView<
}
}
revalidateTransactions() {
this.resetCachedValues();
this.processLatestTransactions();
}
private getOps() {
if (this.cachedOps) {
return this.cachedOps;
@@ -120,11 +159,7 @@ export class RawCoMapView<
[Key in keyof Shape & string]?: MapOp<Key, Shape[Key]>[];
} = {};
for (const {
txID,
changes,
madeAt,
} of this.core.getValidSortedTransactions(this.options)) {
for (const { txID, changes, madeAt } of this.getValidSortedTransactions()) {
for (let changeIdx = 0; changeIdx < changes.length; changeIdx++) {
const change = changes[changeIdx] as MapOpPayload<
keyof Shape & string,
@@ -168,6 +203,8 @@ export class RawCoMapView<
ignorePrivateTransactions:
this.options?.ignorePrivateTransactions ?? false,
atTime: time,
cachedOps: this.cachedOps,
validSortedTransactions: this.validSortedTransactions,
});
Object.setPrototypeOf(clone, this);
return clone as this;
@@ -182,10 +219,10 @@ export class RawCoMapView<
return undefined;
}
if (this.atTimeFilter) {
return this.getOps()[key]?.filter(
(op) => op.madeAt <= this.atTimeFilter!,
);
const atTimeFilter = this.options?.atTime;
if (atTimeFilter) {
return this.getOps()[key]?.filter((op) => op.madeAt <= atTimeFilter);
} else {
return this.getOps()[key];
}
@@ -197,17 +234,13 @@ export class RawCoMapView<
* @category 1. Reading */
keys<K extends keyof Shape & string = keyof Shape & string>(): K[] {
return (Object.keys(this.latest) as K[]).filter((key) => {
const latest = this.latest[key];
if (!latest) {
return undefined;
const latestChange = this.latest[key];
if (!latestChange) {
return false;
}
const includeUntil = this.atTimeFilter;
const lastEntry = includeUntil
? this.getOps()[key]?.findLast((entry) => entry.madeAt <= includeUntil)
: latest!;
if (lastEntry?.change.op === "del") {
if (latestChange.change.op === "del") {
return false;
} else {
return true;
@@ -221,22 +254,15 @@ export class RawCoMapView<
* @category 1. Reading
**/
get<K extends keyof Shape & string>(key: K): Shape[K] | undefined {
const latest = this.latest[key];
if (!latest) {
const latestChange = this.latest[key];
if (!latestChange) {
return undefined;
}
const includeUntil = this.atTimeFilter;
const lastEntry = includeUntil
? this.timeFilteredOps(key)?.findLast(
(entry) => entry.madeAt <= includeUntil,
)
: latest;
if (lastEntry?.change.op === "del") {
if (latestChange.change.op === "del") {
return undefined;
} else {
return lastEntry?.change.value;
return latestChange.change.value as Shape[K];
}
}
@@ -248,7 +274,7 @@ export class RawCoMapView<
[K in keyof Shape & string]: Shape[K];
}> = {};
for (const key of this.keys()) {
for (const key of Object.keys(this.latest) as (keyof Shape & string)[]) {
const value = this.get(key);
if (value !== undefined) {
object[key] = value;
@@ -268,34 +294,21 @@ export class RawCoMapView<
}
/** @category 5. Edit history */
nthEditAt<K extends keyof Shape & string>(
key: K,
n: number,
):
| {
by: RawAccountID | AgentID;
tx: TransactionID;
at: Date;
value?: Shape[K];
}
| undefined {
const ops = this.timeFilteredOps(key);
if (!ops || ops.length <= n) {
nthEditAt<K extends keyof Shape & string>(key: K, n: number) {
const ops = this.getOps()[key];
const atTimeFilter = this.options?.atTime;
const entry = ops?.[n];
if (!entry) {
return undefined;
}
const entry = ops[n]!;
if (this.atTimeFilter && entry.madeAt > this.atTimeFilter) {
if (atTimeFilter && entry.madeAt > atTimeFilter) {
return undefined;
}
return {
by: accountOrAgentIDfromSessionID(entry.txID.sessionID),
tx: entry.txID,
at: new Date(entry.madeAt),
value: entry.change.op === "del" ? undefined : entry.change.value,
};
return operationToEditEntry(entry);
}
/** @category 5. Edit history */
@@ -310,10 +323,13 @@ export class RawCoMapView<
}
| undefined {
const ops = this.timeFilteredOps(key);
if (!ops || ops.length === 0) {
const lastEntry = ops?.[ops.length - 1];
if (!lastEntry) {
return undefined;
}
return this.nthEditAt(key, ops.length - 1);
return operationToEditEntry(lastEntry);
}
/** @category 5. Edit history */
@@ -323,8 +339,8 @@ export class RawCoMapView<
return;
}
for (let i = 0; i < ops.length; i++) {
yield this.nthEditAt(key, i)!;
for (const entry of ops) {
yield operationToEditEntry(entry);
}
}
@@ -370,8 +386,7 @@ export class RawCoMap<
privacy,
);
this.processLatestTransactions();
this.cachedOps = undefined;
this.revalidateTransactions();
}
/** Delete the given key (setting it to undefined).
@@ -396,7 +411,18 @@ export class RawCoMap<
privacy,
);
this.processLatestTransactions();
this.cachedOps = undefined;
this.revalidateTransactions();
}
}
export function operationToEditEntry<
K extends string,
V extends JsonValue | undefined,
>(op: MapOp<K, V>) {
return {
by: accountOrAgentIDfromSessionID(op.txID.sessionID),
tx: op.txID,
at: new Date(op.madeAt),
value: op.change.op === "del" ? undefined : op.change.value,
};
}

View File

@@ -1,5 +1,6 @@
import { expect, test } from "vitest";
import { expectMap } from "../coValue.js";
import { operationToEditEntry } from "../coValues/coMap.js";
import { WasmCrypto } from "../crypto/WasmCrypto.js";
import { LocalNode } from "../localNode.js";
import { accountOrAgentIDfromSessionID } from "../typeUtils/accountOrAgentIDfromSessionID.js";
@@ -82,6 +83,29 @@ test("Can get CoMap entry values at different points in time", () => {
expect(content.atTime(beforeA).get("hello")).toEqual(undefined);
expect(content.atTime(beforeB).get("hello")).toEqual("A");
expect(content.atTime(beforeC).get("hello")).toEqual("B");
const ops = content.timeFilteredOps("hello");
expect(content.atTime(beforeC).lastEditAt("hello")).toEqual(
operationToEditEntry(ops![1]!),
);
expect(content.atTime(beforeC).nthEditAt("hello", 0)).toEqual(
operationToEditEntry(ops![0]!),
);
expect(content.atTime(beforeC).nthEditAt("hello", 2)).toEqual(undefined);
expect([...content.atTime(beforeC).editsAt("hello")]).toEqual([
operationToEditEntry(ops![0]!),
operationToEditEntry(ops![1]!),
]);
expect(content.atTime(beforeB).asObject()).toEqual({
hello: "A",
});
expect(content.atTime(beforeC).asObject()).toEqual({
hello: "B",
});
});
test("Can get all historic values of key in CoMap", () => {

View File

@@ -1,5 +1,14 @@
# jazz-browser-media-images
## 0.8.36
### Patch Changes
- Updated dependencies [441fe27]
- cojson@0.8.36
- jazz-tools@0.8.36
- jazz-browser@0.8.36
## 0.8.35
### Patch Changes

View File

@@ -1,14 +1,14 @@
{
"name": "jazz-browser-auth-clerk",
"version": "0.8.35",
"version": "0.8.36",
"type": "module",
"main": "dist/index.js",
"types": "src/index.ts",
"license": "MIT",
"dependencies": {
"cojson": "workspace:0.8.35",
"jazz-browser": "workspace:0.8.35",
"jazz-tools": "workspace:0.8.35"
"cojson": "workspace:0.8.36",
"jazz-browser": "workspace:0.8.36",
"jazz-tools": "workspace:0.8.36"
},
"scripts": {
"format-and-lint": "biome check .",

View File

@@ -1,5 +1,13 @@
# jazz-browser-media-images
## 0.8.36
### Patch Changes
- Updated dependencies [441fe27]
- jazz-tools@0.8.36
- jazz-browser@0.8.36
## 0.8.35
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "jazz-browser-media-images",
"version": "0.8.35",
"version": "0.8.36",
"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.35",
"jazz-tools": "workspace:0.8.35",
"jazz-browser": "workspace:0.8.36",
"jazz-tools": "workspace:0.8.36",
"pica": "^9.0.1",
"typescript": "^5.3.3"
},

View File

@@ -1,5 +1,16 @@
# jazz-browser
## 0.8.36
### Patch Changes
- Updated dependencies [441fe27]
- Updated dependencies [1afbd2c]
- cojson@0.8.36
- jazz-tools@0.8.36
- cojson-storage-indexeddb@0.8.36
- cojson-transport-ws@0.8.36
## 0.8.35
### Patch Changes

View File

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

View File

@@ -1,5 +1,14 @@
# jazz-autosub
## 0.8.36
### Patch Changes
- Updated dependencies [441fe27]
- cojson@0.8.36
- jazz-tools@0.8.36
- cojson-transport-ws@0.8.36
## 0.8.35
### Patch Changes

View File

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

View File

@@ -1,5 +1,15 @@
# jazz-browser-media-images
## 0.8.36
### Patch Changes
- Updated dependencies [441fe27]
- cojson@0.8.36
- jazz-tools@0.8.36
- jazz-browser-auth-clerk@0.8.36
- jazz-react@0.8.36
## 0.8.35
### Patch Changes

View File

@@ -1,15 +1,15 @@
{
"name": "jazz-react-auth-clerk",
"version": "0.8.35",
"version": "0.8.36",
"type": "module",
"main": "dist/index.js",
"types": "src/index.tsx",
"license": "MIT",
"dependencies": {
"cojson": "workspace:0.8.35",
"jazz-browser-auth-clerk": "workspace:0.8.35",
"jazz-react": "workspace:0.8.35",
"jazz-tools": "workspace:0.8.35"
"cojson": "workspace:0.8.36",
"jazz-browser-auth-clerk": "workspace:0.8.36",
"jazz-react": "workspace:0.8.36",
"jazz-tools": "workspace:0.8.36"
},
"peerDependencies": {
"react": "^18.2.0"

View File

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

View File

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

View File

@@ -0,0 +1,12 @@
# jazz-react-native-auth-clerk
## 0.8.36
### Patch Changes
- c84764a: feat: added jazz-react-native-auth-clerk package
- Updated dependencies [c84764a]
- Updated dependencies [441fe27]
- jazz-react-native@0.8.36
- cojson@0.8.36
- jazz-tools@0.8.36

View File

@@ -0,0 +1,19 @@
Copyright 2024, Garden Computing, Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,31 @@
# `jazz-react-native-auth-clerk`
This package provides a [Clerk-based](https://clerk.com/) authentication strategy for Jazz.
Looking for a React integration? Check out [`jazz-react-auth-clerk`](https://www.npmjs.com/package/jazz-react-native-auth-clerk).
## Usage
`ReactNativeClerkAuth` is a class that provides a `JazzAuth` object. Provide a Clerk instance to `ReactNativeClerkAuth`, and it will return the appropriate `JazzAuth` object. Once authenticated, authentication will persist across page reloads, even if the device is offline.
From [the example app](https://github.com/gardencmp/jazz/tree/main/examples/chat-rn-clerk):
```ts
import { ReactNativeClerkAuth } from "jazz-react-native-auth-clerk";
// ...
const auth = new ReactNativeClerkAuth(
{
onError: (error) => {
void clerk.signOut();
setState((state) => ({
...state,
errors: [...state.errors, error.toString()],
}));
},
},
clerk,
);
```

View File

@@ -0,0 +1,24 @@
{
"name": "jazz-react-native-auth-clerk",
"version": "0.8.36",
"type": "module",
"main": "dist/index.js",
"types": "src/index.ts",
"license": "MIT",
"dependencies": {
"cojson": "workspace:*",
"jazz-tools": "workspace:*",
"jazz-react-native": "workspace:*"
},
"scripts": {
"format-and-lint": "biome check .",
"format-and-lint:fix": "biome check . --write",
"build": "rm -rf ./dist && tsc --sourceMap --outDir dist",
"prepublishOnly": "npm run build",
"test": "vitest --run --root ../../ --project jazz-react-native-auth-clerk",
"test:watch": "vitest --watch --root ../../ --project jazz-react-native-auth-clerk"
},
"devDependencies": {
"typescript": "^5.3.3"
}
}

View File

@@ -0,0 +1,168 @@
import { AgentSecret } from "cojson";
import type { KvStore } from "jazz-react-native";
import { Account, AuthMethod, AuthResult, Credentials, ID } from "jazz-tools";
const localStorageKey = "jazz-clerk-auth";
export type MinimalClerkClient = {
user:
| {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
unsafeMetadata: Record<string, any>;
fullName: string | null;
username: string | null;
id: string;
update: (args: {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
unsafeMetadata: Record<string, any>;
}) => Promise<unknown>;
}
| null
| undefined;
signOut: () => Promise<void>;
};
function saveCredentialsToStorage(kvStore: KvStore, credentials: Credentials) {
kvStore.set(
localStorageKey,
JSON.stringify({
accountID: credentials.accountID,
secret: credentials.secret,
}),
);
}
export class ReactNativeClerkAuth implements AuthMethod {
constructor(
public driver: ReactNativeClerkAuth.Driver,
private readonly clerkClient: MinimalClerkClient,
private readonly kvStore: KvStore,
) {}
async start(): Promise<AuthResult> {
// Check local storage for credentials
const locallyStoredCredentials = await this.kvStore.get(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: () => {
void this.kvStore.delete(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) {
throw new Error("No secret for existing user");
}
return {
type: "existing",
credentials: {
accountID: storedCredentials.jazzAccountID as ID<Account>,
secret: storedCredentials.jazzAccountSecret as AgentSecret,
},
saveCredentials: async ({ accountID, secret }: Credentials) => {
saveCredentialsToStorage(this.kvStore, {
accountID,
secret,
});
},
onSuccess: () => {},
onError: (error: string | Error) => {
this.driver.onError(error);
},
logOut: () => {
void this.clerkClient.signOut();
},
};
} else {
// No credentials found, so we need to create new credentials
return {
type: "new",
creationProps: {
name:
this.clerkClient.user.fullName ||
this.clerkClient.user.username ||
this.clerkClient.user.id,
},
saveCredentials: async ({ accountID, secret }: Credentials) => {
saveCredentialsToStorage(this.kvStore, {
accountID,
secret,
});
await this.clerkClient.user?.update({
unsafeMetadata: {
jazzAccountID: accountID,
jazzAccountSecret: secret,
},
});
},
onSuccess: () => {},
onError: (error: string | Error) => {
this.driver.onError(error);
},
logOut: () => {
void this.clerkClient.signOut();
},
};
}
} else {
// Clerk user not found, so we can't authenticate
throw new Error("Not signed in");
}
}
}
// eslint-disable-next-line @typescript-eslint/no-namespace
export namespace ReactNativeClerkAuth {
export interface Driver {
onError: (error: string | Error) => void;
}
}
////
import { useMemo, useState } from "react";
export function useJazzClerkAuth(
clerk: MinimalClerkClient & {
signOut: () => Promise<unknown>;
},
kvStore: KvStore,
) {
const [state, setState] = useState<{ errors: string[] }>({ errors: [] });
const authMethod = useMemo(() => {
return new ReactNativeClerkAuth(
{
onError: (error) => {
void clerk.signOut();
setState((state) => ({
...state,
errors: [...state.errors, error.toString()],
}));
},
},
clerk,
kvStore,
);
}, [clerk.user]);
return [authMethod, state] as const;
}

View File

@@ -0,0 +1,148 @@
import { AgentSecret } from "cojson";
import type { KvStore } from "jazz-react-native";
import { Account, ID } from "jazz-tools";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { MinimalClerkClient, ReactNativeClerkAuth } from "../index.js";
describe("ReactNativeClerkAuth", () => {
let mockLocalStorage: { [key: string]: string };
let mockClerkClient: MinimalClerkClient;
let mockDriver: ReactNativeClerkAuth.Driver;
let mockKvStore: KvStore;
beforeEach(() => {
// Mock in-memory NativeStorage
mockLocalStorage = {};
mockKvStore = {
get: vi.fn(async (key: string) => mockLocalStorage[key] || null),
set: vi.fn(async (key: string, value: string) => {
mockLocalStorage[key] = value;
}),
delete: vi.fn(async (key: string) => {
delete mockLocalStorage[key];
}),
clearAll: vi.fn(async () => {
mockLocalStorage = {};
}),
};
// 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 NativeStorage", () => {
it("should get credentials from NativeStorage when clerk user is not signed in", async () => {
await mockKvStore.set(
"jazz-clerk-auth",
JSON.stringify({
accountID: "test-account-id",
accountSecret: "test-secret",
}),
);
const auth = new ReactNativeClerkAuth(
mockDriver,
{
...mockClerkClient,
user: null,
},
mockKvStore,
);
const result = await auth.start();
expect(result.type).toBe("existing");
});
});
describe("clerk credentials not in NativeStorage", () => {
it("should return new credentials when clerk user signs up", async () => {
const auth = new ReactNativeClerkAuth(
mockDriver,
mockClerkClient,
mockKvStore,
);
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 ReactNativeClerkAuth(
mockDriver,
mockClerkClient,
mockKvStore,
);
const result = await auth.start();
expect(result.type).toBe("existing");
});
it("should throw error when not signed in", async () => {
const auth = new ReactNativeClerkAuth(
mockDriver,
{
...mockClerkClient,
user: null,
},
mockKvStore,
);
await expect(auth.start()).rejects.toThrow("Not signed in");
});
it("should save credentials to NativeStorage", async () => {
const auth = new ReactNativeClerkAuth(
mockDriver,
mockClerkClient,
mockKvStore,
);
const result = await auth.start();
if (result.saveCredentials) {
await result.saveCredentials({
accountID: "test-account-id" as ID<Account>,
secret: "test-secret" as AgentSecret,
});
}
expect(await mockKvStore.get("jazz-clerk-auth")).toBeDefined();
});
it("should call clerk signOut when logging out", async () => {
const auth = new ReactNativeClerkAuth(
mockDriver,
mockClerkClient,
mockKvStore,
);
const result = await auth.start();
result.logOut();
expect(mockClerkClient.signOut).toHaveBeenCalled();
});
});
});

View File

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

View File

@@ -1,5 +1,12 @@
# jazz-browser-media-images
## 0.8.27
### Patch Changes
- Updated dependencies [441fe27]
- jazz-tools@0.8.36
## 0.8.26
### Patch Changes

View File

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

View File

@@ -1,5 +1,15 @@
# jazz-browser
## 0.8.36
### Patch Changes
- c84764a: feat: added jazz-react-native-auth-clerk package
- Updated dependencies [441fe27]
- cojson@0.8.36
- jazz-tools@0.8.36
- cojson-transport-ws@0.8.36
## 0.8.35
### Patch Changes

View File

@@ -25,7 +25,7 @@ npx expo prebuild
### Install dependencies
```bash
npx expo install expo-linking expo-secure-store expo-file-system @react-native-community/netinfo @bam.tech/react-native-image-resizer
npx expo install expo-linking expo-secure-store expo-file-system @react-native-community/netinfo @bam.tech/react-native-image-resizer @azure/core-asynciterator-polyfill
npm i -S react-native-polyfill-globals react-native-url-polyfill web-streams-polyfill base-64 text-encoding react-native-fetch-api react-native-get-random-values buffer

View File

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

View File

@@ -26,27 +26,27 @@ export { RNDemoAuth } from "./auth/DemoAuthMethod.js";
import { KvStoreContext } from "./storage/kv-store-context.js";
/** @category Context Creation */
export type BrowserContext<Acc extends Account> = {
export type ReactNativeContext<Acc extends Account> = {
me: Acc;
logOut: () => void;
// TODO: Symbol.dispose?
done: () => void;
};
export type BrowserGuestContext = {
export type ReactNativeGuestContext = {
guest: AnonymousJazzAgent;
logOut: () => void;
done: () => void;
};
export type BrowserContextOptions<Acc extends Account> = {
export type ReactNativeContextOptions<Acc extends Account> = {
auth: AuthMethod;
AccountSchema: CoValueClass<Acc> & {
fromNode: (typeof Account)["fromNode"];
};
} & BaseBrowserContextOptions;
} & BaseReactNativeContextOptions;
export type BaseBrowserContextOptions = {
export type BaseReactNativeContextOptions = {
peer: `wss://${string}` | `ws://${string}`;
reconnectionTimeout?: number;
storage?: "indexedDB" | "singleTabOPFS";
@@ -55,17 +55,17 @@ export type BaseBrowserContextOptions = {
/** @category Context Creation */
export async function createJazzRNContext<Acc extends Account>(
options: BrowserContextOptions<Acc>,
): Promise<BrowserContext<Acc>>;
options: ReactNativeContextOptions<Acc>,
): Promise<ReactNativeContext<Acc>>;
export async function createJazzRNContext(
options: BaseBrowserContextOptions,
): Promise<BrowserGuestContext>;
options: BaseReactNativeContextOptions,
): Promise<ReactNativeGuestContext>;
export async function createJazzRNContext<Acc extends Account>(
options: BrowserContextOptions<Acc> | BaseBrowserContextOptions,
): Promise<BrowserContext<Acc> | BrowserGuestContext>;
options: ReactNativeContextOptions<Acc> | BaseReactNativeContextOptions,
): Promise<ReactNativeContext<Acc> | ReactNativeGuestContext>;
export async function createJazzRNContext<Acc extends Account>(
options: BrowserContextOptions<Acc> | BaseBrowserContextOptions,
): Promise<BrowserContext<Acc> | BrowserGuestContext> {
options: ReactNativeContextOptions<Acc> | BaseReactNativeContextOptions,
): Promise<ReactNativeContext<Acc> | ReactNativeGuestContext> {
const firstWsPeer = createWebSocketPeer({
websocket: new WebSocket(options.peer),
id: options.peer + "@" + new Date().toISOString(),
@@ -192,15 +192,6 @@ export async function provideLockSession(
});
}
const window = {
location: {
href: "#",
},
history: {
replaceState: (a: any, b: any, c: any) => {},
},
};
/** @category Invite Links */
export function createInviteLink<C extends CoValue>(
value: C,

View File

@@ -14,9 +14,10 @@ import {
} from "jazz-tools";
import { Linking } from "react-native";
import {
BrowserContext,
BrowserGuestContext,
KvStore,
KvStoreContext,
ReactNativeContext,
ReactNativeGuestContext,
createJazzRNContext,
parseInviteLink,
} from "./index.js";
@@ -28,7 +29,7 @@ export function createJazzRNApp<Acc extends Account>({
AccountSchema = Account as unknown as AccountClass<Acc>,
} = {}): JazzReactApp<Acc> {
const JazzContext = React.createContext<
BrowserContext<Acc> | BrowserGuestContext | undefined
ReactNativeContext<Acc> | ReactNativeGuestContext | undefined
>(undefined);
if (!kvStore) {
@@ -49,7 +50,7 @@ export function createJazzRNApp<Acc extends Account>({
storage?: "indexedDB" | "singleTabOPFS";
}) {
const [ctx, setCtx] = useState<
BrowserContext<Acc> | BrowserGuestContext | undefined
ReactNativeContext<Acc> | ReactNativeGuestContext | undefined
>();
const [sessionCount, setSessionCount] = useState(0);
@@ -80,7 +81,11 @@ export function createJazzRNApp<Acc extends Account>({
});
return () => {
void promiseWithDoneCallback.then((done) => done());
void promiseWithDoneCallback
.then((done) => done())
.catch((e) => {
console.error("Error in createJazzRNContext", e);
});
};
}, [AccountSchema, auth, peer, storage, sessionCount]);
@@ -240,6 +245,7 @@ export function createJazzRNApp<Acc extends Account>({
useAccountOrGuest,
useCoState,
useAcceptInvite,
kvStore,
};
}
@@ -293,6 +299,8 @@ export interface JazzReactApp<Acc extends Account> {
onAccept: (projectID: ID<V>) => void;
forValueHint?: string;
}): void;
kvStore: KvStore;
}
export * from "./media.js";

View File

@@ -1,5 +1,14 @@
# jazz-react
## 0.8.36
### Patch Changes
- Updated dependencies [441fe27]
- cojson@0.8.36
- jazz-tools@0.8.36
- jazz-browser@0.8.36
## 0.8.35
### Patch Changes

View File

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

View File

@@ -1,5 +1,16 @@
# jazz-run
## 0.8.36
### Patch Changes
- Updated dependencies [441fe27]
- Updated dependencies [1afbd2c]
- cojson@0.8.36
- jazz-tools@0.8.36
- cojson-storage-sqlite@0.8.36
- cojson-transport-ws@0.8.36
## 0.8.35
### Patch Changes

View File

@@ -3,7 +3,7 @@
"bin": "./dist/index.js",
"type": "module",
"license": "MIT",
"version": "0.8.35",
"version": "0.8.36",
"scripts": {
"format-and-lint": "biome check .",
"format-and-lint:fix": "biome check . --write",
@@ -18,11 +18,11 @@
"@effect/printer-ansi": "^0.34.5",
"@effect/schema": "^0.71.1",
"@effect/typeclass": "^0.25.5",
"cojson": "workspace:0.8.35",
"cojson-storage-sqlite": "workspace:0.8.35",
"cojson-transport-ws": "workspace:0.8.35",
"cojson": "workspace:0.8.36",
"cojson-storage-sqlite": "workspace:0.8.36",
"cojson-transport-ws": "workspace:0.8.36",
"effect": "^3.6.5",
"jazz-tools": "workspace:0.8.35",
"jazz-tools": "workspace:0.8.36",
"ws": "^8.14.2"
},
"devDependencies": {

View File

@@ -1,5 +1,13 @@
# jazz-svelte
## 0.0.3
### Patch Changes
- Updated dependencies [441fe27]
- jazz-tools@0.8.36
- jazz-browser@0.8.36
## 0.0.2
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "jazz-svelte",
"version": "0.0.2",
"version": "0.0.3",
"scripts": {
"dev": "vite dev",
"build": "vite build && npm run package",

View File

@@ -1,5 +1,13 @@
# jazz-tools
## 0.8.36
### Patch Changes
- 441fe27: Optimise large record-like CoMaps for access of latest value
- Updated dependencies [441fe27]
- cojson@0.8.36
## 0.8.35
### Patch Changes

View File

@@ -19,7 +19,7 @@
},
"type": "module",
"license": "MIT",
"version": "0.8.35",
"version": "0.8.36",
"dependencies": {
"cojson": "workspace:*",
"fast-check": "^3.17.2"

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