chore: react-native scaffolding (wip)

This commit is contained in:
pax-k
2024-09-23 12:52:48 +03:00
parent 3710776d09
commit 15356960e9
29 changed files with 8577 additions and 69 deletions

1
.npmrc Normal file
View File

@@ -0,0 +1 @@
node-linker=hoisted

38
examples/chat-rn/.gitignore vendored Normal file
View File

@@ -0,0 +1,38 @@
# Learn more https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files
# dependencies
node_modules/
# Expo
.expo/
dist/
web-build/
# Native
*.orig.*
*.jks
*.p8
*.p12
*.key
*.mobileprovision
# Metro
.metro-health-check*
# debug
npm-debug.*
yarn-debug.*
yarn-error.*
# macOS
.DS_Store
*.pem
# local env files
.env*.local
# typescript
*.tsbuildinfo
ios
android

20
examples/chat-rn/App.tsx Normal file
View File

@@ -0,0 +1,20 @@
import { StatusBar } from 'expo-status-bar';
import { StyleSheet, Text, View } from 'react-native';
export default function App() {
return (
<View style={styles.container}>
<Text>Open up App.tsx to start working on your app!</Text>
<StatusBar style="auto" />
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
});

29
examples/chat-rn/app.json Normal file
View File

@@ -0,0 +1,29 @@
{
"expo": {
"name": "chat-rn",
"slug": "chat-rn",
"version": "1.0.0",
"orientation": "portrait",
"icon": "./assets/icon.png",
"userInterfaceStyle": "light",
"splash": {
"image": "./assets/splash.png",
"resizeMode": "contain",
"backgroundColor": "#ffffff"
},
"ios": {
"supportsTablet": true,
"bundleIdentifier": "com.jazz.chatrn"
},
"android": {
"adaptiveIcon": {
"foregroundImage": "./assets/adaptive-icon.png",
"backgroundColor": "#ffffff"
},
"package": "com.jazz.chatrn"
},
"web": {
"favicon": "./assets/favicon.png"
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

View File

@@ -0,0 +1,6 @@
module.exports = function(api) {
api.cache(true);
return {
presets: ['babel-preset-expo'],
};
};

View File

@@ -0,0 +1,8 @@
import { registerRootComponent } from "expo";
import App from "./App";
// registerRootComponent calls AppRegistry.registerComponent('main', () => App);
// It also ensures that whether you load the app in the Expo client or in a native build,
// the environment is set up appropriately
registerRootComponent(App);

View File

@@ -0,0 +1,27 @@
// Learn more https://docs.expo.dev/guides/monorepos
const { getDefaultConfig } = require("expo/metro-config");
const { FileStore } = require("metro-cache");
const path = require("path");
const projectRoot = __dirname;
const workspaceRoot = path.resolve(projectRoot, "../..");
const config = getDefaultConfig(projectRoot);
// Since we are using pnpm, we have to setup the monorepo manually for Metro
// #1 - Watch all files in the monorepo
config.watchFolders = [workspaceRoot];
// #2 - Try resolving with project modules first, then workspace modules
config.resolver.nodeModulesPaths = [
path.resolve(projectRoot, "node_modules"),
path.resolve(workspaceRoot, "node_modules"),
];
// Use turborepo to restore the cache when possible
config.cacheStores = [
new FileStore({
root: path.join(projectRoot, "node_modules", ".cache", "metro"),
}),
];
module.exports = config;

View File

@@ -0,0 +1,24 @@
{
"name": "chat-rn",
"version": "1.0.0",
"main": "index.js",
"scripts": {
"start": "expo start",
"android": "expo run:android",
"ios": "expo run:ios",
"web": "expo start --web"
},
"dependencies": {
"expo": "~51.0.28",
"expo-status-bar": "~1.12.1",
"react": "18.2.0",
"react-native": "0.75",
"jazz-react-native": "workspace:*"
},
"devDependencies": {
"@babel/core": "^7.20.0",
"@types/react": "~18.2.45",
"typescript": "^5.1.3"
},
"private": true
}

View File

@@ -0,0 +1,6 @@
{
"extends": "expo/tsconfig.base",
"compilerOptions": {
"strict": true
}
}

View File

@@ -32,5 +32,14 @@
"release": "pnpm changeset publish && git push --follow-tags"
},
"lint-staged": {},
"version": "0.0.0"
"version": "0.0.0",
"pnpm": {
"peerDependencyRules": {
"ignoreMissing": [
"@babel/*",
"expo-modules-*",
"typescript"
]
}
}
}

View File

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

171
packages/jazz-react-native/.gitignore vendored Normal file
View File

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

View File

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

View File

@@ -0,0 +1,9 @@
/** @type {import("prettier").Config} */
const config = {
trailingComma: "all",
tabWidth: 4,
semi: true,
singleQuote: false,
};
export default config;

View File

@@ -0,0 +1,907 @@
# jazz-browser
## 0.8.0
### Minor Changes
- bcec3be: Implement new top-level context creation and auth method API
### Patch Changes
- c2b62a0: Make anonymous auth work better
- 1a979b6: Implement guest auth without account
- Updated dependencies [6a147c2]
- Updated dependencies [ad40b88]
- Updated dependencies [23369dc]
- Updated dependencies [c2b62a0]
- Updated dependencies [1a979b6]
- Updated dependencies [bcec3be]
- cojson@0.8.0
- jazz-tools@0.8.0
- cojson-storage-indexeddb@0.8.0
- cojson-transport-ws@0.8.0
## 0.7.35-guest-auth.6
### Patch Changes
- Updated dependencies
- jazz-tools@0.7.35-guest-auth.6
## 0.7.35
### Patch Changes
- Updated dependencies [49a8b54]
- Updated dependencies [35bbcd9]
- Updated dependencies [6f80282]
- Updated dependencies [35bbcd9]
- Updated dependencies [f350e90]
- jazz-tools@0.7.35
- cojson@0.7.35
- cojson-storage-indexeddb@0.7.35
- cojson-transport-ws@0.7.35
## 0.7.34
### Patch Changes
- Updated dependencies [5d91f9f]
- Updated dependencies [5094e6d]
- Updated dependencies [b09589b]
- Updated dependencies [2c3a40c]
- Updated dependencies [406ab9b]
- Updated dependencies [4e16575]
- Updated dependencies [ea882ab]
- cojson@0.7.34
- cojson-transport-ws@0.7.34
- cojson-storage-indexeddb@0.7.34
- jazz-tools@0.7.34
## 0.7.34-neverthrow.8
### Patch Changes
- Updated dependencies
- cojson@0.7.34-neverthrow.8
- cojson-storage-indexeddb@0.7.34-neverthrow.8
- cojson-transport-ws@0.7.34-neverthrow.8
- jazz-tools@0.7.34-neverthrow.8
## 0.7.34-neverthrow.7
### Patch Changes
- Updated dependencies
- cojson@0.7.34-neverthrow.7
- cojson-storage-indexeddb@0.7.34-neverthrow.7
- cojson-transport-ws@0.7.34-neverthrow.7
- jazz-tools@0.7.34-neverthrow.7
## 0.7.34-neverthrow.4
### Patch Changes
- Updated dependencies
- cojson@0.7.34-neverthrow.4
- cojson-storage-indexeddb@0.7.34-neverthrow.4
- cojson-transport-ws@0.7.34-neverthrow.4
- jazz-tools@0.7.34-neverthrow.4
## 0.7.34-neverthrow.3
### Patch Changes
- Updated dependencies
- cojson@0.7.34-neverthrow.3
- cojson-storage-indexeddb@0.7.34-neverthrow.3
- cojson-transport-ws@0.7.34-neverthrow.3
- jazz-tools@0.7.34-neverthrow.3
## 0.7.34-neverthrow.2
### Patch Changes
- Updated dependencies
- cojson-transport-ws@0.7.34-neverthrow.2
## 0.7.34-neverthrow.1
### Patch Changes
- Updated dependencies
- cojson@0.7.34-neverthrow.1
- cojson-storage-indexeddb@0.7.34-neverthrow.1
- cojson-transport-ws@0.7.34-neverthrow.1
- jazz-tools@0.7.34-neverthrow.1
## 0.7.34-neverthrow.0
### Patch Changes
- Updated dependencies
- cojson@0.7.34-neverthrow.0
- cojson-storage-indexeddb@0.7.34-neverthrow.0
- cojson-transport-ws@0.7.34-neverthrow.0
- jazz-tools@0.7.34-neverthrow.0
## 0.7.33
### Patch Changes
- Updated dependencies [fdde8db]
- Updated dependencies [b297c93]
- Updated dependencies [07fe2b9]
- Updated dependencies [3bf5127]
- Updated dependencies [a8b74ff]
- Updated dependencies [db53161]
- cojson-transport-ws@0.7.33
- cojson@0.7.33
- cojson-storage-indexeddb@0.7.33
- jazz-tools@0.7.33
## 0.7.33-hotfixes.5
### Patch Changes
- Updated dependencies
- cojson@0.7.33-hotfixes.5
- cojson-storage-indexeddb@0.7.33-hotfixes.5
- cojson-transport-ws@0.7.33-hotfixes.5
- jazz-tools@0.7.33-hotfixes.5
## 0.7.33-hotfixes.4
### Patch Changes
- Updated dependencies
- cojson@0.7.33-hotfixes.4
- cojson-storage-indexeddb@0.7.33-hotfixes.4
- cojson-transport-ws@0.7.33-hotfixes.4
- jazz-tools@0.7.33-hotfixes.4
## 0.7.33-hotfixes.3
### Patch Changes
- Updated dependencies
- cojson-storage-indexeddb@0.7.33-hotfixes.3
- cojson-transport-ws@0.7.33-hotfixes.3
- cojson@0.7.33-hotfixes.3
- jazz-tools@0.7.33-hotfixes.3
## 0.7.33-hotfixes.2
### Patch Changes
- Updated dependencies
- cojson-transport-ws@0.7.33-hotfixes.2
## 0.7.33-hotfixes.1
### Patch Changes
- Updated dependencies
- cojson-transport-ws@0.7.33-hotfixes.1
## 0.7.33-hotfixes.0
### Patch Changes
- Updated dependencies
- cojson@0.7.33-hotfixes.0
- cojson-storage-indexeddb@0.7.33-hotfixes.0
- cojson-transport-ws@0.7.33-hotfixes.0
- jazz-tools@0.7.33-hotfixes.0
## 0.7.32
### Patch Changes
- Updated dependencies
- jazz-tools@0.7.32
## 0.7.31
### Patch Changes
- Updated dependencies
- Updated dependencies
- cojson-transport-ws@0.7.31
- cojson@0.7.31
- cojson-storage-indexeddb@0.7.31
- jazz-tools@0.7.31
## 0.7.30
### Patch Changes
- Updated dependencies
- cojson-transport-ws@0.7.30
## 0.7.29
### Patch Changes
- Updated dependencies
- cojson@0.7.29
- cojson-storage-indexeddb@0.7.29
- cojson-transport-ws@0.7.29
- jazz-tools@0.7.29
## 0.7.28
### Patch Changes
- Updated dependencies
- cojson@0.7.28
- cojson-storage-indexeddb@0.7.28
- cojson-transport-ws@0.7.28
- jazz-tools@0.7.28
## 0.7.27
### Patch Changes
- Updated dependencies
- cojson-transport-ws@0.7.27
## 0.7.26
### Patch Changes
- Remove Effect from jazz/cojson internals
- Updated dependencies
- cojson@0.7.26
- cojson-storage-indexeddb@0.7.26
- cojson-transport-ws@0.7.26
- jazz-tools@0.7.26
## 0.7.25
### Patch Changes
- Updated dependencies
- jazz-tools@0.7.25
## 0.7.24
### Patch Changes
- Updated dependencies
- jazz-tools@0.7.24
## 0.7.23
### Patch Changes
- Updated dependencies
- cojson@0.7.23
- jazz-tools@0.7.23
- cojson-storage-indexeddb@0.7.23
- cojson-transport-ws@0.7.23
## 0.7.22
### Patch Changes
- Updated dependencies
- cojson-transport-ws@0.7.22
## 0.7.21
### Patch Changes
- Updated dependencies
- jazz-tools@0.7.21
## 0.7.20
### Patch Changes
- Updated dependencies
- jazz-tools@0.7.20
## 0.7.19
### Patch Changes
- Updated dependencies
- jazz-tools@0.7.19
## 0.7.18
### Patch Changes
- Updated dependencies
- cojson@0.7.18
- cojson-storage-indexeddb@0.7.18
- cojson-transport-ws@0.7.18
- jazz-tools@0.7.18
## 0.7.17
### Patch Changes
- Updated dependencies
- cojson@0.7.17
- cojson-storage-indexeddb@0.7.17
- cojson-transport-ws@0.7.17
- jazz-tools@0.7.17
## 0.7.16
### Patch Changes
- Updated dependencies
- jazz-tools@0.7.16
## 0.7.14
### Patch Changes
- Updated dependencies
- cojson@0.7.14
- jazz-tools@0.7.14
- cojson-storage-indexeddb@0.7.14
- cojson-transport-ws@0.7.14
## 0.7.13
### Patch Changes
- Updated dependencies
- jazz-tools@0.7.13
## 0.7.12
### Patch Changes
- Updated dependencies
- jazz-tools@0.7.12
## 0.7.11
### Patch Changes
- Updated dependencies
- cojson@0.7.11
- cojson-storage-indexeddb@0.7.11
- jazz-tools@0.7.11
## 0.7.10
### Patch Changes
- Updated dependencies
- cojson@0.7.10
- cojson-storage-indexeddb@0.7.10
- jazz-tools@0.7.10
## 0.7.9
### Patch Changes
- Updated dependencies
- cojson@0.7.9
- cojson-storage-indexeddb@0.7.9
- jazz-tools@0.7.9
## 0.7.8
### Patch Changes
- Updated dependencies
- jazz-tools@0.7.8
## 0.7.6
### Patch Changes
- Updated dependencies
- jazz-tools@0.7.6
## 0.7.5
### Patch Changes
- Ability to add seed accounts to DemoAuth
## 0.7.3
### Patch Changes
- Updated dependencies
- jazz-tools@0.7.3
## 0.7.1
### Patch Changes
- Updated dependencies
- jazz-tools@0.7.1
## 0.7.0
### Minor Changes
- e299c3e: New simplified API
### Patch Changes
- 1a35307: WIP working-ish version of LSM storage
- 59c18c3: CoMap fix
- 8636319: Implement deep loading, simplify API
- d8fe2b1: Expose experimental OPFS storage
- c4151fc: Support stricter TS lint rules
- daee49c: Add missing @scure/bip39 dep
- 952982e: Consistent proxy based API
- d2e03ff: Fix variance of ID.\_\_type
- 354bdcd: Even friendlier for subclassing CoMap
- 60d5ca2: Clean up exports
- 69ac514: Use effect schema much less
- f0f6f1b: Clean up API more & re-add jazz-nodejs
- 1a44f87: Refactoring
- 627d895: Get rid of Co namespace
- 85d2b62: More subclass-friendly types in CoMap
- Updated dependencies [8636319]
- Updated dependencies [1a35307]
- Updated dependencies [8636319]
- Updated dependencies [1a35307]
- Updated dependencies [96c494f]
- Updated dependencies [59c18c3]
- Updated dependencies [19f52b7]
- Updated dependencies [8636319]
- Updated dependencies [d8fe2b1]
- Updated dependencies [19004b4]
- Updated dependencies [a78f168]
- Updated dependencies [1200aae]
- Updated dependencies [52675c9]
- Updated dependencies [129e2c1]
- Updated dependencies [1cfa279]
- Updated dependencies [704af7d]
- Updated dependencies [1a35307]
- Updated dependencies [460478f]
- Updated dependencies [6b0418f]
- Updated dependencies [e299c3e]
- Updated dependencies [ed5643a]
- Updated dependencies [bde684f]
- Updated dependencies [bf0f8ec]
- Updated dependencies [c4151fc]
- Updated dependencies [63374cc]
- Updated dependencies [8636319]
- Updated dependencies [01ac646]
- Updated dependencies [a5e68a4]
- Updated dependencies [952982e]
- Updated dependencies [1a35307]
- Updated dependencies [5fa277c]
- Updated dependencies [60d5ca2]
- Updated dependencies [21771c4]
- Updated dependencies [77c2b56]
- Updated dependencies [63374cc]
- Updated dependencies [d2e03ff]
- Updated dependencies [354bdcd]
- Updated dependencies [60d5ca2]
- Updated dependencies [69ac514]
- Updated dependencies [f8a5c46]
- Updated dependencies [f0f6f1b]
- Updated dependencies [e5eed5b]
- Updated dependencies [1a44f87]
- Updated dependencies [627d895]
- Updated dependencies [1200aae]
- Updated dependencies [63374cc]
- Updated dependencies [ece35b3]
- Updated dependencies [38d4410]
- Updated dependencies [85d2b62]
- Updated dependencies [fd86c11]
- Updated dependencies [52675c9]
- jazz-tools@0.7.0
- cojson@0.7.0
- cojson-storage-indexeddb@0.7.0
## 0.7.0-alpha.42
### Patch Changes
- Updated dependencies
- jazz-tools@0.7.0-alpha.42
- cojson@0.7.0-alpha.42
- cojson-storage-indexeddb@0.7.0-alpha.42
## 0.7.0-alpha.41
### Patch Changes
- jazz-tools@0.7.0-alpha.41
## 0.7.0-alpha.39
### Patch Changes
- Updated dependencies
- cojson@0.7.0-alpha.39
- cojson-storage-indexeddb@0.7.0-alpha.39
- jazz-tools@0.7.0-alpha.39
## 0.7.0-alpha.38
### Patch Changes
- Implement deep loading, simplify API
- Updated dependencies
- Updated dependencies
- Updated dependencies
- Updated dependencies
- jazz-tools@0.7.0-alpha.38
- cojson@0.7.0-alpha.38
- cojson-storage-indexeddb@0.7.0-alpha.38
## 0.7.0-alpha.37
### Patch Changes
- Expose experimental OPFS storage
- Updated dependencies
- cojson@0.7.0-alpha.37
- cojson-storage-indexeddb@0.7.0-alpha.37
- jazz-tools@0.7.0-alpha.37
## 0.7.0-alpha.36
### Patch Changes
- 1a35307: WIP working-ish version of LSM storage
- Updated dependencies [1a35307]
- Updated dependencies [1a35307]
- Updated dependencies [1a35307]
- Updated dependencies [6b0418f]
- Updated dependencies [1a35307]
- cojson@0.7.0-alpha.36
- jazz-tools@0.7.0-alpha.36
## 0.7.0-alpha.35
### Patch Changes
- Updated dependencies
- Updated dependencies
- cojson@0.7.0-alpha.35
- jazz-tools@0.7.0-alpha.35
- cojson-storage-indexeddb@0.7.0-alpha.35
## 0.7.0-alpha.34
### Patch Changes
- Updated dependencies
- jazz-tools@0.7.0-alpha.34
## 0.7.0-alpha.32
### Patch Changes
- Clean up exports
- Updated dependencies
- Updated dependencies
- jazz-tools@0.7.0-alpha.32
## 0.7.0-alpha.31
### Patch Changes
- Updated dependencies
- jazz-tools@0.7.0-alpha.31
## 0.7.0-alpha.30
### Patch Changes
- Updated dependencies
- jazz-tools@0.7.0-alpha.30
## 0.7.0-alpha.29
### Patch Changes
- Updated dependencies
- cojson-storage-indexeddb@0.7.0-alpha.29
- jazz-tools@0.7.0-alpha.29
- cojson@0.7.0-alpha.29
## 0.7.0-alpha.28
### Patch Changes
- Updated dependencies
- jazz-tools@0.7.0-alpha.28
- cojson@0.7.0-alpha.28
- cojson-storage-indexeddb@0.7.0-alpha.28
## 0.7.0-alpha.27
### Patch Changes
- Updated dependencies
- Updated dependencies
- jazz-tools@0.7.0-alpha.27
- cojson@0.7.0-alpha.27
- cojson-storage-indexeddb@0.7.0-alpha.27
## 0.7.0-alpha.26
### Patch Changes
- Updated dependencies
- jazz-tools@0.7.0-alpha.26
## 0.7.0-alpha.25
### Patch Changes
- Updated dependencies
- jazz-tools@0.7.0-alpha.25
## 0.7.0-alpha.24
### Patch Changes
- Updated dependencies
- Updated dependencies
- Updated dependencies
- jazz-tools@0.7.0-alpha.24
- cojson@0.7.0-alpha.24
- cojson-storage-indexeddb@0.7.0-alpha.24
## 0.7.0-alpha.23
### Patch Changes
- Updated dependencies
- jazz-tools@0.7.0-alpha.23
## 0.7.0-alpha.22
### Patch Changes
- Updated dependencies
- jazz-tools@0.7.0-alpha.22
## 0.7.0-alpha.21
### Patch Changes
- Updated dependencies
- jazz-tools@0.7.0-alpha.21
## 0.7.0-alpha.20
### Patch Changes
- Updated dependencies
- jazz-tools@0.7.0-alpha.20
## 0.7.0-alpha.19
### Patch Changes
- Updated dependencies
- jazz-tools@0.7.0-alpha.19
## 0.7.0-alpha.18
### Patch Changes
- Add missing @scure/bip39 dep
## 0.7.0-alpha.17
### Patch Changes
- Updated dependencies
- jazz-tools@0.7.0-alpha.17
## 0.7.0-alpha.16
### Patch Changes
- Updated dependencies
- jazz-tools@0.7.0-alpha.16
## 0.7.0-alpha.15
### Patch Changes
- Updated dependencies
- jazz-tools@0.7.0-alpha.15
## 0.7.0-alpha.14
### Patch Changes
- Updated dependencies
- jazz-tools@0.7.0-alpha.14
## 0.7.0-alpha.13
### Patch Changes
- Updated dependencies
- jazz-tools@0.7.0-alpha.13
## 0.7.0-alpha.12
### Patch Changes
- Fix variance of ID.\_\_type
- Updated dependencies
- jazz-tools@0.7.0-alpha.12
## 0.7.0-alpha.11
### Patch Changes
- Support stricter TS lint rules
- Updated dependencies
- cojson-storage-indexeddb@0.7.0-alpha.11
- jazz-tools@0.7.0-alpha.11
- cojson@0.7.0-alpha.11
## 0.7.0-alpha.10
### Patch Changes
- Clean up API more & re-add jazz-nodejs
- Updated dependencies
- cojson-storage-indexeddb@0.7.0-alpha.10
- jazz-tools@0.7.0-alpha.10
- cojson@0.7.0-alpha.10
## 0.7.0-alpha.9
### Patch Changes
- Even friendlier for subclassing CoMap
- Updated dependencies
- jazz-tools@0.7.0-alpha.9
## 0.7.0-alpha.8
### Patch Changes
- More subclass-friendly types in CoMap
- Updated dependencies
- jazz-tools@0.7.0-alpha.8
## 0.7.0-alpha.7
### Patch Changes
- Consistent proxy based API
- Updated dependencies
- cojson-storage-indexeddb@0.6.4-alpha.4
- jazz-tools@0.7.0-alpha.7
- cojson@0.7.0-alpha.7
## 0.7.0-alpha.6
### Patch Changes
- CoMap fix
- Updated dependencies
- jazz-tools@0.7.0-alpha.6
## 0.7.0-alpha.5
### Patch Changes
- Refactoring
- Updated dependencies
- cojson-storage-indexeddb@0.6.4-alpha.3
- jazz-tools@0.7.0-alpha.5
- cojson@0.7.0-alpha.5
## 0.7.0-alpha.4
### Patch Changes
- Updated dependencies
- jazz-tools@0.7.0-alpha.4
## 0.7.0-alpha.3
### Patch Changes
- Updated dependencies
- jazz-tools@0.7.0-alpha.3
## 0.7.0-alpha.2
### Patch Changes
- Get rid of Co namespace
- Updated dependencies
- cojson-storage-indexeddb@0.6.4-alpha.2
- jazz-tools@0.7.0-alpha.2
## 0.7.0-alpha.1
### Patch Changes
- Use effect schema much less
- Updated dependencies
- cojson-storage-indexeddb@0.6.4-alpha.1
- jazz-tools@0.7.0-alpha.1
- cojson@0.7.0-alpha.1
## 0.7.0-alpha.0
### Minor Changes
- New simplified API
### Patch Changes
- Updated dependencies
- jazz-tools@0.7.0-alpha.0
- cojson@0.7.0-alpha.0
- cojson-storage-indexeddb@0.6.4-alpha.0
## 0.6.3
### Patch Changes
- Fix migration changes being lost on loaded account
- Updated dependencies
- cojson@0.6.6
## 0.6.2
### Patch Changes
- Fix loading of accounts
- Updated dependencies
- cojson@0.6.5
- jazz-autosub@0.6.1
## 0.6.1
### Patch Changes
- IndexedDB & timer perf improvements
- Updated dependencies
- cojson@0.6.4
- cojson-storage-indexeddb@0.6.1
## 0.6.0
### Minor Changes
- Make addMember and removeMember take loaded Accounts instead of just IDs
### Patch Changes
- Updated dependencies
- cojson-storage-indexeddb@0.6.0
- jazz-autosub@0.6.0
- cojson@0.6.0
## 0.5.1
### Patch Changes
- Allow account migrations to be async
- Updated dependencies
- cojson@0.5.2
## 0.5.0
### Minor Changes
- Adding a lot of performance improvements to cojson, add a stresstest for the twit example and make that run smoother in a lot of ways.
### Patch Changes
- Updated dependencies
- cojson-storage-indexeddb@0.5.0
- jazz-autosub@0.5.0
- cojson@0.5.0

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,10 @@
# `jazz-browser`
These are browser bindings for Jazz (see [jazz.tools](https://jazz.tools)), a framework for distributed state.
Use this only if you want to write Jazz apps using plain JavaScript,
or to build your framework bindings for Jazz.
## Higher-level framework bindings:
- `jazz-react` - React bindings for Jazz

View File

@@ -0,0 +1,27 @@
{
"name": "jazz-react-native",
"version": "0.8.0",
"type": "module",
"main": "dist/index.js",
"types": "src/index.ts",
"license": "MIT",
"dependencies": {
"@scure/bip39": "^1.3.0",
"cojson": "workspace:0.8.0",
"cojson-storage-indexeddb": "workspace:0.8.0",
"cojson-transport-ws": "workspace:0.8.0",
"jazz-tools": "workspace:0.8.0",
"typescript": "^5.3.3"
},
"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"
},
"lint-staged": {
"*.{ts,tsx}": "eslint --fix",
"*.{js,jsx,mdx,json}": "prettier --write"
},
"gitHead": "33c27053293b4801b968c61d5c4c989f93a67d13"
}

View File

@@ -0,0 +1,348 @@
import { BlockFilename, FileSystem, WalFilename, CryptoProvider } from "cojson";
export class OPFSFilesystem
implements
FileSystem<
{ id: number; filename: string },
{ id: number; filename: string }
>
{
opfsWorker: Worker;
callbacks: Map<number, (event: MessageEvent) => void> = new Map();
nextRequestId = 0;
constructor(public crypto: CryptoProvider) {
this.opfsWorker = new Worker(
URL.createObjectURL(
new Blob([opfsWorkerJSSrc], { type: "text/javascript" }),
),
);
this.opfsWorker.onmessage = (event) => {
// console.log("Received from OPFS worker", event.data);
const handler = this.callbacks.get(event.data.requestId);
if (handler) {
handler(event);
this.callbacks.delete(event.data.requestId);
}
};
}
listFiles(): Promise<string[]> {
return new Promise((resolve) => {
const requestId = this.nextRequestId++;
performance.mark("listFiles" + requestId + "_listFiles");
this.callbacks.set(requestId, (event) => {
performance.mark("listFilesEnd" + requestId + "_listFiles");
performance.measure(
"listFiles" + requestId + "_listFiles",
"listFiles" + requestId + "_listFiles",
"listFilesEnd" + requestId + "_listFiles",
);
resolve(event.data.fileNames);
});
this.opfsWorker.postMessage({ type: "listFiles", requestId });
});
}
openToRead(
filename: string,
): Promise<{ handle: { id: number; filename: string }; size: number }> {
return new Promise((resolve) => {
const requestId = this.nextRequestId++;
performance.mark("openToRead" + "_" + filename);
this.callbacks.set(requestId, (event) => {
resolve({
handle: { id: event.data.handle, filename },
size: event.data.size,
});
performance.mark("openToReadEnd" + "_" + filename);
performance.measure(
"openToRead" + "_" + filename,
"openToRead" + "_" + filename,
"openToReadEnd" + "_" + filename,
);
});
this.opfsWorker.postMessage({
type: "openToRead",
filename,
requestId,
});
});
}
createFile(filename: string): Promise<{ id: number; filename: string }> {
return new Promise((resolve) => {
const requestId = this.nextRequestId++;
performance.mark("createFile" + "_" + filename);
this.callbacks.set(requestId, (event) => {
performance.mark("createFileEnd" + "_" + filename);
performance.measure(
"createFile" + "_" + filename,
"createFile" + "_" + filename,
"createFileEnd" + "_" + filename,
);
resolve({ id: event.data.handle, filename });
});
this.opfsWorker.postMessage({
type: "createFile",
filename,
requestId,
});
});
}
openToWrite(filename: string): Promise<{ id: number; filename: string }> {
return new Promise((resolve) => {
const requestId = this.nextRequestId++;
performance.mark("openToWrite" + "_" + filename);
this.callbacks.set(requestId, (event) => {
performance.mark("openToWriteEnd" + "_" + filename);
performance.measure(
"openToWrite" + "_" + filename,
"openToWrite" + "_" + filename,
"openToWriteEnd" + "_" + filename,
);
resolve({ id: event.data.handle, filename });
});
this.opfsWorker.postMessage({
type: "openToWrite",
filename,
requestId,
});
});
}
append(
handle: { id: number; filename: string },
data: Uint8Array,
): Promise<void> {
return new Promise((resolve) => {
const requestId = this.nextRequestId++;
performance.mark("append" + "_" + handle.filename);
this.callbacks.set(requestId, (_) => {
performance.mark("appendEnd" + "_" + handle.filename);
performance.measure(
"append" + "_" + handle.filename,
"append" + "_" + handle.filename,
"appendEnd" + "_" + handle.filename,
);
resolve(undefined);
});
this.opfsWorker.postMessage({
type: "append",
handle: handle.id,
data,
requestId,
});
});
}
read(
handle: { id: number; filename: string },
offset: number,
length: number,
): Promise<Uint8Array> {
return new Promise((resolve) => {
const requestId = this.nextRequestId++;
performance.mark("read" + "_" + handle.filename);
this.callbacks.set(requestId, (event) => {
performance.mark("readEnd" + "_" + handle.filename);
performance.measure(
"read" + "_" + handle.filename,
"read" + "_" + handle.filename,
"readEnd" + "_" + handle.filename,
);
resolve(event.data.data);
});
this.opfsWorker.postMessage({
type: "read",
handle: handle.id,
offset,
length,
requestId,
});
});
}
close(handle: { id: number; filename: string }): Promise<void> {
return new Promise((resolve) => {
const requestId = this.nextRequestId++;
performance.mark("close" + "_" + handle.filename);
this.callbacks.set(requestId, (_) => {
performance.mark("closeEnd" + "_" + handle.filename);
performance.measure(
"close" + "_" + handle.filename,
"close" + "_" + handle.filename,
"closeEnd" + "_" + handle.filename,
);
resolve(undefined);
});
this.opfsWorker.postMessage({
type: "close",
handle: handle.id,
requestId,
});
});
}
closeAndRename(
handle: { id: number; filename: string },
filename: BlockFilename,
): Promise<void> {
return new Promise((resolve) => {
const requestId = this.nextRequestId++;
performance.mark("closeAndRename" + "_" + handle.filename);
this.callbacks.set(requestId, () => {
performance.mark("closeAndRenameEnd" + "_" + handle.filename);
performance.measure(
"closeAndRename" + "_" + handle.filename,
"closeAndRename" + "_" + handle.filename,
"closeAndRenameEnd" + "_" + handle.filename,
);
resolve(undefined);
});
this.opfsWorker.postMessage({
type: "closeAndRename",
handle: handle.id,
filename,
requestId,
});
});
}
removeFile(filename: BlockFilename | WalFilename): Promise<void> {
return new Promise((resolve) => {
const requestId = this.nextRequestId++;
performance.mark("removeFile" + "_" + filename);
this.callbacks.set(requestId, () => {
performance.mark("removeFileEnd" + "_" + filename);
performance.measure(
"removeFile" + "_" + filename,
"removeFile" + "_" + filename,
"removeFileEnd" + "_" + filename,
);
resolve(undefined);
});
this.opfsWorker.postMessage({
type: "removeFile",
filename,
requestId,
});
});
}
}
const opfsWorkerJSSrc = `
let rootDirHandle;
const handlesByRequest = new Map();
const handlesByFilename = new Map();
const filenamesForHandles = new Map();
onmessage = async function handleEvent(event) {
rootDirHandle = rootDirHandle || await navigator.storage.getDirectory();
// console.log("Received in OPFS worker", {...event.data, data: event.data.data ? "some data of length " + event.data.data.length : undefined});
if (event.data.type === "listFiles") {
const fileNames = [];
for await (const entry of rootDirHandle.values()) {
if (entry.kind === "file") {
fileNames.push(entry.name);
}
}
postMessage({requestId: event.data.requestId, fileNames});
} else if (event.data.type === "openToRead" || event.data.type === "openToWrite") {
let syncHandle;
const existingHandle = handlesByFilename.get(event.data.filename);
if (existingHandle) {
throw new Error("Handle already exists for file: " + event.data.filename);
} else {
const handle = await rootDirHandle.getFileHandle(event.data.filename);
try {
syncHandle = await handle.createSyncAccessHandle();
} catch (e) {
throw new Error("Couldn't open file for reading: " + event.data.filename, {cause: e});
}
}
handlesByRequest.set(event.data.requestId, syncHandle);
handlesByFilename.set(event.data.filename, syncHandle);
filenamesForHandles.set(syncHandle, event.data.filename);
let size;
try {
size = syncHandle.getSize();
} catch (e) {
throw new Error("Couldn't get size of file: " + event.data.filename, {cause: e});
}
postMessage({requestId: event.data.requestId, handle: event.data.requestId, size});
} else if (event.data.type === "createFile") {
const handle = await rootDirHandle.getFileHandle(event.data.filename, {
create: true,
});
let syncHandle;
try {
syncHandle = await handle.createSyncAccessHandle();
} catch (e) {
throw new Error("Couldn't create file: " + event.data.filename, {cause: e});
}
handlesByRequest.set(event.data.requestId, syncHandle);
handlesByFilename.set(event.data.filename, syncHandle);
filenamesForHandles.set(syncHandle, event.data.filename);
postMessage({requestId: event.data.requestId, handle: event.data.requestId, result: "done"});
} else if (event.data.type === "append") {
const writable = handlesByRequest.get(event.data.handle);
writable.write(event.data.data, {at: writable.getSize()});
writable.flush();
postMessage({requestId: event.data.requestId, result: "done"});
} else if (event.data.type === "read") {
const readable = handlesByRequest.get(event.data.handle);
const buffer = new Uint8Array(event.data.length);
const read = readable.read(buffer, {at: event.data.offset});
if (read < event.data.length) {
throw new Error("Couldn't read enough");
}
postMessage({requestId: event.data.requestId, data: buffer, result: "done"});
} else if (event.data.type === "close") {
const handle = handlesByRequest.get(event.data.handle);
// console.log("Closing handle", filenamesForHandles.get(handle), event.data.handle, handle);
handle.flush();
handle.close();
handlesByRequest.delete(handle);
const filename = filenamesForHandles.get(handle);
handlesByFilename.delete(filename);
filenamesForHandles.delete(handle);
postMessage({requestId: event.data.requestId, result: "done"});
} else if (event.data.type === "closeAndRename") {
const handle = handlesByRequest.get(event.data.handle);
handle.flush();
const buffer = new Uint8Array(handle.getSize());
const read = handle.read(buffer, {at: 0});
if (read < buffer.length) {
throw new Error("Couldn't read enough " + read + ", " + handle.getSize());
}
handle.close();
const oldFilename = filenamesForHandles.get(handle);
await rootDirHandle.removeEntry(oldFilename);
const newHandle = await rootDirHandle.getFileHandle(event.data.filename, { create: true });
let writable;
try {
writable = await newHandle.createSyncAccessHandle();
} catch (e) {
throw new Error("Couldn't create file (to rename to): " + event.data.filename, { cause: e })
}
writable.write(buffer);
writable.close();
postMessage({requestId: event.data.requestId, result: "done"});
} else if (event.data.type === "removeFile") {
try {
await rootDirHandle.removeEntry(event.data.filename);
} catch(e) {
throw new Error("Couldn't remove file: " + event.data.filename, { cause: e });
}
postMessage({requestId: event.data.requestId, result: "done"});
} else {
console.error("Unknown event type", event.data.type);
}
};
//# sourceURL=opfsWorker.js
`;

View File

@@ -0,0 +1,158 @@
import { AgentSecret } from "cojson";
import { Account, AuthMethod, AuthResult, ID } from "jazz-tools";
type StorageData = {
accountID: ID<Account>;
accountSecret: AgentSecret;
};
const localStorageKey = "demo-auth-logged-in-secret";
export class BrowserDemoAuth implements AuthMethod {
constructor(
public driver: BrowserDemoAuth.Driver,
seedAccounts?: {
[name: string]: {
accountID: ID<Account>;
accountSecret: AgentSecret;
};
},
) {
for (const [name, credentials] of Object.entries(seedAccounts || {})) {
const storageData = JSON.stringify(
credentials satisfies StorageData,
);
if (
!(
localStorage["demo-auth-existing-users"]?.split(",") as
| string[]
| undefined
)?.includes(name)
) {
localStorage["demo-auth-existing-users"] = localStorage[
"demo-auth-existing-users"
]
? localStorage["demo-auth-existing-users"] + "," + name
: name;
}
localStorage["demo-auth-existing-users-" + name] = storageData;
}
}
async start() {
if (localStorage["demo-auth-logged-in-secret"]) {
const localStorageData = JSON.parse(
localStorage[localStorageKey],
) as StorageData;
const accountID = localStorageData.accountID as ID<Account>;
const secret = localStorageData.accountSecret;
return {
type: "existing",
credentials: { accountID, secret },
onSuccess: () => {
this.driver.onSignedIn({ logOut });
},
onError: (error: string | Error) => {
this.driver.onError(error)
},
logOut: () => {
delete localStorage[localStorageKey];
}
} satisfies AuthResult;
} else {
return new Promise<AuthResult>((resolve) => {
this.driver.onReady({
signUp: async (username) => {
resolve({
type: "new",
creationProps: { name: username },
saveCredentials: async (credentials: {
accountID: ID<Account>;
secret: AgentSecret;
}) => {
const storageData = JSON.stringify({
accountID: credentials.accountID,
accountSecret: credentials.secret,
} satisfies StorageData);
localStorage["demo-auth-logged-in-secret"] =
storageData;
localStorage[
"demo-auth-existing-users-" + username
] = storageData;
localStorage["demo-auth-existing-users"] =
localStorage["demo-auth-existing-users"]
? localStorage[
"demo-auth-existing-users"
] +
"," +
username
: username;
},
onSuccess: () => {
this.driver.onSignedIn({ logOut });
},
onError: (error: string | Error) => {
this.driver.onError(error)
},
logOut: () => {
delete localStorage[localStorageKey];
}
});
},
existingUsers:
localStorage["demo-auth-existing-users"]?.split(",") ??
[],
logInAs: async (existingUser) => {
const storageData = JSON.parse(
localStorage[
"demo-auth-existing-users-" + existingUser
],
) as StorageData;
localStorage["demo-auth-logged-in-secret"] =
JSON.stringify(storageData);
resolve({
type: "existing",
credentials: {
accountID: storageData.accountID,
secret: storageData.accountSecret,
},
onSuccess: () => {
this.driver.onSignedIn({ logOut });
},
onError: (error: string | Error) => {
this.driver.onError(error)
},
logOut: () => {
delete localStorage[localStorageKey];
}
});
},
});
});
}
}
}
/** @category Auth Providers */
// eslint-disable-next-line @typescript-eslint/no-namespace
export namespace BrowserDemoAuth {
export interface Driver {
onReady: (next: {
signUp: (username: string) => Promise<void>;
existingUsers: string[];
logInAs: (existingUser: string) => Promise<void>;
}) => void;
onSignedIn: (next: { logOut: () => void }) => void;
onError: (error: string | Error) => void;
}
}
function logOut() {
delete localStorage[localStorageKey];
}

View File

@@ -0,0 +1,192 @@
import {
RawAccountID,
AgentSecret,
cojsonInternals,
CryptoProvider,
} from "cojson";
import { Account, AuthMethod, AuthResult, ID } from "jazz-tools";
type LocalStorageData = {
accountID: ID<Account>;
accountSecret: AgentSecret;
};
const localStorageKey = "jazz-logged-in-secret";
export class BrowserPasskeyAuth implements AuthMethod {
constructor(
public driver: BrowserPasskeyAuth.Driver,
public appName: string,
// TODO: is this a safe default?
public appHostname: string = window.location.hostname,
) {}
accountLoaded() {
this.driver.onSignedIn({ logOut });
}
onError(error: string | Error) {
this.driver.onError(error);
}
async start(crypto: CryptoProvider): Promise<AuthResult> {
if (localStorage[localStorageKey]) {
const localStorageData = JSON.parse(
localStorage[localStorageKey],
) as LocalStorageData;
const accountID = localStorageData.accountID as ID<Account>;
const secret = localStorageData.accountSecret;
return {
type: "existing",
credentials: { accountID, secret },
onSuccess: () => {
this.driver.onSignedIn({ logOut });
},
onError: (error: string | Error) => {
this.driver.onError(error);
},
logOut: () => {
delete localStorage[localStorageKey];
},
} satisfies AuthResult;
} else {
return new Promise<AuthResult>((resolve) => {
this.driver.onReady({
signUp: async (username) => {
const secretSeed = crypto.newRandomSecretSeed();
resolve({
type: "new",
creationProps: { name: username },
initialSecret:
crypto.agentSecretFromSecretSeed(secretSeed),
saveCredentials: async ({ accountID, secret }) => {
const webAuthNCredentialPayload =
new Uint8Array(
cojsonInternals.secretSeedLength +
cojsonInternals.shortHashLength,
);
webAuthNCredentialPayload.set(secretSeed);
webAuthNCredentialPayload.set(
cojsonInternals.rawCoIDtoBytes(
accountID as unknown as RawAccountID,
),
cojsonInternals.secretSeedLength,
);
await navigator.credentials.create({
publicKey: {
challenge: Uint8Array.from([0, 1, 2]),
rp: {
name: this.appName,
id: this.appHostname,
},
user: {
id: webAuthNCredentialPayload,
name:
username +
` (${new Date().toLocaleString()})`,
displayName: username,
},
pubKeyCredParams: [
{ alg: -7, type: "public-key" },
],
authenticatorSelection: {
authenticatorAttachment: "platform",
},
timeout: 60000,
attestation: "direct",
},
});
localStorage[localStorageKey] = JSON.stringify({
accountID,
accountSecret: secret,
} satisfies LocalStorageData);
},
onSuccess: () => {
this.driver.onSignedIn({ logOut });
},
onError: (error: string | Error) => {
this.driver.onError(error);
},
logOut: () => {
delete localStorage[localStorageKey];
},
});
},
logIn: async () => {
const webAuthNCredential =
(await navigator.credentials.get({
publicKey: {
challenge: Uint8Array.from([0, 1, 2]),
rpId: this.appHostname,
allowCredentials: [],
timeout: 60000,
},
})) as unknown as {
response: { userHandle: ArrayBuffer };
};
if (!webAuthNCredential) {
throw new Error("Couldn't log in");
}
const webAuthNCredentialPayload = new Uint8Array(
webAuthNCredential.response.userHandle,
);
const accountSecretSeed =
webAuthNCredentialPayload.slice(
0,
cojsonInternals.secretSeedLength,
);
const secret =
crypto.agentSecretFromSecretSeed(accountSecretSeed);
const accountID = cojsonInternals.rawCoIDfromBytes(
webAuthNCredentialPayload.slice(
cojsonInternals.secretSeedLength,
cojsonInternals.secretSeedLength +
cojsonInternals.shortHashLength,
),
) as ID<Account>;
resolve({
type: "existing",
credentials: { accountID, secret },
onSuccess: () => {
this.driver.onSignedIn({ logOut });
},
onError: (error: string | Error) => {
this.driver.onError(error);
},
logOut: () => {
delete localStorage[localStorageKey];
},
});
},
});
});
}
}
}
/** @category Auth Providers */
// eslint-disable-next-line @typescript-eslint/no-namespace
export namespace BrowserPasskeyAuth {
export interface Driver {
onReady: (next: {
signUp: (username: string) => Promise<void>;
logIn: () => Promise<void>;
}) => void;
onSignedIn: (next: { logOut: () => void }) => void;
onError: (error: string | Error) => void;
}
}
function logOut() {
delete localStorage[localStorageKey];
}

View File

@@ -0,0 +1,144 @@
import { AgentSecret, cojsonInternals, CryptoProvider } from "cojson";
import {
Account,
AuthMethod,
AuthResult,
ID,
} from "jazz-tools";
import * as bip39 from "@scure/bip39";
type LocalStorageData = {
accountID: ID<Account>;
accountSecret: AgentSecret;
};
const localStorageKey = "jazz-logged-in-secret";
export class BrowserPassphraseAuth implements AuthMethod {
constructor(
public driver: BrowserPassphraseAuth.Driver,
public wordlist: string[],
public appName: string,
// TODO: is this a safe default?
public appHostname: string = window.location.hostname,
) {}
async start(crypto: CryptoProvider): Promise<AuthResult> {
if (localStorage[localStorageKey]) {
const localStorageData = JSON.parse(
localStorage[localStorageKey],
) as LocalStorageData;
const accountID = localStorageData.accountID as ID<Account>;
const secret = localStorageData.accountSecret;
return {
type: "existing",
credentials: { accountID, secret },
onSuccess: () => {
this.driver.onSignedIn({ logOut });
},
onError: (error: string | Error) => {
this.driver.onError(error);
},
logOut: () => {
delete localStorage[localStorageKey];
},
} satisfies AuthResult;
} else {
return new Promise<AuthResult>((resolve) => {
this.driver.onReady({
signUp: async (username, passphrase) => {
const secretSeed = bip39.mnemonicToEntropy(
passphrase,
this.wordlist,
);
const accountSecret =
crypto.agentSecretFromSecretSeed(secretSeed);
if (!accountSecret) {
this.driver.onError("Invalid passphrase");
return;
}
resolve({
type: "new",
creationProps: { name: username },
initialSecret: accountSecret,
saveCredentials: async (credentials) => {
localStorage[localStorageKey] = JSON.stringify({
accountID: credentials.accountID,
accountSecret: credentials.secret,
} satisfies LocalStorageData);
},
onSuccess: () => {
this.driver.onSignedIn({ logOut });
},
onError: (error: string | Error) => {
this.driver.onError(error);
},
logOut: () => {
delete localStorage[localStorageKey];
},
});
},
logIn: async (passphrase: string) => {
const secretSeed = bip39.mnemonicToEntropy(
passphrase,
this.wordlist,
);
const accountSecret =
crypto.agentSecretFromSecretSeed(secretSeed);
if (!accountSecret) {
this.driver.onError("Invalid passphrase");
return;
}
const accountID = cojsonInternals.idforHeader(
cojsonInternals.accountHeaderForInitialAgentSecret(
accountSecret,
crypto,
),
crypto,
) as ID<Account>;
resolve({
type: "existing",
credentials: { accountID, secret: accountSecret },
onSuccess: () => {
localStorage[localStorageKey] = JSON.stringify({
accountID,
accountSecret,
} satisfies LocalStorageData);
this.driver.onSignedIn({ logOut });
},
onError: (error: string | Error) => {
this.driver.onError(error);
},
logOut: () => {
delete localStorage[localStorageKey];
},
});
},
});
});
}
}
}
/** @category Auth Providers */
// eslint-disable-next-line @typescript-eslint/no-namespace
export namespace BrowserPassphraseAuth {
export interface Driver {
onReady: (next: {
signUp: (username: string, passphrase: string) => Promise<void>;
logIn: (passphrase: string) => Promise<void>;
}) => void;
onSignedIn: (next: { logOut: () => void }) => void;
onError: (error: string | Error) => void;
}
}
function logOut() {
delete localStorage[localStorageKey];
}

View File

@@ -0,0 +1,362 @@
import {
CoValue,
ID,
AgentID,
SessionID,
cojsonInternals,
InviteSecret,
Account,
CoValueClass,
WasmCrypto,
CryptoProvider,
AuthMethod,
createJazzContext,
AnonymousJazzAgent,
} from "jazz-tools";
import { RawAccountID, LSMStorage } from "cojson";
import { OPFSFilesystem } from "./OPFSFilesystem.js";
import { IDBStorage } from "cojson-storage-indexeddb";
import { createWebSocketPeer } from "cojson-transport-ws";
export { BrowserDemoAuth } from "./auth/DemoAuth.js";
export { BrowserPasskeyAuth } from "./auth/PasskeyAuth.js";
export { BrowserPassphraseAuth } from "./auth/PassphraseAuth.js";
/** @category Context Creation */
export type BrowserContext<Acc extends Account> = {
me: Acc;
logOut: () => void;
// TODO: Symbol.dispose?
done: () => void;
};
export type BrowserGuestContext = {
guest: AnonymousJazzAgent;
logOut: () => void;
done: () => void;
};
export type BrowserContextOptions<Acc extends Account> = {
auth: AuthMethod;
AccountSchema: CoValueClass<Acc> & {
fromNode: (typeof Account)["fromNode"];
};
} & BaseBrowserContextOptions;
export type BaseBrowserContextOptions = {
peer: `wss://${string}` | `ws://${string}`;
reconnectionTimeout?: number;
storage?: "indexedDB" | "singleTabOPFS";
crypto?: CryptoProvider;
};
/** @category Context Creation */
export async function createJazzBrowserContext<Acc extends Account>(
options: BrowserContextOptions<Acc>,
): Promise<BrowserContext<Acc>>;
export async function createJazzBrowserContext(
options: BaseBrowserContextOptions,
): Promise<BrowserGuestContext>;
export async function createJazzBrowserContext<Acc extends Account>(
options: BrowserContextOptions<Acc> | BaseBrowserContextOptions,
): Promise<BrowserContext<Acc> | BrowserGuestContext>;
export async function createJazzBrowserContext<Acc extends Account>(
options: BrowserContextOptions<Acc> | BaseBrowserContextOptions,
): 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",
});
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
? await createJazzContext({
AccountSchema: options.AccountSchema,
auth: options.auth,
crypto: await WasmCrypto.create(),
peersToLoadFrom: [
options.storage === "singleTabOPFS"
? await LSMStorage.asPeer({
fs: new OPFSFilesystem(crypto),
// trace: true,
})
: await IDBStorage.asPeer(),
firstWsPeer,
],
sessionProvider: provideBroswerLockSession,
})
: await createJazzContext({
crypto: await WasmCrypto.create(),
peersToLoadFrom: [
options.storage === "singleTabOPFS"
? await LSMStorage.asPeer({
fs: new OPFSFilesystem(crypto),
// trace: true,
})
: await IDBStorage.asPeer(),
firstWsPeer,
],
});
const node =
"account" in context
? 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);
context.done();
},
logOut: () => {
context.logOut();
},
}
: {
guest: context.agent,
done: () => {
shouldTryToReconnect = false;
window.removeEventListener("online", onOnline);
context.done();
},
logOut: () => {
context.logOut();
},
};
}
/** @category Auth Providers */
export type SessionProvider = (
accountID: ID<Account> | AgentID,
) => Promise<SessionID>;
export function provideBroswerLockSession(
accountID: ID<Account> | AgentID,
crypto: CryptoProvider,
) {
let sessionDone!: () => void;
const donePromise = new Promise<void>((resolve) => {
sessionDone = resolve;
});
let resolveSession: (sessionID: SessionID) => void;
const sessionPromise = new Promise<SessionID>((resolve) => {
resolveSession = resolve;
});
void (async function () {
for (let idx = 0; idx < 100; idx++) {
// To work better around StrictMode
for (let retry = 0; retry < 2; retry++) {
// console.debug("Trying to get lock", accountID + "_" + idx);
const sessionFinishedOrNoLock = await navigator.locks.request(
accountID + "_" + idx,
{ ifAvailable: true },
async (lock) => {
if (!lock) return "noLock";
const sessionID =
localStorage[accountID + "_" + idx] ||
crypto.newRandomSessionID(
accountID as RawAccountID | AgentID,
);
localStorage[accountID + "_" + idx] = sessionID;
// console.debug(
// "Got lock",
// accountID + "_" + idx,
// sessionID
// );
resolveSession(sessionID);
await donePromise;
console.log(
"Done with lock",
accountID + "_" + idx,
sessionID,
);
return "sessionFinished";
},
);
if (sessionFinishedOrNoLock === "sessionFinished") {
return;
}
}
}
throw new Error("Couldn't get lock on session after 100x2 tries");
})();
return sessionPromise.then((sessionID) => ({
sessionID,
sessionDone,
}));
}
/** @category Invite Links */
export function createInviteLink<C extends CoValue>(
value: C,
role: "reader" | "writer" | "admin",
// default to same address as window.location, but without hash
{
baseURL = window.location.href.replace(/#.*$/, ""),
valueHint,
}: { baseURL?: string; valueHint?: string } = {},
): string {
const coValueCore = value._raw.core;
let currentCoValue = coValueCore;
while (currentCoValue.header.ruleset.type === "ownedByGroup") {
currentCoValue = currentCoValue.getGroup().core;
}
if (currentCoValue.header.ruleset.type !== "group") {
throw new Error("Can't create invite link for object without group");
}
const group = cojsonInternals.expectGroup(
currentCoValue.getCurrentContent(),
);
const inviteSecret = group.createInvite(role);
return `${baseURL}#/invite/${valueHint ? valueHint + "/" : ""}${
value.id
}/${inviteSecret}`;
}
/** @category Invite Links */
export function parseInviteLink<C extends CoValue>(
inviteURL: string,
):
| {
valueID: ID<C>;
valueHint?: string;
inviteSecret: InviteSecret;
}
| undefined {
const url = new URL(inviteURL);
const parts = url.hash.split("/");
let valueHint: string | undefined;
let valueID: ID<C> | undefined;
let inviteSecret: InviteSecret | undefined;
if (parts[0] === "#" && parts[1] === "invite") {
if (parts.length === 5) {
valueHint = parts[2];
valueID = parts[3] as ID<C>;
inviteSecret = parts[4] as InviteSecret;
} else if (parts.length === 4) {
valueID = parts[2] as ID<C>;
inviteSecret = parts[3] as InviteSecret;
}
if (!valueID || !inviteSecret) {
return undefined;
}
return { valueID, inviteSecret, valueHint };
}
}
/** @category Invite Links */
export function consumeInviteLinkFromWindowLocation<V extends CoValue>({
as,
forValueHint,
invitedObjectSchema,
}: {
as: Account;
forValueHint?: string;
invitedObjectSchema: CoValueClass<V>;
}): Promise<
| {
valueID: ID<V>;
valueHint?: string;
inviteSecret: InviteSecret;
}
| undefined
> {
return new Promise((resolve, reject) => {
const result = parseInviteLink<V>(window.location.href);
if (result && result.valueHint === forValueHint) {
as.acceptInvite(
result.valueID,
result.inviteSecret,
invitedObjectSchema,
)
.then(() => {
resolve(result);
window.history.replaceState(
{},
"",
window.location.href.replace(/#.*$/, ""),
);
})
.catch(reject);
} else {
resolve(undefined);
}
});
}

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/**/*"],
}

6087
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff