Compare commits
66 Commits
cojson-sto
...
chat-rn@1.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
47ad35085e | ||
|
|
0b0d06ea44 | ||
|
|
ec546b4cd6 | ||
|
|
cadc5ef913 | ||
|
|
f961dedbd0 | ||
|
|
c7b27e902b | ||
|
|
b1cbc9283a | ||
|
|
f2c7c20a75 | ||
|
|
53705d0ac1 | ||
|
|
4031b7532f | ||
|
|
5ac43c300d | ||
|
|
064174501e | ||
|
|
b0c2a5a53f | ||
|
|
24d9a6b7e1 | ||
|
|
e270295387 | ||
|
|
67fa7be0d4 | ||
|
|
3431076350 | ||
|
|
e8092141e8 | ||
|
|
a2aac6791d | ||
|
|
aebd1519c3 | ||
|
|
2e0378639c | ||
|
|
5ba76eeab5 | ||
|
|
279fc1c390 | ||
|
|
865b0e81a7 | ||
|
|
829ab08873 | ||
|
|
2c9b08a080 | ||
|
|
48bda8854f | ||
|
|
2858db7419 | ||
|
|
96ed9adf59 | ||
|
|
5e4905ca99 | ||
|
|
1d4949b70c | ||
|
|
7dacfd03f9 | ||
|
|
bd4191520e | ||
|
|
e3dfb1b06e | ||
|
|
7de210f225 | ||
|
|
d456a8c124 | ||
|
|
1676ff852a | ||
|
|
2217e12ba6 | ||
|
|
a8af6efe1a | ||
|
|
519eda0ac2 | ||
|
|
a8725abfb4 | ||
|
|
2229e5a64f | ||
|
|
ee11b30d3a | ||
|
|
ef78d58729 | ||
|
|
40e2dd0ece | ||
|
|
b60c7c1ce0 | ||
|
|
69cd362114 | ||
|
|
526bf0a3cf | ||
|
|
439f0fe57e | ||
|
|
686433f42e | ||
|
|
3ba258e181 | ||
|
|
d6e143e4d5 | ||
|
|
3e6229da4d | ||
|
|
adfc9a6032 | ||
|
|
1a6879b1c2 | ||
|
|
172fec56f6 | ||
|
|
13892071f5 | ||
|
|
b62d75b847 | ||
|
|
beafbd3088 | ||
|
|
653d8ba69f | ||
|
|
6c23dab790 | ||
|
|
80530a4065 | ||
|
|
e14e61f7d9 | ||
|
|
a8466946d3 | ||
|
|
02a240ce75 | ||
|
|
6b781cf4a6 |
@@ -15,6 +15,7 @@
|
||||
"jazz-browser-media-images",
|
||||
"jazz-expo",
|
||||
"jazz-inspector",
|
||||
"jazz-inspector-element",
|
||||
"jazz-nodejs",
|
||||
"jazz-react",
|
||||
"jazz-react-core",
|
||||
|
||||
@@ -1,5 +1,46 @@
|
||||
# chat-rn-expo-clerk
|
||||
|
||||
## 1.0.115
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [02a240c]
|
||||
- jazz-tools@0.13.23
|
||||
- jazz-expo@0.13.23
|
||||
- jazz-react-native-media-images@0.13.23
|
||||
|
||||
## 1.0.114
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-expo@0.13.22
|
||||
|
||||
## 1.0.113
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-expo@0.13.21
|
||||
- jazz-tools@0.13.21
|
||||
- jazz-react-native-media-images@0.13.21
|
||||
|
||||
## 1.0.112
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [439f0fe]
|
||||
- jazz-tools@0.13.20
|
||||
- jazz-expo@0.13.20
|
||||
- jazz-react-native-media-images@0.13.20
|
||||
|
||||
## 1.0.111
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [80530a4]
|
||||
- jazz-tools@0.13.19
|
||||
- jazz-expo@0.13.19
|
||||
- jazz-react-native-media-images@0.13.19
|
||||
|
||||
## 1.0.110
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "chat-rn-expo-clerk",
|
||||
"main": "index.js",
|
||||
"version": "1.0.110",
|
||||
"version": "1.0.115",
|
||||
"scripts": {
|
||||
"build": "expo export -p ios",
|
||||
"start": "expo start",
|
||||
|
||||
@@ -1,5 +1,42 @@
|
||||
# chat-rn-expo
|
||||
|
||||
## 1.0.102
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [02a240c]
|
||||
- jazz-tools@0.13.23
|
||||
- jazz-expo@0.13.23
|
||||
|
||||
## 1.0.101
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-expo@0.13.22
|
||||
|
||||
## 1.0.100
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-expo@0.13.21
|
||||
- jazz-tools@0.13.21
|
||||
|
||||
## 1.0.99
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [439f0fe]
|
||||
- jazz-tools@0.13.20
|
||||
- jazz-expo@0.13.20
|
||||
|
||||
## 1.0.98
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [80530a4]
|
||||
- jazz-tools@0.13.19
|
||||
- jazz-expo@0.13.19
|
||||
|
||||
## 1.0.97
|
||||
|
||||
### Patch Changes
|
||||
|
||||
55
examples/chat-rn-expo/app.config.js
Normal file
55
examples/chat-rn-expo/app.config.js
Normal file
@@ -0,0 +1,55 @@
|
||||
const { withBuildProperties } = require("expo-build-properties");
|
||||
const { withDangerousMod } = require("@expo/config-plugins");
|
||||
const fs = require("fs/promises");
|
||||
const path = require("path");
|
||||
|
||||
/**
|
||||
* https://github.com/mrousavy/nitro/issues/422#issuecomment-2545988256
|
||||
*/
|
||||
function withCustomIosMod(config) {
|
||||
// Use expo-build-properties to bump iOS deployment target
|
||||
config = withBuildProperties(config, { ios: { deploymentTarget: "16.0" } });
|
||||
// Patch the generated Podfile fallback to ensure platform is always 16.0
|
||||
config = withDangerousMod(config, [
|
||||
"ios",
|
||||
async (modConfig) => {
|
||||
const podfilePath = path.join(
|
||||
modConfig.modRequest.platformProjectRoot,
|
||||
"Podfile",
|
||||
);
|
||||
let contents = await fs.readFile(podfilePath, "utf-8");
|
||||
|
||||
// Check if the IPHONEOS_DEPLOYMENT_TARGET setting is already present
|
||||
// We search for the key being assigned, e.g., config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] =
|
||||
const deploymentTargetSettingExists =
|
||||
/\.build_settings\s*\[\s*['"]IPHONEOS_DEPLOYMENT_TARGET['"]\s*\]\s*=/.test(
|
||||
contents,
|
||||
);
|
||||
|
||||
if (!deploymentTargetSettingExists) {
|
||||
// IPHONEOS_DEPLOYMENT_TARGET setting not found, proceed to add it.
|
||||
contents = contents.replace(
|
||||
/(post_install\s+do\s+\|installer\|[\s\S]*?)(\r?\n\s end\s*)$/m,
|
||||
`$1
|
||||
|
||||
# Expo Build Properties: force deployment target
|
||||
# https://github.com/mrousavy/nitro/issues/422#issuecomment-2545988256
|
||||
installer.pods_project.targets.each do |target|
|
||||
target.build_configurations.each do |config|
|
||||
config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '16.0'
|
||||
end
|
||||
end
|
||||
$2`,
|
||||
);
|
||||
}
|
||||
|
||||
await fs.writeFile(podfilePath, contents);
|
||||
return modConfig;
|
||||
},
|
||||
]);
|
||||
return config;
|
||||
}
|
||||
|
||||
module.exports = ({ config }) => {
|
||||
return withCustomIosMod(config);
|
||||
};
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "chat-rn-expo",
|
||||
"version": "1.0.97",
|
||||
"version": "1.0.102",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"build": "expo export -p ios",
|
||||
@@ -36,6 +36,8 @@
|
||||
"react-dom": "18.3.1",
|
||||
"react-native": "0.76.7",
|
||||
"react-native-get-random-values": "^1.11.0",
|
||||
"react-native-nitro-modules": "0.25.2",
|
||||
"react-native-quick-crypto": "1.0.0-beta.15",
|
||||
"react-native-safe-area-context": "4.12.0",
|
||||
"react-native-screens": "4.4.0",
|
||||
"react-native-url-polyfill": "^2.0.0",
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
} from "@react-navigation/native";
|
||||
import { createNativeStackNavigator } from "@react-navigation/native-stack";
|
||||
import * as Linking from "expo-linking";
|
||||
import { RNQuickCrypto } from "jazz-expo/crypto";
|
||||
import React, { StrictMode, useEffect, useState } from "react";
|
||||
import HandleInviteScreen from "./invite";
|
||||
|
||||
@@ -46,6 +47,7 @@ function App() {
|
||||
return (
|
||||
<StrictMode>
|
||||
<JazzProvider
|
||||
CryptoProvider={RNQuickCrypto}
|
||||
sync={{
|
||||
peer: `wss://cloud.jazz.tools/?key=${apiKey}`,
|
||||
}}
|
||||
|
||||
@@ -20,6 +20,7 @@ import { Chat, Message } from "./schema";
|
||||
export default function ChatScreen({ navigation }: { navigation: any }) {
|
||||
const { me, logOut } = useAccount();
|
||||
const [chatId, setChatId] = useState<ID<Chat>>();
|
||||
const [chatIdInput, setChatIdInput] = useState<string>();
|
||||
const loadedChat = useCoState(Chat, chatId, { resolve: { $each: true } });
|
||||
const [message, setMessage] = useState("");
|
||||
const profile = useCoState(Profile, me._refs.profile?.id, {});
|
||||
@@ -57,27 +58,11 @@ export default function ChatScreen({ navigation }: { navigation: any }) {
|
||||
};
|
||||
|
||||
const joinChat = () => {
|
||||
Alert.prompt(
|
||||
"Join Chat",
|
||||
"Enter the Chat ID (example: co_zBGEHYvRfGuT2YSBraY3njGjnde)",
|
||||
[
|
||||
{
|
||||
text: "Cancel",
|
||||
style: "cancel",
|
||||
},
|
||||
{
|
||||
text: "Join",
|
||||
onPress: (chatId) => {
|
||||
if (chatId) {
|
||||
setChatId(chatId as ID<Chat>);
|
||||
} else {
|
||||
Alert.alert("Error", "Chat ID cannot be empty.");
|
||||
}
|
||||
},
|
||||
},
|
||||
],
|
||||
"plain-text",
|
||||
);
|
||||
if (chatIdInput) {
|
||||
setChatId(chatIdInput as ID<Chat>);
|
||||
} else {
|
||||
Alert.alert("Error", "Chat ID cannot be empty.");
|
||||
}
|
||||
};
|
||||
|
||||
const sendMessage = () => {
|
||||
@@ -160,9 +145,25 @@ export default function ChatScreen({ navigation }: { navigation: any }) {
|
||||
>
|
||||
<Text className="text-white font-semibold">Start new chat</Text>
|
||||
</TouchableOpacity>
|
||||
<Text className="text-m font-bold mt-6">Join existing chat</Text>
|
||||
<TextInput
|
||||
className="rounded h-12 p-2 m-2 mt-4 w-80 border border-gray-200 block"
|
||||
placeholder="Chat ID"
|
||||
value={chatIdInput ?? ""}
|
||||
onChangeText={(value) => {
|
||||
setChatIdInput(value);
|
||||
}}
|
||||
textAlignVertical="center"
|
||||
onSubmitEditing={() => {
|
||||
if (chatIdInput) {
|
||||
setChatId(chatIdInput as ID<Chat>);
|
||||
}
|
||||
}}
|
||||
testID="chat-id-input"
|
||||
/>
|
||||
<TouchableOpacity
|
||||
onPress={joinChat}
|
||||
className="bg-green-500 p-4 rounded-md mt-4"
|
||||
className="bg-green-500 p-4 rounded-md"
|
||||
>
|
||||
<Text className="text-white font-semibold">Join chat</Text>
|
||||
</TouchableOpacity>
|
||||
@@ -172,7 +173,6 @@ export default function ChatScreen({ navigation }: { navigation: any }) {
|
||||
<FlatList
|
||||
contentContainerStyle={{
|
||||
flexGrow: 1,
|
||||
flex: 1,
|
||||
gap: 6,
|
||||
padding: 8,
|
||||
}}
|
||||
|
||||
@@ -44,9 +44,11 @@ appId: com.jazz.chatrn
|
||||
# logout
|
||||
- tapOn: "Logout"
|
||||
- assertVisible: "Anonymous user"
|
||||
# This doesn't work on CI, maybe because Android has a different alert dialog
|
||||
# - tapOn: "Join chat"
|
||||
# - inputText: "co_zFs6KFyhxPw4xtw83tcEMzeHUNv" # Use a static id because maestro doesn't have access to the system clipboard
|
||||
# - pressKey: "enter"
|
||||
# - assertVisible: "boorad"
|
||||
# - assertVisible: "bro, low key, it do be like that tho"
|
||||
|
||||
# join chat
|
||||
- tapOn:
|
||||
id: "chat-id-input"
|
||||
- inputText: "co_zFs6KFyhxPw4xtw83tcEMzeHUNv" # Use a static id because maestro doesn't have access to the system clipboard
|
||||
- tapOn: "Join chat"
|
||||
- assertVisible: "boorad"
|
||||
- assertVisible: "bro, low key, it do be like that tho"
|
||||
|
||||
@@ -1,5 +1,54 @@
|
||||
# chat-rn
|
||||
|
||||
## 1.0.110
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [6b781cf]
|
||||
- Updated dependencies [02a240c]
|
||||
- cojson@0.13.23
|
||||
- jazz-tools@0.13.23
|
||||
- cojson-transport-ws@0.13.23
|
||||
- jazz-react-native@0.13.23
|
||||
|
||||
## 1.0.109
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react-native@0.13.22
|
||||
|
||||
## 1.0.108
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [e14e61f]
|
||||
- cojson@0.13.21
|
||||
- cojson-transport-ws@0.13.21
|
||||
- jazz-react-native@0.13.21
|
||||
- jazz-tools@0.13.21
|
||||
|
||||
## 1.0.107
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [adfc9a6]
|
||||
- Updated dependencies [1389207]
|
||||
- Updated dependencies [d6e143e]
|
||||
- Updated dependencies [439f0fe]
|
||||
- Updated dependencies [3e6229d]
|
||||
- cojson@0.13.20
|
||||
- jazz-tools@0.13.20
|
||||
- jazz-react-native@0.13.20
|
||||
- cojson-transport-ws@0.13.20
|
||||
|
||||
## 1.0.106
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [80530a4]
|
||||
- jazz-tools@0.13.19
|
||||
- jazz-react-native@0.13.19
|
||||
|
||||
## 1.0.105
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "chat-rn",
|
||||
"version": "1.0.105",
|
||||
"version": "1.0.110",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"android": "react-native run-android",
|
||||
|
||||
@@ -1,5 +1,40 @@
|
||||
# chat-vue
|
||||
|
||||
## 0.0.93
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [02a240c]
|
||||
- jazz-tools@0.13.23
|
||||
- jazz-browser@0.13.23
|
||||
- jazz-vue@0.13.23
|
||||
|
||||
## 0.0.92
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-browser@0.13.21
|
||||
- jazz-tools@0.13.21
|
||||
- jazz-vue@0.13.21
|
||||
|
||||
## 0.0.91
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [439f0fe]
|
||||
- jazz-tools@0.13.20
|
||||
- jazz-browser@0.13.20
|
||||
- jazz-vue@0.13.20
|
||||
|
||||
## 0.0.90
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [80530a4]
|
||||
- jazz-tools@0.13.19
|
||||
- jazz-browser@0.13.19
|
||||
- jazz-vue@0.13.19
|
||||
|
||||
## 0.0.89
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "chat-vue",
|
||||
"version": "0.0.89",
|
||||
"version": "0.0.93",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
|
||||
@@ -1,5 +1,41 @@
|
||||
# jazz-example-chat
|
||||
|
||||
## 0.0.191
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [02a240c]
|
||||
- jazz-tools@0.13.23
|
||||
- jazz-inspector@0.13.23
|
||||
- jazz-react@0.13.23
|
||||
|
||||
## 0.0.190
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [7de210f]
|
||||
- jazz-inspector@0.13.21
|
||||
- jazz-react@0.13.21
|
||||
- jazz-tools@0.13.21
|
||||
|
||||
## 0.0.189
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [439f0fe]
|
||||
- jazz-tools@0.13.20
|
||||
- jazz-inspector@0.13.20
|
||||
- jazz-react@0.13.20
|
||||
|
||||
## 0.0.188
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [80530a4]
|
||||
- jazz-tools@0.13.19
|
||||
- jazz-inspector@0.13.19
|
||||
- jazz-react@0.13.19
|
||||
|
||||
## 0.0.187
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "jazz-example-chat",
|
||||
"private": true,
|
||||
"version": "0.0.187",
|
||||
"version": "0.0.191",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -1,5 +1,40 @@
|
||||
# minimal-auth-clerk
|
||||
|
||||
## 0.0.90
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [02a240c]
|
||||
- jazz-tools@0.13.23
|
||||
- jazz-react@0.13.23
|
||||
- jazz-react-auth-clerk@0.13.23
|
||||
|
||||
## 0.0.89
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react@0.13.21
|
||||
- jazz-react-auth-clerk@0.13.21
|
||||
- jazz-tools@0.13.21
|
||||
|
||||
## 0.0.88
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [439f0fe]
|
||||
- jazz-tools@0.13.20
|
||||
- jazz-react@0.13.20
|
||||
- jazz-react-auth-clerk@0.13.20
|
||||
|
||||
## 0.0.87
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [80530a4]
|
||||
- jazz-tools@0.13.19
|
||||
- jazz-react@0.13.19
|
||||
- jazz-react-auth-clerk@0.13.19
|
||||
|
||||
## 0.0.86
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "clerk",
|
||||
"private": true,
|
||||
"version": "0.0.86",
|
||||
"version": "0.0.90",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -1,5 +1,46 @@
|
||||
# file-share-svelte
|
||||
|
||||
## 0.0.74
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [ec546b4]
|
||||
- jazz-svelte@0.13.24
|
||||
|
||||
## 0.0.73
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [3431076]
|
||||
- Updated dependencies [02a240c]
|
||||
- jazz-svelte@0.13.23
|
||||
- jazz-tools@0.13.23
|
||||
- jazz-inspector-element@0.13.23
|
||||
|
||||
## 0.0.72
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-inspector-element@0.13.21
|
||||
- jazz-svelte@0.13.21
|
||||
- jazz-tools@0.13.21
|
||||
|
||||
## 0.0.71
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [439f0fe]
|
||||
- jazz-tools@0.13.20
|
||||
- jazz-svelte@0.13.20
|
||||
|
||||
## 0.0.70
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [80530a4]
|
||||
- jazz-tools@0.13.19
|
||||
- jazz-svelte@0.13.19
|
||||
|
||||
## 0.0.69
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "file-share-svelte",
|
||||
"version": "0.0.69",
|
||||
"version": "0.0.74",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
@@ -39,6 +39,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@tailwindcss/typography": "^0.5.15",
|
||||
"jazz-inspector-element": "workspace:*",
|
||||
"jazz-svelte": "workspace:*",
|
||||
"jazz-tools": "workspace:*",
|
||||
"lucide-svelte": "^0.463.0",
|
||||
|
||||
@@ -3,9 +3,8 @@
|
||||
import { SharedFile } from '$lib/schema';
|
||||
import { FileStream } from 'jazz-tools';
|
||||
import { File, FileDown, Trash2, Link2 } from 'lucide-svelte';
|
||||
import { useAccount } from 'jazz-svelte';
|
||||
import { toast } from 'svelte-sonner';
|
||||
import { formatFileSize } from '$lib/utils';
|
||||
import { downloadFileBlob, formatFileSize } from '$lib/utils';
|
||||
|
||||
const {
|
||||
file,
|
||||
@@ -17,32 +16,22 @@
|
||||
onDelete: (file: SharedFile) => void;
|
||||
} = $props();
|
||||
|
||||
const { me } = useAccount();
|
||||
const isAdmin = $derived(me && file._owner?.myRole() === 'admin');
|
||||
const isAdmin = $derived(file._owner?.myRole() === 'admin');
|
||||
const fileStreamId = $derived(file._refs.file?.id);
|
||||
|
||||
async function downloadFile() {
|
||||
if (!file._refs.file?.id || !me) {
|
||||
if (!fileStreamId) {
|
||||
toast.error('Failed to download file');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const fileId = file._refs.file.id;
|
||||
|
||||
// Load the file as a blob, can take a while
|
||||
const blob = await FileStream.loadAsBlob(fileId);
|
||||
const blob = await FileStream.loadAsBlob(fileStreamId);
|
||||
if (!blob) {
|
||||
toast.error('Failed to download file');
|
||||
return;
|
||||
}
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = file.name;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
URL.revokeObjectURL(url);
|
||||
downloadFileBlob(blob, file.name);
|
||||
toast.success('File downloaded successfully');
|
||||
} catch (error) {
|
||||
console.error('Error downloading file:', error);
|
||||
@@ -66,14 +55,19 @@
|
||||
class="flex items-center justify-between rounded-lg border border-gray-200 bg-white p-4"
|
||||
transition:slide={{ duration: 200 }}
|
||||
>
|
||||
<div class="flex items-center space-x-4">
|
||||
<div class="flex items-center space-x-4 flex-grow">
|
||||
<div class="flex h-10 w-10 items-center justify-center rounded-lg bg-blue-100 text-blue-600">
|
||||
<File class="h-6 w-6" />
|
||||
</div>
|
||||
<div>
|
||||
<a href="/file/{file.id}" class="hover:text-blue-600 hover:underline">
|
||||
<div class="flex-grow">
|
||||
{#if isAdmin}
|
||||
<label class="sr-only" for={`file-name-${file.id}`}>File name</label>
|
||||
<!-- Jazz values are reactive, but they are not recognized as reactive by Svelte -->
|
||||
<!-- svelte-ignore binding_property_non_reactive -->
|
||||
<input class="font-medium text-gray-900 w-full py-1" type="text" bind:value={file.name} id={`file-name-${file.id}`} />
|
||||
{:else}
|
||||
<h3 class="font-medium text-gray-900">{file.name}</h3>
|
||||
</a>
|
||||
{/if}
|
||||
<p class="text-sm text-gray-500">
|
||||
{isAdmin ? 'Owned by you' : ''} • Uploaded {new Date(
|
||||
file.createdAt || 0
|
||||
|
||||
@@ -15,9 +15,8 @@ export class FileShareProfile extends Profile {
|
||||
export class ListOfSharedFiles extends CoList.Of(co.ref(SharedFile)) {}
|
||||
|
||||
export class FileShareAccountRoot extends CoMap {
|
||||
type = co.string;
|
||||
type = co.literal('file-share-account');
|
||||
sharedFiles = co.ref(ListOfSharedFiles);
|
||||
publicGroup = co.ref(Group);
|
||||
}
|
||||
|
||||
export class FileShareAccount extends Account {
|
||||
@@ -31,7 +30,7 @@ export class FileShareAccount extends Account {
|
||||
await this._refs.root?.load();
|
||||
|
||||
// Initialize root if it doesn't exist
|
||||
if (!this.root || this.root.type !== 'file-share-account') {
|
||||
if (this.root === undefined || this.root?.type !== 'file-share-account') {
|
||||
// Create a group that will own all shared files
|
||||
const publicGroup = Group.create({ owner: this });
|
||||
publicGroup.addMember('everyone', 'reader');
|
||||
@@ -40,9 +39,7 @@ export class FileShareAccount extends Account {
|
||||
{
|
||||
type: 'file-share-account',
|
||||
sharedFiles: ListOfSharedFiles.create([], { owner: publicGroup }),
|
||||
publicGroup
|
||||
},
|
||||
{ owner: this }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,3 +20,13 @@ export function formatFileSize(bytes: number): string {
|
||||
export function generateTempFileId(fileName: string | undefined, createdAt: Date | undefined): string {
|
||||
return `file-${fileName ?? 'unknown'}-${createdAt?.getTime() ?? 0}`;
|
||||
}
|
||||
|
||||
export function downloadFileBlob(blob: Blob, fileName: string) {
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = fileName;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
}
|
||||
@@ -8,7 +8,8 @@
|
||||
|
||||
<script lang="ts">
|
||||
import { JazzProvider } from 'jazz-svelte';
|
||||
import { PasskeyAuthBasicUI, usePasskeyAuth } from 'jazz-svelte';
|
||||
import "jazz-inspector-element"
|
||||
import { PasskeyAuthBasicUI } from 'jazz-svelte';
|
||||
import { Toaster } from 'svelte-sonner';
|
||||
import '../app.css';
|
||||
import { FileShareAccount } from '$lib/schema';
|
||||
@@ -29,6 +30,7 @@
|
||||
peer: `wss://cloud.jazz.tools/?key=${apiKey}`,
|
||||
}}
|
||||
>
|
||||
<jazz-inspector></jazz-inspector>
|
||||
<PasskeyAuthBasicUI appName="File Share">
|
||||
<div class="min-h-screen bg-gray-100">
|
||||
{@render children()}
|
||||
|
||||
@@ -1,14 +1,22 @@
|
||||
<script lang="ts">
|
||||
import { useAccount, useCoState } from 'jazz-svelte';
|
||||
import { SharedFile, ListOfSharedFiles } from '$lib/schema';
|
||||
import { AccountCoState } from 'jazz-svelte';
|
||||
import { SharedFile } from '$lib/schema';
|
||||
import { FileStream } from 'jazz-tools';
|
||||
import FileItem from '$lib/components/FileItem.svelte';
|
||||
import { CloudUpload } from 'lucide-svelte';
|
||||
|
||||
const { me, logOut } = useAccount();
|
||||
const me = new AccountCoState({
|
||||
resolve: {
|
||||
profile: true,
|
||||
root: {
|
||||
sharedFiles: {
|
||||
$each: true
|
||||
},
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const mySharedFilesId = me?.root?._refs.sharedFiles.id;
|
||||
const sharedFiles = $derived(useCoState(ListOfSharedFiles, mySharedFilesId));
|
||||
const sharedFiles = $derived(me.current?.root.sharedFiles);
|
||||
|
||||
let fileInput: HTMLInputElement;
|
||||
|
||||
@@ -16,17 +24,15 @@
|
||||
const input = event.target as HTMLInputElement;
|
||||
const files = input.files;
|
||||
|
||||
if (!files || !files.length || !me?.root?.sharedFiles || !me?.root?.publicGroup) return;
|
||||
if (!files?.length || !sharedFiles) return;
|
||||
|
||||
const file = files[0];
|
||||
const fileName = file.name;
|
||||
const createdAt = new Date();
|
||||
|
||||
try {
|
||||
const ownership = { owner: me.root.publicGroup };
|
||||
|
||||
// Create a FileStream from the uploaded file
|
||||
const fileStream = await FileStream.createFromBlob(file, ownership);
|
||||
const fileStream = await FileStream.createFromBlob(file, sharedFiles._owner);
|
||||
|
||||
// Create the shared file entry
|
||||
const sharedFile = SharedFile.create(
|
||||
@@ -37,22 +43,22 @@
|
||||
uploadedAt: new Date(),
|
||||
size: file.size
|
||||
},
|
||||
ownership
|
||||
sharedFiles._owner
|
||||
);
|
||||
|
||||
// Add the file to the user's files list
|
||||
me.root.sharedFiles.push(sharedFile);
|
||||
sharedFiles.push(sharedFile);
|
||||
} finally {
|
||||
fileInput.value = ''; // reset input
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteFile(file: SharedFile) {
|
||||
if (!me?.root?.sharedFiles || !sharedFiles.current) return;
|
||||
if (!sharedFiles) return;
|
||||
|
||||
const index = sharedFiles.current.indexOf(file);
|
||||
const index = sharedFiles.indexOf(file);
|
||||
if (index > -1) {
|
||||
me.root.sharedFiles.splice(index, 1);
|
||||
sharedFiles.splice(index, 1);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -62,11 +68,11 @@
|
||||
<div class="mb-12 flex items-center justify-between">
|
||||
<div>
|
||||
<h1 class="mb-2 text-4xl font-bold text-gray-900">File Share</h1>
|
||||
<h2 class="text-xl text-gray-600">Welcome back, {me?.profile?.name}</h2>
|
||||
<h2 class="text-xl text-gray-600">Welcome back, {me.current?.profile.name}</h2>
|
||||
</div>
|
||||
|
||||
<button
|
||||
onclick={logOut}
|
||||
onclick={me.logOut}
|
||||
class="rounded-lg bg-red-500 px-6 py-2.5 text-sm font-medium text-white transition-colors hover:bg-red-600 focus:outline-none focus:ring-2 focus:ring-red-500 focus:ring-offset-2"
|
||||
>
|
||||
Log Out
|
||||
@@ -97,9 +103,9 @@
|
||||
|
||||
<!-- Files List -->
|
||||
<div class="space-y-4">
|
||||
{#if sharedFiles.current}
|
||||
{#if !(sharedFiles.current.length === 0)}
|
||||
{#each sharedFiles.current as file}
|
||||
{#if sharedFiles}
|
||||
{#if sharedFiles.length}
|
||||
{#each sharedFiles as file}
|
||||
{#if file}
|
||||
<FileItem
|
||||
{file}
|
||||
|
||||
@@ -1,39 +1,33 @@
|
||||
<script lang="ts">
|
||||
import { page } from '$app/stores';
|
||||
import { useAccount, useCoState } from 'jazz-svelte';
|
||||
import { CoState } from 'jazz-svelte';
|
||||
import { SharedFile } from '$lib/schema';
|
||||
import { File, FileDown, Link2 } from 'lucide-svelte';
|
||||
import type { ID } from 'jazz-tools';
|
||||
import { FileStream } from 'jazz-tools';
|
||||
import { toast } from 'svelte-sonner';
|
||||
import { downloadFileBlob } from '$lib/utils';
|
||||
|
||||
const { me } = useAccount();
|
||||
const fileId = $page.params.fileId;
|
||||
|
||||
const file = $state(useCoState(SharedFile, fileId as ID<SharedFile>, {}));
|
||||
const isAdmin = $derived(me && file.current?._owner?.myRole() === 'admin');
|
||||
const file = $derived(new CoState(SharedFile, fileId as ID<SharedFile>));
|
||||
const isAdmin = $derived(file.current?._owner?.myRole() === 'admin');
|
||||
|
||||
const fileStreamId = $derived(file.current?._refs.file?.id);
|
||||
|
||||
async function downloadFile() {
|
||||
if (!file.current?._refs.file?.id || !me) {
|
||||
if (!fileStreamId || !file.current) {
|
||||
toast.error('Failed to download file');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const fileId = file.current._refs.file.id;
|
||||
const blob = await FileStream.loadAsBlob(fileId, me, {});
|
||||
const blob = await FileStream.loadAsBlob(fileStreamId);
|
||||
if (!blob) {
|
||||
toast.error('Failed to download file');
|
||||
return;
|
||||
}
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = file.current.name;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
URL.revokeObjectURL(url);
|
||||
downloadFileBlob(blob, file.current.name);
|
||||
toast.success('File downloaded successfully');
|
||||
} catch (error) {
|
||||
console.error('Error downloading file:', error);
|
||||
|
||||
@@ -59,7 +59,7 @@ test('can login with passkey and upload file', async ({ page, browser }) => {
|
||||
await fileChooser.setFiles(filePath);
|
||||
|
||||
// Verify the uploaded file appears in the list
|
||||
await expect(page.getByText('test-file.txt')).toBeVisible();
|
||||
await expect(page.getByRole("textbox", { name: "File name" })).toHaveValue("test-file.txt");
|
||||
|
||||
await page.getByRole('button', { name: 'Share file' }).click();
|
||||
const inviteLink = await page.evaluate(() => navigator.clipboard.readText());
|
||||
|
||||
@@ -1,5 +1,41 @@
|
||||
# jazz-tailwind-demo-auth-starter
|
||||
|
||||
## 0.0.30
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [02a240c]
|
||||
- jazz-tools@0.13.23
|
||||
- jazz-inspector@0.13.23
|
||||
- jazz-react@0.13.23
|
||||
|
||||
## 0.0.29
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [7de210f]
|
||||
- jazz-inspector@0.13.21
|
||||
- jazz-react@0.13.21
|
||||
- jazz-tools@0.13.21
|
||||
|
||||
## 0.0.28
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [439f0fe]
|
||||
- jazz-tools@0.13.20
|
||||
- jazz-inspector@0.13.20
|
||||
- jazz-react@0.13.20
|
||||
|
||||
## 0.0.27
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [80530a4]
|
||||
- jazz-tools@0.13.19
|
||||
- jazz-inspector@0.13.19
|
||||
- jazz-react@0.13.19
|
||||
|
||||
## 0.0.26
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "filestream",
|
||||
"private": true,
|
||||
"version": "0.0.26",
|
||||
"version": "0.0.30",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -189,7 +189,7 @@ export function FileWidget() {
|
||||
);
|
||||
}
|
||||
|
||||
const fileData = me?.profile?.file?.getChunks();
|
||||
const fileData = me?.profile?.file?.getMetadata();
|
||||
const mimeType = fileData?.mimeType || "unknown";
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,5 +1,36 @@
|
||||
# form
|
||||
|
||||
## 0.1.31
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [02a240c]
|
||||
- jazz-tools@0.13.23
|
||||
- jazz-react@0.13.23
|
||||
|
||||
## 0.1.30
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react@0.13.21
|
||||
- jazz-tools@0.13.21
|
||||
|
||||
## 0.1.29
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [439f0fe]
|
||||
- jazz-tools@0.13.20
|
||||
- jazz-react@0.13.20
|
||||
|
||||
## 0.1.28
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [80530a4]
|
||||
- jazz-tools@0.13.19
|
||||
- jazz-react@0.13.19
|
||||
|
||||
## 0.1.27
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "form",
|
||||
"private": true,
|
||||
"version": "0.1.27",
|
||||
"version": "0.1.31",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -1,5 +1,36 @@
|
||||
# image-upload
|
||||
|
||||
## 0.0.87
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [02a240c]
|
||||
- jazz-tools@0.13.23
|
||||
- jazz-react@0.13.23
|
||||
|
||||
## 0.0.86
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react@0.13.21
|
||||
- jazz-tools@0.13.21
|
||||
|
||||
## 0.0.85
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [439f0fe]
|
||||
- jazz-tools@0.13.20
|
||||
- jazz-react@0.13.20
|
||||
|
||||
## 0.0.84
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [80530a4]
|
||||
- jazz-tools@0.13.19
|
||||
- jazz-react@0.13.19
|
||||
|
||||
## 0.0.83
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "image-upload",
|
||||
"private": true,
|
||||
"version": "0.0.83",
|
||||
"version": "0.0.87",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -1,5 +1,42 @@
|
||||
# jazz-example-inspector
|
||||
|
||||
## 0.0.141
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [6b781cf]
|
||||
- cojson@0.13.23
|
||||
- cojson-transport-ws@0.13.23
|
||||
- jazz-inspector@0.13.23
|
||||
|
||||
## 0.0.140
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [7de210f]
|
||||
- Updated dependencies [e14e61f]
|
||||
- jazz-inspector@0.13.21
|
||||
- cojson@0.13.21
|
||||
- cojson-transport-ws@0.13.21
|
||||
|
||||
## 0.0.139
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [adfc9a6]
|
||||
- Updated dependencies [1389207]
|
||||
- Updated dependencies [d6e143e]
|
||||
- Updated dependencies [3e6229d]
|
||||
- cojson@0.13.20
|
||||
- cojson-transport-ws@0.13.20
|
||||
- jazz-inspector@0.13.20
|
||||
|
||||
## 0.0.138
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-inspector@0.13.19
|
||||
|
||||
## 0.0.137
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "jazz-inspector-app",
|
||||
"private": true,
|
||||
"version": "0.0.137",
|
||||
"version": "0.0.141",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -8,7 +8,10 @@ export function cn(...inputs: ClassValue[]) {
|
||||
/**
|
||||
* Given a player selections, returns the winner of the current game.
|
||||
*/
|
||||
export function determineWinner(player1Choice: string, player2Choice: string) {
|
||||
export function determineWinner(
|
||||
player1Choice: "rock" | "paper" | "scissors",
|
||||
player2Choice: "rock" | "paper" | "scissors",
|
||||
) {
|
||||
if (player1Choice === player2Choice) {
|
||||
return "draw";
|
||||
} else if (
|
||||
|
||||
@@ -14,7 +14,7 @@ import { type ID } from "jazz-tools";
|
||||
import { Badge, CircleHelp, Scissors, ScrollText } from "lucide-react";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
const playIcon = (selection: string | undefined) => {
|
||||
const playIcon = (selection: "rock" | "paper" | "scissors" | undefined) => {
|
||||
switch (selection) {
|
||||
case "rock":
|
||||
return <Badge className="w-5 h-5" />;
|
||||
@@ -50,9 +50,9 @@ function RouteComponent() {
|
||||
const isPlayer1 = loaderGame.player1?.account?.isMe;
|
||||
const player = isPlayer1 ? "player1" : "player2";
|
||||
|
||||
const [playSelection, setPlaySelection] = useState(
|
||||
loaderGame[player]?.playSelection ?? "",
|
||||
);
|
||||
const [playSelection, setPlaySelection] = useState<
|
||||
"rock" | "paper" | "scissors" | undefined
|
||||
>(loaderGame[player]?.playSelection);
|
||||
const sendInboxMessage = experimental_useInboxSender(WORKER_ID);
|
||||
|
||||
const game = useCoState(Game, gameId as ID<Game>);
|
||||
@@ -62,7 +62,7 @@ function RouteComponent() {
|
||||
|
||||
return loaderGame.subscribe((game) => {
|
||||
if (gameCompleted && !game.outcome) {
|
||||
setPlaySelection(""); // Reset play selection when one player clicks on "Start a new game"
|
||||
setPlaySelection(undefined); // Reset play selection when one player clicks on "Start a new game"
|
||||
}
|
||||
|
||||
gameCompleted = Boolean(game.outcome);
|
||||
@@ -82,7 +82,10 @@ function RouteComponent() {
|
||||
|
||||
const opponentSelection = opponentPlayer?.playSelection;
|
||||
|
||||
const onSubmit = async (playSelection: string) => {
|
||||
const onSubmit = async (
|
||||
playSelection: "rock" | "paper" | "scissors" | undefined,
|
||||
) => {
|
||||
if (!playSelection) return;
|
||||
sendInboxMessage(
|
||||
PlayIntent.create({ type: "play", gameId, player, playSelection }),
|
||||
);
|
||||
@@ -117,7 +120,9 @@ function RouteComponent() {
|
||||
) : null}
|
||||
<CardContent>
|
||||
<div>
|
||||
{playSelection === "" ? "Make Your Selection" : "Your Selection: "}
|
||||
{playSelection === undefined
|
||||
? "Make Your Selection"
|
||||
: "Your Selection: "}
|
||||
</div>
|
||||
<CardSmall>{playIcon(playSelection)}</CardSmall>
|
||||
{gameComplete ? null : (
|
||||
@@ -148,7 +153,7 @@ function RouteComponent() {
|
||||
<div className="m-4">
|
||||
<Button
|
||||
disabled={
|
||||
playSelection === "" ||
|
||||
playSelection === undefined ||
|
||||
Boolean(currentPlayer?.playSelection)
|
||||
}
|
||||
onClick={() => onSubmit(playSelection)}
|
||||
|
||||
@@ -30,7 +30,7 @@ export class Game extends CoMap {
|
||||
|
||||
export class Player extends CoMap {
|
||||
account = co.ref(Account);
|
||||
playSelection? = co.string;
|
||||
playSelection? = co.literal("rock", "paper", "scissors");
|
||||
}
|
||||
|
||||
export class WaitingRoom extends CoMap {
|
||||
@@ -47,7 +47,7 @@ export class PlayIntent extends InboxMessage {
|
||||
type = co.literal("play");
|
||||
gameId = co.string;
|
||||
player = co.literal("player1", "player2");
|
||||
playSelection = co.string;
|
||||
playSelection = co.literal("rock", "paper", "scissors");
|
||||
}
|
||||
|
||||
export class NewGameIntent extends InboxMessage {
|
||||
|
||||
@@ -186,9 +186,9 @@ async function handlePlayIntent(_: ID<Account>, message: PlayIntent) {
|
||||
// once both players have a selection, determine the winner
|
||||
if (
|
||||
!!player1Selection &&
|
||||
player1Selection !== "" &&
|
||||
player1Selection !== undefined &&
|
||||
!!player2Selection &&
|
||||
player2Selection !== ""
|
||||
player2Selection !== undefined
|
||||
) {
|
||||
const outcome = determineWinner(player1Selection, player2Selection);
|
||||
game.outcome = outcome;
|
||||
|
||||
@@ -1,5 +1,36 @@
|
||||
# multi-cursors
|
||||
|
||||
## 0.0.83
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [02a240c]
|
||||
- jazz-tools@0.13.23
|
||||
- jazz-react@0.13.23
|
||||
|
||||
## 0.0.82
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react@0.13.21
|
||||
- jazz-tools@0.13.21
|
||||
|
||||
## 0.0.81
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [439f0fe]
|
||||
- jazz-tools@0.13.20
|
||||
- jazz-react@0.13.20
|
||||
|
||||
## 0.0.80
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [80530a4]
|
||||
- jazz-tools@0.13.19
|
||||
- jazz-react@0.13.19
|
||||
|
||||
## 0.0.79
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "multi-cursors",
|
||||
"private": true,
|
||||
"version": "0.0.79",
|
||||
"version": "0.0.83",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -1,5 +1,40 @@
|
||||
# multiauth
|
||||
|
||||
## 0.0.31
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [02a240c]
|
||||
- jazz-tools@0.13.23
|
||||
- jazz-react@0.13.23
|
||||
- jazz-react-auth-clerk@0.13.23
|
||||
|
||||
## 0.0.30
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react@0.13.21
|
||||
- jazz-react-auth-clerk@0.13.21
|
||||
- jazz-tools@0.13.21
|
||||
|
||||
## 0.0.29
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [439f0fe]
|
||||
- jazz-tools@0.13.20
|
||||
- jazz-react@0.13.20
|
||||
- jazz-react-auth-clerk@0.13.20
|
||||
|
||||
## 0.0.28
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [80530a4]
|
||||
- jazz-tools@0.13.19
|
||||
- jazz-react@0.13.19
|
||||
- jazz-react-auth-clerk@0.13.19
|
||||
|
||||
## 0.0.27
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "multiauth",
|
||||
"private": true,
|
||||
"version": "0.0.27",
|
||||
"version": "0.0.31",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -1,5 +1,41 @@
|
||||
# jazz-example-musicplayer
|
||||
|
||||
## 0.0.112
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [02a240c]
|
||||
- jazz-tools@0.13.23
|
||||
- jazz-inspector@0.13.23
|
||||
- jazz-react@0.13.23
|
||||
|
||||
## 0.0.111
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [7de210f]
|
||||
- jazz-inspector@0.13.21
|
||||
- jazz-react@0.13.21
|
||||
- jazz-tools@0.13.21
|
||||
|
||||
## 0.0.110
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [439f0fe]
|
||||
- jazz-tools@0.13.20
|
||||
- jazz-inspector@0.13.20
|
||||
- jazz-react@0.13.20
|
||||
|
||||
## 0.0.109
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [80530a4]
|
||||
- jazz-tools@0.13.19
|
||||
- jazz-inspector@0.13.19
|
||||
- jazz-react@0.13.19
|
||||
|
||||
## 0.0.108
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "jazz-example-music-player",
|
||||
"private": true,
|
||||
"version": "0.0.108",
|
||||
"version": "0.0.112",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -1,5 +1,36 @@
|
||||
# organization
|
||||
|
||||
## 0.0.83
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [02a240c]
|
||||
- jazz-tools@0.13.23
|
||||
- jazz-react@0.13.23
|
||||
|
||||
## 0.0.82
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react@0.13.21
|
||||
- jazz-tools@0.13.21
|
||||
|
||||
## 0.0.81
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [439f0fe]
|
||||
- jazz-tools@0.13.20
|
||||
- jazz-react@0.13.20
|
||||
|
||||
## 0.0.80
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [80530a4]
|
||||
- jazz-tools@0.13.19
|
||||
- jazz-react@0.13.19
|
||||
|
||||
## 0.0.79
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "organization",
|
||||
"private": true,
|
||||
"version": "0.0.79",
|
||||
"version": "0.0.83",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -1,5 +1,44 @@
|
||||
# passkey-svelte
|
||||
|
||||
## 0.0.78
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [ec546b4]
|
||||
- jazz-svelte@0.13.24
|
||||
|
||||
## 0.0.77
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [3431076]
|
||||
- Updated dependencies [02a240c]
|
||||
- jazz-svelte@0.13.23
|
||||
- jazz-tools@0.13.23
|
||||
|
||||
## 0.0.76
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-svelte@0.13.21
|
||||
- jazz-tools@0.13.21
|
||||
|
||||
## 0.0.75
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [439f0fe]
|
||||
- jazz-tools@0.13.20
|
||||
- jazz-svelte@0.13.20
|
||||
|
||||
## 0.0.74
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [80530a4]
|
||||
- jazz-tools@0.13.19
|
||||
- jazz-svelte@0.13.19
|
||||
|
||||
## 0.0.73
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "passkey-svelte",
|
||||
"version": "0.0.73",
|
||||
"version": "0.0.78",
|
||||
"type": "module",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
|
||||
@@ -1,5 +1,36 @@
|
||||
# minimal-auth-passkey
|
||||
|
||||
## 0.0.88
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [02a240c]
|
||||
- jazz-tools@0.13.23
|
||||
- jazz-react@0.13.23
|
||||
|
||||
## 0.0.87
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react@0.13.21
|
||||
- jazz-tools@0.13.21
|
||||
|
||||
## 0.0.86
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [439f0fe]
|
||||
- jazz-tools@0.13.20
|
||||
- jazz-react@0.13.20
|
||||
|
||||
## 0.0.85
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [80530a4]
|
||||
- jazz-tools@0.13.19
|
||||
- jazz-react@0.13.19
|
||||
|
||||
## 0.0.84
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "passkey",
|
||||
"private": true,
|
||||
"version": "0.0.84",
|
||||
"version": "0.0.88",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -1,5 +1,36 @@
|
||||
# passphrase
|
||||
|
||||
## 0.0.85
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [02a240c]
|
||||
- jazz-tools@0.13.23
|
||||
- jazz-react@0.13.23
|
||||
|
||||
## 0.0.84
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react@0.13.21
|
||||
- jazz-tools@0.13.21
|
||||
|
||||
## 0.0.83
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [439f0fe]
|
||||
- jazz-tools@0.13.20
|
||||
- jazz-react@0.13.20
|
||||
|
||||
## 0.0.82
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [80530a4]
|
||||
- jazz-tools@0.13.19
|
||||
- jazz-react@0.13.19
|
||||
|
||||
## 0.0.81
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "passphrase",
|
||||
"private": true,
|
||||
"version": "0.0.81",
|
||||
"version": "0.0.85",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -1,5 +1,36 @@
|
||||
# jazz-password-manager
|
||||
|
||||
## 0.0.109
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [02a240c]
|
||||
- jazz-tools@0.13.23
|
||||
- jazz-react@0.13.23
|
||||
|
||||
## 0.0.108
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react@0.13.21
|
||||
- jazz-tools@0.13.21
|
||||
|
||||
## 0.0.107
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [439f0fe]
|
||||
- jazz-tools@0.13.20
|
||||
- jazz-react@0.13.20
|
||||
|
||||
## 0.0.106
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [80530a4]
|
||||
- jazz-tools@0.13.19
|
||||
- jazz-react@0.13.19
|
||||
|
||||
## 0.0.105
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "jazz-password-manager",
|
||||
"private": true,
|
||||
"version": "0.0.105",
|
||||
"version": "0.0.109",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -1,5 +1,36 @@
|
||||
# jazz-example-pets
|
||||
|
||||
## 0.0.207
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [02a240c]
|
||||
- jazz-tools@0.13.23
|
||||
- jazz-react@0.13.23
|
||||
|
||||
## 0.0.206
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react@0.13.21
|
||||
- jazz-tools@0.13.21
|
||||
|
||||
## 0.0.205
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [439f0fe]
|
||||
- jazz-tools@0.13.20
|
||||
- jazz-react@0.13.20
|
||||
|
||||
## 0.0.204
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [80530a4]
|
||||
- jazz-tools@0.13.19
|
||||
- jazz-react@0.13.19
|
||||
|
||||
## 0.0.203
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "jazz-example-pets",
|
||||
"private": true,
|
||||
"version": "0.0.203",
|
||||
"version": "0.0.207",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -1,5 +1,36 @@
|
||||
# reactions
|
||||
|
||||
## 0.0.87
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [02a240c]
|
||||
- jazz-tools@0.13.23
|
||||
- jazz-react@0.13.23
|
||||
|
||||
## 0.0.86
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react@0.13.21
|
||||
- jazz-tools@0.13.21
|
||||
|
||||
## 0.0.85
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [439f0fe]
|
||||
- jazz-tools@0.13.20
|
||||
- jazz-react@0.13.20
|
||||
|
||||
## 0.0.84
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [80530a4]
|
||||
- jazz-tools@0.13.19
|
||||
- jazz-react@0.13.19
|
||||
|
||||
## 0.0.83
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "reactions",
|
||||
"private": true,
|
||||
"version": "0.0.83",
|
||||
"version": "0.0.87",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -1,5 +1,40 @@
|
||||
# richtext
|
||||
|
||||
## 0.0.77
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [02a240c]
|
||||
- jazz-tools@0.13.23
|
||||
- jazz-react@0.13.23
|
||||
- jazz-richtext-prosemirror@0.1.11
|
||||
|
||||
## 0.0.76
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react@0.13.21
|
||||
- jazz-tools@0.13.21
|
||||
- jazz-richtext-prosemirror@0.1.10
|
||||
|
||||
## 0.0.75
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [439f0fe]
|
||||
- jazz-tools@0.13.20
|
||||
- jazz-react@0.13.20
|
||||
- jazz-richtext-prosemirror@0.1.9
|
||||
|
||||
## 0.0.74
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [80530a4]
|
||||
- jazz-tools@0.13.19
|
||||
- jazz-react@0.13.19
|
||||
- jazz-richtext-prosemirror@0.1.8
|
||||
|
||||
## 0.0.73
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "richtext",
|
||||
"private": true,
|
||||
"version": "0.0.73",
|
||||
"version": "0.0.77",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -1,5 +1,40 @@
|
||||
# todo-vue
|
||||
|
||||
## 0.0.91
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [02a240c]
|
||||
- jazz-tools@0.13.23
|
||||
- jazz-browser@0.13.23
|
||||
- jazz-vue@0.13.23
|
||||
|
||||
## 0.0.90
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-browser@0.13.21
|
||||
- jazz-tools@0.13.21
|
||||
- jazz-vue@0.13.21
|
||||
|
||||
## 0.0.89
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [439f0fe]
|
||||
- jazz-tools@0.13.20
|
||||
- jazz-browser@0.13.20
|
||||
- jazz-vue@0.13.20
|
||||
|
||||
## 0.0.88
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [80530a4]
|
||||
- jazz-tools@0.13.19
|
||||
- jazz-browser@0.13.19
|
||||
- jazz-vue@0.13.19
|
||||
|
||||
## 0.0.87
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "todo-vue",
|
||||
"version": "0.0.87",
|
||||
"version": "0.0.91",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
|
||||
@@ -1,5 +1,36 @@
|
||||
# jazz-example-todo
|
||||
|
||||
## 0.0.206
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [02a240c]
|
||||
- jazz-tools@0.13.23
|
||||
- jazz-react@0.13.23
|
||||
|
||||
## 0.0.205
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react@0.13.21
|
||||
- jazz-tools@0.13.21
|
||||
|
||||
## 0.0.204
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [439f0fe]
|
||||
- jazz-tools@0.13.20
|
||||
- jazz-react@0.13.20
|
||||
|
||||
## 0.0.203
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [80530a4]
|
||||
- jazz-tools@0.13.19
|
||||
- jazz-react@0.13.19
|
||||
|
||||
## 0.0.202
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "jazz-example-todo",
|
||||
"private": true,
|
||||
"version": "0.0.202",
|
||||
"version": "0.0.206",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -1,5 +1,41 @@
|
||||
# version-history
|
||||
|
||||
## 0.0.85
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [02a240c]
|
||||
- jazz-tools@0.13.23
|
||||
- jazz-inspector@0.13.23
|
||||
- jazz-react@0.13.23
|
||||
|
||||
## 0.0.84
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [7de210f]
|
||||
- jazz-inspector@0.13.21
|
||||
- jazz-react@0.13.21
|
||||
- jazz-tools@0.13.21
|
||||
|
||||
## 0.0.83
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [439f0fe]
|
||||
- jazz-tools@0.13.20
|
||||
- jazz-inspector@0.13.20
|
||||
- jazz-react@0.13.20
|
||||
|
||||
## 0.0.82
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [80530a4]
|
||||
- jazz-tools@0.13.19
|
||||
- jazz-inspector@0.13.19
|
||||
- jazz-react@0.13.19
|
||||
|
||||
## 0.0.81
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "version-history",
|
||||
"private": true,
|
||||
"version": "0.0.81",
|
||||
"version": "0.0.85",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -13,15 +13,14 @@ For now, you can get your account credentials from the `jazz-logged-in-secret` l
|
||||
## Exporting current account to Inspector from your app [!framework=react,svelte,vue,vanilla]
|
||||
|
||||
In development mode, you can launch the Inspector from your Jazz app to inspect your account by pressing `Cmd+J`.
|
||||
</ContentByFramework>
|
||||
|
||||
<ContentByFramework framework="react">
|
||||
## Embedding the Inspector widget into your app [!framework=react]
|
||||
## Embedding the Inspector widget into your app [!framework=react,svelte,vue,vanilla]
|
||||
|
||||
Alternatively, you can embed the Inspector directly into your app, so you don't need to open a separate window.
|
||||
|
||||
Install the package.
|
||||
|
||||
<ContentByFramework framework="react">
|
||||
<CodeGroup>
|
||||
```sh
|
||||
npm install jazz-inspector
|
||||
@@ -40,9 +39,42 @@ import { JazzInspector } from "jazz-inspector";
|
||||
</JazzProvider>
|
||||
```
|
||||
</CodeGroup>
|
||||
</ContentByFramework>
|
||||
|
||||
<ContentByFramework framework={["svelte", "vue", "vanilla"]}>
|
||||
<CodeGroup>
|
||||
```sh
|
||||
npm install jazz-inspector-element
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
Render the component.
|
||||
|
||||
<CodeGroup>
|
||||
```ts
|
||||
import "jazz-inspector-element"
|
||||
|
||||
document.body.appendChild(document.createElement("jazz-inspector"))
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
Or
|
||||
|
||||
<CodeGroup>
|
||||
```tsx
|
||||
import "jazz-inspector-element"
|
||||
|
||||
<jazz-inspector />
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
</ContentByFramework>
|
||||
|
||||
This will show the Inspector launch button on the right of your page.
|
||||
|
||||
</ContentByFramework>
|
||||
|
||||
<ContentByFramework framework="react">
|
||||
### Positioning the Inspector button [!framework=react]
|
||||
|
||||
You can also customize the button position with the following options:
|
||||
@@ -55,6 +87,7 @@ You can also customize the button position with the following options:
|
||||
- top left
|
||||
|
||||
For example:
|
||||
|
||||
<CodeGroup>
|
||||
```tsx
|
||||
<JazzInspector position="bottom left"/>
|
||||
@@ -68,6 +101,12 @@ For example:
|
||||
<JazzIcon className="w-full h-auto"/>
|
||||
</button>
|
||||
</div>
|
||||
</ContentByFramework>
|
||||
|
||||
<ContentByFramework framework="react">
|
||||
Check out the [music player app](https://github.com/garden-co/jazz/blob/main/examples/music-player/src/2_main.tsx) for a full example.
|
||||
</ContentByFramework>
|
||||
|
||||
<ContentByFramework framework="svelte">
|
||||
Check out the [file share app](https://github.com/garden-co/jazz/blob/main/examples/file-share-svelte/src/src/routes/%2Blayout.svelte) for a full example.
|
||||
</ContentByFramework>
|
||||
|
||||
@@ -1,5 +1,32 @@
|
||||
# cojson-storage-indexeddb
|
||||
|
||||
## 0.13.23
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [6b781cf]
|
||||
- cojson@0.13.23
|
||||
- cojson-storage@0.13.23
|
||||
|
||||
## 0.13.21
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [e14e61f]
|
||||
- cojson@0.13.21
|
||||
- cojson-storage@0.13.21
|
||||
|
||||
## 0.13.20
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [adfc9a6]
|
||||
- Updated dependencies [1389207]
|
||||
- Updated dependencies [d6e143e]
|
||||
- Updated dependencies [3e6229d]
|
||||
- cojson-storage@0.13.20
|
||||
- cojson@0.13.20
|
||||
|
||||
## 0.13.18
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "cojson-storage-indexeddb",
|
||||
"version": "0.13.18",
|
||||
"version": "0.13.23",
|
||||
"main": "dist/index.js",
|
||||
"type": "module",
|
||||
"types": "dist/index.d.ts",
|
||||
|
||||
@@ -44,10 +44,7 @@ export class IDBNode {
|
||||
}
|
||||
|
||||
static async asPeer(
|
||||
{
|
||||
trace,
|
||||
localNodeName = "local",
|
||||
}: { trace?: boolean; localNodeName?: string } | undefined = {
|
||||
{ localNodeName = "local" }: { localNodeName?: string } | undefined = {
|
||||
localNodeName: "local",
|
||||
},
|
||||
): Promise<Peer> {
|
||||
@@ -57,7 +54,6 @@ export class IDBNode {
|
||||
{
|
||||
peer1role: "client",
|
||||
peer2role: "storage",
|
||||
trace,
|
||||
crashOnClose: true,
|
||||
},
|
||||
);
|
||||
|
||||
@@ -1,5 +1,32 @@
|
||||
# cojson-storage-sqlite
|
||||
|
||||
## 0.13.23
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [6b781cf]
|
||||
- cojson@0.13.23
|
||||
- cojson-storage@0.13.23
|
||||
|
||||
## 0.13.21
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [e14e61f]
|
||||
- cojson@0.13.21
|
||||
- cojson-storage@0.13.21
|
||||
|
||||
## 0.13.20
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [adfc9a6]
|
||||
- Updated dependencies [1389207]
|
||||
- Updated dependencies [d6e143e]
|
||||
- Updated dependencies [3e6229d]
|
||||
- cojson-storage@0.13.20
|
||||
- cojson@0.13.20
|
||||
|
||||
## 0.13.18
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
{
|
||||
"name": "cojson-storage-sqlite",
|
||||
"type": "module",
|
||||
"version": "0.13.18",
|
||||
"version": "0.13.23",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"better-sqlite3": "^11.7.0",
|
||||
"cojson": "workspace:0.13.18",
|
||||
"cojson": "workspace:0.13.23",
|
||||
"cojson-storage": "workspace:*"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -56,17 +56,15 @@ export class SQLiteNode {
|
||||
|
||||
static async asPeer({
|
||||
filename,
|
||||
trace,
|
||||
localNodeName = "local",
|
||||
}: {
|
||||
filename: string;
|
||||
trace?: boolean;
|
||||
localNodeName?: string;
|
||||
}): Promise<Peer> {
|
||||
const [localNodeAsPeer, storageAsPeer] = cojsonInternals.connectedPeers(
|
||||
localNodeName,
|
||||
"storage",
|
||||
{ peer1role: "client", peer2role: "storage", trace, crashOnClose: true },
|
||||
{ peer1role: "client", peer2role: "storage", crashOnClose: true },
|
||||
);
|
||||
|
||||
await SQLiteNode.open(
|
||||
|
||||
@@ -79,7 +79,9 @@ test("should sync and load data from storage", async () => {
|
||||
).toMatchInlineSnapshot(`
|
||||
[
|
||||
"client -> CONTENT Group header: true new: After: 0 New: 3",
|
||||
"storage -> KNOWN Group sessions: header/3",
|
||||
"client -> CONTENT Map header: true new: After: 0 New: 1",
|
||||
"storage -> KNOWN Map sessions: header/1",
|
||||
]
|
||||
`);
|
||||
|
||||
@@ -164,8 +166,11 @@ test("should load dependencies correctly (group inheritance)", async () => {
|
||||
).toMatchInlineSnapshot(`
|
||||
[
|
||||
"client -> CONTENT ParentGroup header: true new: After: 0 New: 4",
|
||||
"storage -> KNOWN ParentGroup sessions: header/4",
|
||||
"client -> CONTENT Group header: true new: After: 0 New: 5",
|
||||
"storage -> KNOWN Group sessions: header/5",
|
||||
"client -> CONTENT Map header: true new: After: 0 New: 1",
|
||||
"storage -> KNOWN Map sessions: header/1",
|
||||
]
|
||||
`);
|
||||
|
||||
@@ -342,10 +347,13 @@ test("should recover from data loss", async () => {
|
||||
).toMatchInlineSnapshot(`
|
||||
[
|
||||
"client -> CONTENT Group header: true new: After: 0 New: 3",
|
||||
"storage -> KNOWN Group sessions: header/3",
|
||||
"client -> CONTENT Map header: true new: After: 0 New: 1",
|
||||
"storage -> KNOWN Map sessions: header/1",
|
||||
"client -> CONTENT Map header: false new: After: 3 New: 1",
|
||||
"storage -> KNOWN CORRECTION Map sessions: header/1",
|
||||
"client -> CONTENT Map header: false new: After: 1 New: 3",
|
||||
"storage -> KNOWN Map sessions: header/4",
|
||||
]
|
||||
`);
|
||||
|
||||
|
||||
@@ -1,5 +1,30 @@
|
||||
# cojson-storage
|
||||
|
||||
## 0.13.23
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [6b781cf]
|
||||
- cojson@0.13.23
|
||||
|
||||
## 0.13.21
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [e14e61f]
|
||||
- cojson@0.13.21
|
||||
|
||||
## 0.13.20
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- adfc9a6: Make waitForSync work on storage peers by handling optimistic/known states
|
||||
- Updated dependencies [adfc9a6]
|
||||
- Updated dependencies [1389207]
|
||||
- Updated dependencies [d6e143e]
|
||||
- Updated dependencies [3e6229d]
|
||||
- cojson@0.13.20
|
||||
|
||||
## 0.13.18
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "cojson-storage",
|
||||
"version": "0.13.18",
|
||||
"version": "0.13.23",
|
||||
"main": "dist/index.js",
|
||||
"type": "module",
|
||||
"types": "dist/index.d.ts",
|
||||
|
||||
@@ -237,7 +237,13 @@ export class SyncManager {
|
||||
if ((sessionRow?.lastIdx || 0) < (msg.new[sessionID]?.after || 0)) {
|
||||
invalidAssumptions = true;
|
||||
} else {
|
||||
return this.putNewTxs(msg, sessionID, sessionRow, storedCoValueRowID);
|
||||
const newLastIdx = await this.putNewTxs(
|
||||
msg,
|
||||
sessionID,
|
||||
sessionRow,
|
||||
storedCoValueRowID,
|
||||
);
|
||||
ourKnown.sessions[sessionID] = newLastIdx;
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -248,6 +254,11 @@ export class SyncManager {
|
||||
...ourKnown,
|
||||
isCorrection: invalidAssumptions,
|
||||
});
|
||||
} else {
|
||||
this.sendStateMessage({
|
||||
action: "known",
|
||||
...ourKnown,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -310,11 +321,13 @@ export class SyncManager {
|
||||
});
|
||||
}
|
||||
|
||||
return Promise.all(
|
||||
await Promise.all(
|
||||
actuallyNewTransactions.map((newTransaction, i) =>
|
||||
this.dbClient.addTransaction(sessionRowID, nextIdx + i, newTransaction),
|
||||
),
|
||||
);
|
||||
|
||||
return newLastIdx;
|
||||
}
|
||||
|
||||
handleKnown(_msg: CojsonInternalTypes.KnownStateMessage) {
|
||||
|
||||
@@ -291,7 +291,7 @@ describe("DB sync manager", () => {
|
||||
});
|
||||
});
|
||||
|
||||
test("Saves new transaction without sending message when IDB has fewer transactions", async () => {
|
||||
test("Saves new transaction and sends an ack message as response", async () => {
|
||||
DBClient.prototype.getCoValue.mockResolvedValueOnce({
|
||||
id: coValueIdToLoad,
|
||||
header: coValueHeader,
|
||||
@@ -314,7 +314,12 @@ describe("DB sync manager", () => {
|
||||
incomingTxCount,
|
||||
);
|
||||
|
||||
expect(syncManager.sendStateMessage).not.toBeCalled();
|
||||
expect(syncManager.sendStateMessage).toBeCalledWith({
|
||||
action: "known",
|
||||
header: true,
|
||||
id: coValueIdToLoad,
|
||||
sessions: expect.any(Object),
|
||||
});
|
||||
});
|
||||
|
||||
test("Sends correction message when peer sends a message far ahead of our state due to invalid assumption", async () => {
|
||||
|
||||
@@ -1,5 +1,29 @@
|
||||
# cojson-transport-nodejs-ws
|
||||
|
||||
## 0.13.23
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [6b781cf]
|
||||
- cojson@0.13.23
|
||||
|
||||
## 0.13.21
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [e14e61f]
|
||||
- cojson@0.13.21
|
||||
|
||||
## 0.13.20
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [adfc9a6]
|
||||
- Updated dependencies [1389207]
|
||||
- Updated dependencies [d6e143e]
|
||||
- Updated dependencies [3e6229d]
|
||||
- cojson@0.13.20
|
||||
|
||||
## 0.13.18
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "cojson-transport-ws",
|
||||
"type": "module",
|
||||
"version": "0.13.18",
|
||||
"version": "0.13.23",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,5 +1,26 @@
|
||||
# cojson
|
||||
|
||||
## 0.13.23
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 6b781cf: Add getBinaryStreamInfo to RawBinaryCoStreamView to make it possible to retrieve file info without processing all the chunks
|
||||
|
||||
## 0.13.21
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- e14e61f: Optimized the acceptInvite flow
|
||||
|
||||
## 0.13.20
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- adfc9a6: Make waitForSync work on storage peers by handling optimistic/known states
|
||||
- 1389207: Removed throw error when the profile is unavailable after a login
|
||||
- d6e143e: Wait for storage sync before resolving new account creation
|
||||
- 3e6229d: Skip closed and unsubscribed peers when calling waitForSync
|
||||
|
||||
## 0.13.18
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
},
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"version": "0.13.18",
|
||||
"version": "0.13.23",
|
||||
"devDependencies": {
|
||||
"@opentelemetry/sdk-metrics": "^2.0.0",
|
||||
"typescript": "catalog:"
|
||||
|
||||
@@ -23,15 +23,7 @@ export class PeerState {
|
||||
});
|
||||
|
||||
this._knownStates = knownStates?.clone() ?? new PeerKnownStates();
|
||||
|
||||
// We assume that exchanges with storage peers are always successful
|
||||
// hence we don't need to differentiate between knownStates and optimisticKnownStates
|
||||
if (peer.role === "storage") {
|
||||
this._optimisticKnownStates = "assumeInfallible";
|
||||
} else {
|
||||
this._optimisticKnownStates =
|
||||
knownStates?.clone() ?? new PeerKnownStates();
|
||||
}
|
||||
this._optimisticKnownStates = knownStates?.clone() ?? new PeerKnownStates();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -52,13 +44,9 @@ export class PeerState {
|
||||
* The main difference with knownState is that this is updated when the content is sent to the peer without
|
||||
* waiting for any acknowledgement from the peer.
|
||||
*/
|
||||
readonly _optimisticKnownStates: PeerKnownStates | "assumeInfallible";
|
||||
readonly _optimisticKnownStates: PeerKnownStates;
|
||||
|
||||
get optimisticKnownStates(): ReadonlyPeerKnownStates {
|
||||
if (this._optimisticKnownStates === "assumeInfallible") {
|
||||
return this.knownStates;
|
||||
}
|
||||
|
||||
return this._optimisticKnownStates;
|
||||
}
|
||||
|
||||
@@ -76,53 +64,33 @@ export class PeerState {
|
||||
|
||||
updateHeader(id: RawCoID, header: boolean) {
|
||||
this._knownStates.updateHeader(id, header);
|
||||
|
||||
if (this._optimisticKnownStates !== "assumeInfallible") {
|
||||
this._optimisticKnownStates.updateHeader(id, header);
|
||||
}
|
||||
this._optimisticKnownStates.updateHeader(id, header);
|
||||
}
|
||||
|
||||
combineWith(id: RawCoID, value: CoValueKnownState) {
|
||||
this._knownStates.combineWith(id, value);
|
||||
|
||||
if (this._optimisticKnownStates !== "assumeInfallible") {
|
||||
this._optimisticKnownStates.combineWith(id, value);
|
||||
}
|
||||
this._optimisticKnownStates.combineWith(id, value);
|
||||
}
|
||||
|
||||
combineOptimisticWith(id: RawCoID, value: CoValueKnownState) {
|
||||
if (this._optimisticKnownStates === "assumeInfallible") {
|
||||
this._knownStates.combineWith(id, value);
|
||||
} else {
|
||||
this._optimisticKnownStates.combineWith(id, value);
|
||||
}
|
||||
this._optimisticKnownStates.combineWith(id, value);
|
||||
}
|
||||
|
||||
updateSessionCounter(id: RawCoID, sessionId: SessionID, value: number) {
|
||||
this._knownStates.updateSessionCounter(id, sessionId, value);
|
||||
|
||||
if (this._optimisticKnownStates !== "assumeInfallible") {
|
||||
this._optimisticKnownStates.updateSessionCounter(id, sessionId, value);
|
||||
}
|
||||
this._optimisticKnownStates.updateSessionCounter(id, sessionId, value);
|
||||
}
|
||||
|
||||
setKnownState(id: RawCoID, knownState: CoValueKnownState | "empty") {
|
||||
this._knownStates.set(id, knownState);
|
||||
|
||||
if (this._optimisticKnownStates !== "assumeInfallible") {
|
||||
this._optimisticKnownStates.set(id, knownState);
|
||||
}
|
||||
this._optimisticKnownStates.set(id, knownState);
|
||||
}
|
||||
|
||||
setOptimisticKnownState(
|
||||
id: RawCoID,
|
||||
knownState: CoValueKnownState | "empty",
|
||||
) {
|
||||
if (this._optimisticKnownStates === "assumeInfallible") {
|
||||
this._knownStates.set(id, knownState);
|
||||
} else {
|
||||
this._optimisticKnownStates.set(id, knownState);
|
||||
}
|
||||
this._optimisticKnownStates.set(id, knownState);
|
||||
}
|
||||
|
||||
get id() {
|
||||
|
||||
@@ -2,11 +2,7 @@ import { UpDownCounter, ValueType, metrics } from "@opentelemetry/api";
|
||||
import { Result, err } from "neverthrow";
|
||||
import { PeerState } from "../PeerState.js";
|
||||
import { RawCoValue } from "../coValue.js";
|
||||
import {
|
||||
ControlledAccount,
|
||||
ControlledAccountOrAgent,
|
||||
RawAccountID,
|
||||
} from "../coValues/account.js";
|
||||
import { ControlledAccountOrAgent, RawAccountID } from "../coValues/account.js";
|
||||
import { RawGroup } from "../coValues/group.js";
|
||||
import { coreToCoValue } from "../coreToCoValue.js";
|
||||
import {
|
||||
@@ -315,14 +311,10 @@ export class CoValueCore {
|
||||
}
|
||||
}
|
||||
|
||||
contentInClonedNodeWithDifferentAccount(
|
||||
controlledAccountOrAgent: ControlledAccountOrAgent,
|
||||
): RawCoValue {
|
||||
const newNode = this.node.cloneWithDifferentAccount(
|
||||
controlledAccountOrAgent,
|
||||
);
|
||||
|
||||
return newNode.expectCoValueLoaded(this.id).getCurrentContent();
|
||||
contentInClonedNodeWithDifferentAccount(account: ControlledAccountOrAgent) {
|
||||
return this.node
|
||||
.loadCoValueAsDifferentAgent(this.id, account.agentSecret, account.id)
|
||||
.getCurrentContent();
|
||||
}
|
||||
|
||||
knownState(): CoValueKnownState {
|
||||
@@ -656,15 +648,15 @@ export class CoValueCore {
|
||||
a: Pick<DecryptedTransaction, "madeAt" | "txID">,
|
||||
b: Pick<DecryptedTransaction, "madeAt" | "txID">,
|
||||
) {
|
||||
return (
|
||||
a.madeAt - b.madeAt ||
|
||||
(a.txID.sessionID === b.txID.sessionID
|
||||
? 0
|
||||
: a.txID.sessionID < b.txID.sessionID
|
||||
? -1
|
||||
: 1) ||
|
||||
a.txID.txIndex - b.txID.txIndex
|
||||
);
|
||||
if (a.madeAt !== b.madeAt) {
|
||||
return a.madeAt - b.madeAt;
|
||||
}
|
||||
|
||||
if (a.txID.sessionID === b.txID.sessionID) {
|
||||
return a.txID.txIndex - b.txID.txIndex;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
getCurrentReadKey(): {
|
||||
@@ -1006,6 +998,10 @@ export class CoValueCore {
|
||||
const waitingForPeer = new Promise<void>((resolve) => {
|
||||
const markNotFound = () => {
|
||||
if (this.peers.get(peer.id)?.type === "pending") {
|
||||
logger.warn("Timeout waiting for peer to load coValue", {
|
||||
id: this.id,
|
||||
peerID: peer.id,
|
||||
});
|
||||
this.markNotFoundInPeer(peer.id);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -319,11 +319,7 @@ export class RawBinaryCoStreamView<
|
||||
return lastItem?.type === "end";
|
||||
}
|
||||
|
||||
getBinaryChunks(
|
||||
allowUnfinished?: boolean,
|
||||
):
|
||||
| (BinaryStreamInfo & { chunks: Uint8Array[]; finished: boolean })
|
||||
| undefined {
|
||||
getBinaryStreamInfo(): BinaryStreamInfo | undefined {
|
||||
const items = this.getSingleStream();
|
||||
|
||||
// No active streams
|
||||
@@ -336,6 +332,27 @@ export class RawBinaryCoStreamView<
|
||||
return;
|
||||
}
|
||||
|
||||
return {
|
||||
mimeType: start.mimeType,
|
||||
fileName: start.fileName,
|
||||
totalSizeBytes: start.totalSizeBytes,
|
||||
};
|
||||
}
|
||||
|
||||
getBinaryChunks(
|
||||
allowUnfinished?: boolean,
|
||||
):
|
||||
| (BinaryStreamInfo & { chunks: Uint8Array[]; finished: boolean })
|
||||
| undefined {
|
||||
const items = this.getSingleStream();
|
||||
|
||||
// No active streams
|
||||
if (!items) return;
|
||||
|
||||
const info = this.getBinaryStreamInfo();
|
||||
|
||||
if (!info) return;
|
||||
|
||||
const end = items[items.length - 1];
|
||||
|
||||
if (end?.type !== "end" && !allowUnfinished) return;
|
||||
@@ -360,9 +377,7 @@ export class RawBinaryCoStreamView<
|
||||
}
|
||||
|
||||
return {
|
||||
mimeType: start.mimeType,
|
||||
fileName: start.fileName,
|
||||
totalSizeBytes: start.totalSizeBytes,
|
||||
...info,
|
||||
chunks,
|
||||
finished,
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Result, ResultAsync, err, ok, okAsync } from "neverthrow";
|
||||
import { Result, err, ok } from "neverthrow";
|
||||
import { CoID } from "./coValue.js";
|
||||
import { RawCoValue } from "./coValue.js";
|
||||
import {
|
||||
@@ -223,10 +223,19 @@ export class LocalNode {
|
||||
account.set("profile", profile.id, "trusting");
|
||||
}
|
||||
|
||||
if (!account.get("profile")) {
|
||||
const profileId = account.get("profile");
|
||||
|
||||
if (!profileId) {
|
||||
throw new Error("Must set account profile in initial migration");
|
||||
}
|
||||
|
||||
if (node.syncManager.hasStoragePeers()) {
|
||||
await Promise.all([
|
||||
node.syncManager.waitForStorageSync(account.id),
|
||||
node.syncManager.waitForStorageSync(profileId),
|
||||
]);
|
||||
}
|
||||
|
||||
return {
|
||||
node,
|
||||
accountID: account.id,
|
||||
@@ -272,11 +281,9 @@ export class LocalNode {
|
||||
if (!profileID) {
|
||||
throw new Error("Account has no profile");
|
||||
}
|
||||
const profile = await node.load(profileID);
|
||||
|
||||
if (profile === "unavailable") {
|
||||
throw new Error("Profile unavailable from all peers");
|
||||
}
|
||||
// Preload the profile
|
||||
await node.load(profileID);
|
||||
|
||||
if (migration) {
|
||||
await migration(account, node);
|
||||
@@ -426,32 +433,44 @@ export class LocalNode {
|
||||
groupOrOwnedValueID: CoID<T>,
|
||||
inviteSecret: InviteSecret,
|
||||
): Promise<void> {
|
||||
const groupOrOwnedValue = await this.load(groupOrOwnedValueID);
|
||||
const value = await this.load(groupOrOwnedValueID);
|
||||
|
||||
if (groupOrOwnedValue === "unavailable") {
|
||||
if (value === "unavailable") {
|
||||
throw new Error(
|
||||
"Trying to accept invite: Group/owned value unavailable from all peers",
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
groupOrOwnedValue.core.verified.header.ruleset.type === "ownedByGroup"
|
||||
) {
|
||||
return this.acceptInvite(
|
||||
groupOrOwnedValue.core.verified.header.ruleset.group as CoID<RawGroup>,
|
||||
inviteSecret,
|
||||
);
|
||||
} else if (
|
||||
groupOrOwnedValue.core.verified.header.ruleset.type !== "group"
|
||||
) {
|
||||
throw new Error("Can only accept invites to groups");
|
||||
const ruleset = value.core.verified.header.ruleset;
|
||||
|
||||
let group: RawGroup;
|
||||
|
||||
if (ruleset.type === "unsafeAllowAll") {
|
||||
throw new Error("Can only accept invites to values owned by groups");
|
||||
}
|
||||
|
||||
const group = expectGroup(groupOrOwnedValue);
|
||||
if (ruleset.type === "ownedByGroup") {
|
||||
const owner = await this.load(ruleset.group as CoID<RawGroup>);
|
||||
|
||||
if (owner === "unavailable") {
|
||||
throw new Error(
|
||||
"Trying to accept invite: CoValue owner unavailable from all peers",
|
||||
);
|
||||
}
|
||||
|
||||
group = expectGroup(owner);
|
||||
} else {
|
||||
group = expectGroup(value);
|
||||
}
|
||||
|
||||
if (group.core.verified.header.meta?.type === "account") {
|
||||
throw new Error("Can't accept invites to values owned by accounts");
|
||||
}
|
||||
|
||||
const inviteAgentSecret = this.crypto.agentSecretFromSecretSeed(
|
||||
secretSeedFromInviteSecret(inviteSecret),
|
||||
);
|
||||
|
||||
const inviteAgentID = this.crypto.getAgentID(inviteAgentSecret);
|
||||
|
||||
const inviteRole = await new Promise((resolve, reject) => {
|
||||
@@ -486,9 +505,10 @@ export class LocalNode {
|
||||
}
|
||||
|
||||
const groupAsInvite = expectGroup(
|
||||
group.core.contentInClonedNodeWithDifferentAccount(
|
||||
new ControlledAgent(inviteAgentSecret, this.crypto),
|
||||
),
|
||||
this.loadCoValueAsDifferentAgent(
|
||||
group.id,
|
||||
inviteAgentSecret,
|
||||
).getCurrentContent(),
|
||||
);
|
||||
|
||||
groupAsInvite.addMemberInternal(
|
||||
@@ -517,7 +537,7 @@ export class LocalNode {
|
||||
|
||||
if (!coValue.isAvailable()) {
|
||||
throw new Error(
|
||||
`${expectation ? expectation + ": " : ""}CoValue ${id} not yet loaded. Current state: ${JSON.stringify(coValue)}`,
|
||||
`${expectation ? expectation + ": " : ""}CoValue ${id} not yet loaded.`,
|
||||
);
|
||||
}
|
||||
return coValue;
|
||||
@@ -614,46 +634,56 @@ export class LocalNode {
|
||||
return group;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
cloneWithDifferentAccount(
|
||||
controlledAccountOrAgent: ControlledAccountOrAgent,
|
||||
): LocalNode {
|
||||
loadCoValueAsDifferentAgent(
|
||||
id: RawCoID,
|
||||
secret: AgentSecret,
|
||||
accountId?: RawAccountID | AgentID,
|
||||
) {
|
||||
const agent = new ControlledAgent(secret, this.crypto);
|
||||
|
||||
const newNode = new LocalNode(
|
||||
controlledAccountOrAgent.agentSecret,
|
||||
this.crypto.newRandomSessionID(controlledAccountOrAgent.id),
|
||||
secret,
|
||||
this.crypto.newRandomSessionID(accountId || agent.id),
|
||||
this.crypto,
|
||||
);
|
||||
|
||||
newNode.cloneVerifiedStateFrom(this);
|
||||
newNode.cloneVerifiedStateFrom(this, id);
|
||||
|
||||
return newNode;
|
||||
return newNode.expectCoValueLoaded(id);
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
cloneVerifiedStateFrom(otherNode: LocalNode) {
|
||||
const coValuesToCopy = Array.from(otherNode.coValues.entries());
|
||||
cloneVerifiedStateFrom(otherNode: LocalNode, id: RawCoID) {
|
||||
const coValuesIdsToCopy = [id];
|
||||
|
||||
while (coValuesToCopy.length > 0) {
|
||||
const [coValueID, coValue] = coValuesToCopy[coValuesToCopy.length - 1]!;
|
||||
// Scan all the dependencies and add them to the list
|
||||
for (let i = 0; i < coValuesIdsToCopy.length; i++) {
|
||||
const coValueID = coValuesIdsToCopy[i]!;
|
||||
const coValue = otherNode.getCoValue(coValueID);
|
||||
|
||||
if (!coValue.isAvailable()) {
|
||||
coValuesToCopy.pop();
|
||||
continue;
|
||||
} else {
|
||||
const allDepsCopied = coValue
|
||||
.getDependedOnCoValues()
|
||||
.every((dep) => this.coValues.get(dep)?.isAvailable());
|
||||
|
||||
if (!allDepsCopied) {
|
||||
// move to end of queue
|
||||
coValuesToCopy.unshift(coValuesToCopy.pop()!);
|
||||
continue;
|
||||
}
|
||||
|
||||
this.putCoValue(coValueID, coValue.verified);
|
||||
|
||||
coValuesToCopy.pop();
|
||||
}
|
||||
|
||||
for (const dep of coValue.getDependedOnCoValues()) {
|
||||
coValuesIdsToCopy.push(dep);
|
||||
}
|
||||
}
|
||||
|
||||
// Copy the coValue all the dependencies by following the dependency order
|
||||
while (coValuesIdsToCopy.length > 0) {
|
||||
const coValueID = coValuesIdsToCopy.pop()!;
|
||||
const coValue = otherNode.getCoValue(coValueID);
|
||||
|
||||
if (!coValue.isAvailable()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (this.coValues.get(coValueID)?.isAvailable()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
this.putCoValue(coValueID, coValue.verified);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,23 +6,17 @@ export function connectedPeers(
|
||||
peer1id: PeerID,
|
||||
peer2id: PeerID,
|
||||
{
|
||||
trace = false,
|
||||
peer1role = "client",
|
||||
peer2role = "client",
|
||||
crashOnClose = false,
|
||||
}: {
|
||||
trace?: boolean;
|
||||
peer1role?: Peer["role"];
|
||||
peer2role?: Peer["role"];
|
||||
crashOnClose?: boolean;
|
||||
} = {},
|
||||
): [Peer, Peer] {
|
||||
const [from1to2Rx, from1to2Tx] = newQueuePair(
|
||||
trace ? { traceAs: `${peer1id} -> ${peer2id}` } : undefined,
|
||||
);
|
||||
const [from2to1Rx, from2to1Tx] = newQueuePair(
|
||||
trace ? { traceAs: `${peer2id} -> ${peer1id}` } : undefined,
|
||||
);
|
||||
const [from1to2Rx, from1to2Tx] = newQueuePair();
|
||||
const [from2to1Rx, from2to1Tx] = newQueuePair();
|
||||
|
||||
const peer2AsPeer: Peer = {
|
||||
id: peer2id,
|
||||
@@ -43,32 +37,11 @@ export function connectedPeers(
|
||||
return [peer1AsPeer, peer2AsPeer];
|
||||
}
|
||||
|
||||
export function newQueuePair(
|
||||
options: { traceAs?: string } = {},
|
||||
): [AsyncIterable<SyncMessage>, Channel<SyncMessage>] {
|
||||
export function newQueuePair(): [
|
||||
AsyncIterable<SyncMessage>,
|
||||
Channel<SyncMessage>,
|
||||
] {
|
||||
const channel = new Channel<SyncMessage>();
|
||||
|
||||
if (options.traceAs) {
|
||||
return [
|
||||
(async function* () {
|
||||
for await (const msg of channel) {
|
||||
console.debug(
|
||||
options.traceAs,
|
||||
JSON.stringify(
|
||||
msg,
|
||||
(k, v) =>
|
||||
k === "changes" || k === "encryptedChanges"
|
||||
? v.slice(0, 20) + "..."
|
||||
: v,
|
||||
2,
|
||||
),
|
||||
);
|
||||
yield msg;
|
||||
}
|
||||
})(),
|
||||
channel,
|
||||
];
|
||||
} else {
|
||||
return [channel.wrap(), channel];
|
||||
}
|
||||
return [channel.wrap(), channel];
|
||||
}
|
||||
|
||||
@@ -159,6 +159,12 @@ export class SyncManager {
|
||||
);
|
||||
}
|
||||
|
||||
hasStoragePeers(): boolean {
|
||||
return this.getPeers().some(
|
||||
(peer) => peer.role === "storage" && !peer.closed,
|
||||
);
|
||||
}
|
||||
|
||||
handleSyncMessage(msg: SyncMessage, peer: PeerState) {
|
||||
if (this.local.getCoValue(msg.id).isErroredInPeer(peer.id)) {
|
||||
logger.warn(
|
||||
@@ -393,10 +399,12 @@ export class SyncManager {
|
||||
|
||||
return;
|
||||
} else {
|
||||
// Should move the state to loading
|
||||
this.local.loadCoValueCore(msg.id, peer.id).catch((e) => {
|
||||
logger.error("Error loading coValue in handleLoad", { err: e });
|
||||
});
|
||||
// Syncronously updates the state loading is possible
|
||||
coValue
|
||||
.loadFromPeers(this.getServerAndStoragePeers(peer.id))
|
||||
.catch((e) => {
|
||||
logger.error("Error loading coValue in handleLoad", { err: e });
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -622,28 +630,24 @@ export class SyncManager {
|
||||
|
||||
handleUnsubscribe(_msg: DoneMessage) {}
|
||||
|
||||
requestedSyncs = new Map<RawCoID, Promise<void>>();
|
||||
|
||||
async requestCoValueSync(coValue: CoValueCore) {
|
||||
const promise = this.requestedSyncs.get(coValue.id);
|
||||
|
||||
if (promise) {
|
||||
return promise;
|
||||
} else {
|
||||
const promise = new Promise<void>((resolve) => {
|
||||
queueMicrotask(() => {
|
||||
this.requestedSyncs.delete(coValue.id);
|
||||
this.syncCoValue(coValue);
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
|
||||
this.requestedSyncs.set(coValue.id, promise);
|
||||
return promise;
|
||||
requestedSyncs = new Set<RawCoID>();
|
||||
requestCoValueSync(coValue: CoValueCore) {
|
||||
if (this.requestedSyncs.has(coValue.id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
queueMicrotask(() => {
|
||||
if (this.requestedSyncs.has(coValue.id)) {
|
||||
this.syncCoValue(coValue);
|
||||
}
|
||||
});
|
||||
|
||||
this.requestedSyncs.add(coValue.id);
|
||||
}
|
||||
|
||||
async syncCoValue(coValue: CoValueCore) {
|
||||
this.requestedSyncs.delete(coValue.id);
|
||||
|
||||
for (const peer of this.peersInPriorityOrder()) {
|
||||
if (peer.closed) continue;
|
||||
if (coValue.isErroredInPeer(peer.id)) continue;
|
||||
@@ -674,6 +678,21 @@ export class SyncManager {
|
||||
return true;
|
||||
}
|
||||
|
||||
const peerState = this.peers[peerId];
|
||||
|
||||
// The peer has been closed, so it isn't possible to sync
|
||||
if (!peerState || peerState.closed) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// The client isn't subscribed to the coValue, so we won't sync it
|
||||
if (
|
||||
peerState.role === "client" &&
|
||||
!peerState.optimisticKnownStates.has(id)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const unsubscribe = this.syncState.subscribeToPeerUpdates(
|
||||
peerId,
|
||||
@@ -693,10 +712,20 @@ export class SyncManager {
|
||||
});
|
||||
}
|
||||
|
||||
async waitForStorageSync(id: RawCoID, timeout = 30_000) {
|
||||
const peers = this.getPeers();
|
||||
|
||||
await Promise.all(
|
||||
peers
|
||||
.filter((peer) => peer.role === "storage")
|
||||
.map((peer) => this.waitForSyncWithPeer(peer.id, id, timeout)),
|
||||
);
|
||||
}
|
||||
|
||||
async waitForSync(id: RawCoID, timeout = 30_000) {
|
||||
const peers = this.getPeers();
|
||||
|
||||
return Promise.all(
|
||||
await Promise.all(
|
||||
peers.map((peer) => this.waitForSyncWithPeer(peer.id, id, timeout)),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -174,9 +174,6 @@ describe("PeerState", () => {
|
||||
test("should dispatch to both states", () => {
|
||||
const { peerState } = setup();
|
||||
const knownStatesSpy = vi.spyOn(peerState._knownStates, "set");
|
||||
if (peerState._optimisticKnownStates === "assumeInfallible") {
|
||||
throw new Error("Expected normal optimisticKnownStates");
|
||||
}
|
||||
|
||||
const optimisticKnownStatesSpy = vi.spyOn(
|
||||
peerState._optimisticKnownStates,
|
||||
@@ -195,40 +192,6 @@ describe("PeerState", () => {
|
||||
expect(optimisticKnownStatesSpy).toHaveBeenCalledWith("co_z1", state);
|
||||
});
|
||||
|
||||
test("should use same reference for knownStates and optimisticKnownStates for storage peers", () => {
|
||||
const mockStoragePeer: Peer = {
|
||||
id: "test-storage-peer",
|
||||
role: "storage",
|
||||
priority: 1,
|
||||
crashOnClose: false,
|
||||
incoming: (async function* () {})(),
|
||||
outgoing: {
|
||||
push: vi.fn().mockResolvedValue(undefined),
|
||||
close: vi.fn(),
|
||||
},
|
||||
};
|
||||
const peerState = new PeerState(mockStoragePeer, undefined);
|
||||
|
||||
// Verify they are the same reference
|
||||
expect(peerState.knownStates).toBe(peerState.optimisticKnownStates);
|
||||
|
||||
// Verify that dispatching only updates one state
|
||||
const knownStatesSpy = vi.spyOn(peerState._knownStates, "set");
|
||||
expect(peerState._optimisticKnownStates).toBe("assumeInfallible");
|
||||
|
||||
const state: CoValueKnownState = {
|
||||
id: "co_z1",
|
||||
header: false,
|
||||
sessions: {},
|
||||
};
|
||||
|
||||
peerState.setKnownState("co_z1", state);
|
||||
|
||||
// Only one dispatch should happen since they're the same reference
|
||||
expect(knownStatesSpy).toHaveBeenCalledTimes(1);
|
||||
expect(knownStatesSpy).toHaveBeenCalledWith("co_z1", state);
|
||||
});
|
||||
|
||||
test("should use separate references for knownStates and optimisticKnownStates for non-storage peers", () => {
|
||||
const { peerState } = setup(); // Uses a regular peer
|
||||
|
||||
|
||||
@@ -70,12 +70,12 @@ describe("SyncStateManager", () => {
|
||||
const map = group.createMap();
|
||||
map.set("key1", "value1", "trusting");
|
||||
|
||||
const [clientStoragePeer] = connectedPeers("clientStorage", "unusedPeer", {
|
||||
peer1role: "client",
|
||||
peer2role: "server",
|
||||
const [serverPeer] = connectedPeers("serverPeer", "unusedPeer", {
|
||||
peer1role: "server",
|
||||
peer2role: "client",
|
||||
});
|
||||
|
||||
client.node.syncManager.addPeer(clientStoragePeer);
|
||||
client.node.syncManager.addPeer(serverPeer);
|
||||
|
||||
const subscriptionManager = client.node.syncManager.syncState;
|
||||
|
||||
@@ -86,7 +86,7 @@ describe("SyncStateManager", () => {
|
||||
updateToJazzCloudSpy,
|
||||
);
|
||||
const unsubscribe2 = subscriptionManager.subscribeToPeerUpdates(
|
||||
clientStoragePeer.id,
|
||||
serverPeer.id,
|
||||
updateToStorageSpy,
|
||||
);
|
||||
|
||||
@@ -115,7 +115,7 @@ describe("SyncStateManager", () => {
|
||||
);
|
||||
|
||||
expect(updateToStorageSpy).toHaveBeenLastCalledWith(
|
||||
emptyKnownState(map.core.id),
|
||||
emptyKnownState(group.core.id),
|
||||
{ uploaded: false },
|
||||
);
|
||||
});
|
||||
@@ -247,4 +247,54 @@ describe("SyncStateManager", () => {
|
||||
),
|
||||
).toEqual({ uploaded: true });
|
||||
});
|
||||
|
||||
test("should skip closed peers", async () => {
|
||||
const client = setupTestNode();
|
||||
const { peerState } = client.connectToSyncServer();
|
||||
|
||||
peerState.gracefulShutdown();
|
||||
|
||||
const group = client.node.createGroup();
|
||||
const map = group.createMap();
|
||||
|
||||
await expect(map.core.waitForSync()).resolves.toBeUndefined();
|
||||
});
|
||||
|
||||
test("should skip client peers that are not subscribed to the coValue", async () => {
|
||||
const server = setupTestNode({ isSyncServer: true });
|
||||
const client = setupTestNode();
|
||||
|
||||
client.connectToSyncServer({
|
||||
syncServer: server.node,
|
||||
});
|
||||
|
||||
const group = server.node.createGroup();
|
||||
const map = group.createMap();
|
||||
|
||||
await map.core.waitForSync();
|
||||
|
||||
expect(client.node.getCoValue(map.id).isAvailable()).toBe(false);
|
||||
});
|
||||
|
||||
test("should wait for client peers that are subscribed to the coValue", async () => {
|
||||
const server = setupTestNode({ isSyncServer: true });
|
||||
const client = setupTestNode();
|
||||
|
||||
const { peerStateOnServer } = client.connectToSyncServer();
|
||||
|
||||
const group = server.node.createGroup();
|
||||
const map = group.createMap();
|
||||
map.set("key1", "value1", "trusting");
|
||||
|
||||
// Simulate the subscription to the coValue
|
||||
peerStateOnServer.setKnownState(map.core.id, {
|
||||
id: map.core.id,
|
||||
header: true,
|
||||
sessions: {},
|
||||
});
|
||||
|
||||
await map.core.waitForSync();
|
||||
|
||||
expect(client.node.getCoValue(map.id).isAvailable()).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import { expect, test } from "vitest";
|
||||
import { expectAccount } from "../coValues/account.js";
|
||||
import { WasmCrypto } from "../crypto/WasmCrypto.js";
|
||||
import { LocalNode } from "../localNode.js";
|
||||
import { connectedPeers } from "../streamUtils.js";
|
||||
import { createMockStoragePeer } from "./testUtils.js";
|
||||
|
||||
const Crypto = await WasmCrypto.create();
|
||||
|
||||
@@ -85,3 +87,25 @@ test("throws an error if the user tried to create an invite from an account", as
|
||||
"Cannot create invite from an account",
|
||||
);
|
||||
});
|
||||
|
||||
test("wait for storage sync before resolving withNewlyCreatedAccount", async () => {
|
||||
const { storage, peer } = createMockStoragePeer({
|
||||
peerId: "account-node",
|
||||
});
|
||||
|
||||
const { node, accountID } = await LocalNode.withNewlyCreatedAccount({
|
||||
creationProps: { name: "Hermes Puggington" },
|
||||
crypto: Crypto,
|
||||
peersToLoadFrom: [peer],
|
||||
});
|
||||
|
||||
const account = storage.getCoValue(accountID);
|
||||
|
||||
expect(account.isAvailable()).toBe(true);
|
||||
|
||||
const profile = storage.getCoValue(
|
||||
expectAccount(account.getCurrentContent()).get("profile")!,
|
||||
);
|
||||
|
||||
expect(profile.isAvailable()).toBe(true);
|
||||
});
|
||||
|
||||
@@ -750,7 +750,6 @@ describe("extend with role mapping", () => {
|
||||
const mapOnNode2 = await loadCoValueOrFail(node2.node, map.id);
|
||||
|
||||
expect(mapOnNode2.get("test")).toEqual("Written from the admin");
|
||||
|
||||
mapOnNode2.set("test", "Written from the inherited role");
|
||||
expect(mapOnNode2.get("test")).toEqual("Written from the inherited role");
|
||||
|
||||
|
||||
@@ -512,7 +512,6 @@ test("Admins can set group read key and then use it to create private transactio
|
||||
const { node, groupCore, admin } = newGroup();
|
||||
|
||||
const reader1 = createAccountInNode(node);
|
||||
|
||||
const reader2 = createAccountInNode(node);
|
||||
|
||||
const { secret: readKey, id: readKeyID } = Crypto.newRandomKeySecret();
|
||||
@@ -583,6 +582,9 @@ test("Admins can set group read key and then use it to create private transactio
|
||||
childObject.contentInClonedNodeWithDifferentAccount(reader2),
|
||||
);
|
||||
|
||||
// Need to copy the account coValue to the new node to be able to read the readKey
|
||||
childObjectAsReader2.core.node.cloneVerifiedStateFrom(node, reader2.id);
|
||||
|
||||
expect(childObjectAsReader2.core.getCurrentReadKey().secret).toEqual(readKey);
|
||||
expect(childObjectAsReader2.get("foo")).toEqual("bar");
|
||||
});
|
||||
@@ -1113,9 +1115,10 @@ test("Admins can create an adminInvite, which can add an admin (high-level)", as
|
||||
const invitedAdminSecret = Crypto.newRandomAgentSecret();
|
||||
const invitedAdminID = Crypto.getAgentID(invitedAdminSecret);
|
||||
|
||||
const nodeAsInvitedAdmin = node.cloneWithDifferentAccount(
|
||||
new ControlledAgent(invitedAdminSecret, Crypto),
|
||||
);
|
||||
const nodeAsInvitedAdmin = node.loadCoValueAsDifferentAgent(
|
||||
group.id,
|
||||
invitedAdminSecret,
|
||||
).node;
|
||||
|
||||
await nodeAsInvitedAdmin.acceptInvite(group.id, inviteSecret);
|
||||
|
||||
@@ -1219,9 +1222,10 @@ test("Admins can create a writerInvite, which can add a writer (high-level)", as
|
||||
const invitedWriterSecret = Crypto.newRandomAgentSecret();
|
||||
const invitedWriterID = Crypto.getAgentID(invitedWriterSecret);
|
||||
|
||||
const nodeAsInvitedWriter = node.cloneWithDifferentAccount(
|
||||
new ControlledAgent(invitedWriterSecret, Crypto),
|
||||
);
|
||||
const nodeAsInvitedWriter = node.loadCoValueAsDifferentAgent(
|
||||
group.id,
|
||||
invitedWriterSecret,
|
||||
).node;
|
||||
|
||||
await nodeAsInvitedWriter.acceptInvite(group.id, inviteSecret);
|
||||
|
||||
@@ -1308,9 +1312,10 @@ test("Admins can create a readerInvite, which can add a reader (high-level)", as
|
||||
const invitedReaderSecret = Crypto.newRandomAgentSecret();
|
||||
const invitedReaderID = Crypto.getAgentID(invitedReaderSecret);
|
||||
|
||||
const nodeAsInvitedReader = node.cloneWithDifferentAccount(
|
||||
new ControlledAgent(invitedReaderSecret, Crypto),
|
||||
);
|
||||
const nodeAsInvitedReader = node.loadCoValueAsDifferentAgent(
|
||||
group.id,
|
||||
invitedReaderSecret,
|
||||
).node;
|
||||
|
||||
await nodeAsInvitedReader.acceptInvite(group.id, inviteSecret);
|
||||
|
||||
|
||||
@@ -1,16 +1,11 @@
|
||||
import { assert, beforeEach, describe, expect, test } from "vitest";
|
||||
|
||||
import { expectMap } from "../coValue";
|
||||
import { expectAccount } from "../coValues/account";
|
||||
import { WasmCrypto } from "../crypto/WasmCrypto";
|
||||
import { LocalNode } from "../localNode";
|
||||
import { toSimplifiedMessages } from "./messagesTestUtils";
|
||||
import {
|
||||
SyncMessagesLog,
|
||||
getSyncServerConnectedPeer,
|
||||
loadCoValueOrFail,
|
||||
setupTestNode,
|
||||
waitFor,
|
||||
} from "./testUtils";
|
||||
|
||||
const Crypto = await WasmCrypto.create();
|
||||
@@ -27,7 +22,7 @@ describe("LocalNode auth sync", () => {
|
||||
peerId: "new-account",
|
||||
});
|
||||
|
||||
const { accountID, node } = await LocalNode.withNewlyCreatedAccount({
|
||||
const { node } = await LocalNode.withNewlyCreatedAccount({
|
||||
creationProps: {
|
||||
name: "new-account",
|
||||
},
|
||||
@@ -128,6 +123,7 @@ describe("LocalNode auth sync", () => {
|
||||
test("authenticate to an existing account", async () => {
|
||||
const { peer: newAccountPeer } = getSyncServerConnectedPeer({
|
||||
peerId: "new-account",
|
||||
ourName: "creation-node",
|
||||
});
|
||||
|
||||
const { accountID, accountSecret } =
|
||||
@@ -141,10 +137,9 @@ describe("LocalNode auth sync", () => {
|
||||
|
||||
const { peer: existingAccountPeer } = getSyncServerConnectedPeer({
|
||||
peerId: "existing-account",
|
||||
ourName: "auth-node",
|
||||
});
|
||||
|
||||
SyncMessagesLog.clear();
|
||||
|
||||
const node = await LocalNode.withLoadedAccount({
|
||||
accountID,
|
||||
accountSecret,
|
||||
@@ -169,19 +164,82 @@ describe("LocalNode auth sync", () => {
|
||||
}),
|
||||
).toMatchInlineSnapshot(`
|
||||
[
|
||||
"client -> server | LOAD Account sessions: empty",
|
||||
"server -> client | KNOWN Account sessions: header/4",
|
||||
"client -> server | CONTENT ProfileGroup header: true new: After: 0 New: 5",
|
||||
"server -> client | CONTENT Account header: true new: After: 0 New: 4",
|
||||
"server -> client | KNOWN ProfileGroup sessions: header/5",
|
||||
"client -> server | CONTENT Profile header: true new: After: 0 New: 1",
|
||||
"client -> server | KNOWN Account sessions: header/4",
|
||||
"server -> client | KNOWN Profile sessions: header/1",
|
||||
"client -> server | LOAD Profile sessions: empty",
|
||||
"server -> client | CONTENT ProfileGroup header: true new: After: 0 New: 5",
|
||||
"client -> server | KNOWN ProfileGroup sessions: header/5",
|
||||
"server -> client | CONTENT Profile header: true new: After: 0 New: 1",
|
||||
"client -> server | KNOWN Profile sessions: header/1",
|
||||
"creation-node -> server | CONTENT Account header: true new: After: 0 New: 4",
|
||||
"auth-node -> server | LOAD Account sessions: empty",
|
||||
"server -> creation-node | KNOWN Account sessions: header/4",
|
||||
"creation-node -> server | CONTENT ProfileGroup header: true new: After: 0 New: 5",
|
||||
"server -> auth-node | CONTENT Account header: true new: After: 0 New: 4",
|
||||
"server -> creation-node | KNOWN ProfileGroup sessions: header/5",
|
||||
"creation-node -> server | CONTENT Profile header: true new: After: 0 New: 1",
|
||||
"auth-node -> server | KNOWN Account sessions: header/4",
|
||||
"server -> creation-node | KNOWN Profile sessions: header/1",
|
||||
"auth-node -> server | LOAD Profile sessions: empty",
|
||||
"server -> auth-node | CONTENT ProfileGroup header: true new: After: 0 New: 5",
|
||||
"auth-node -> server | KNOWN ProfileGroup sessions: header/5",
|
||||
"server -> auth-node | CONTENT Profile header: true new: After: 0 New: 1",
|
||||
"auth-node -> server | KNOWN Profile sessions: header/1",
|
||||
]
|
||||
`);
|
||||
});
|
||||
|
||||
test("authenticate to an existing account after immediately close the creation node", async () => {
|
||||
const { peer: newAccountPeer } = getSyncServerConnectedPeer({
|
||||
peerId: "new-account",
|
||||
ourName: "creation-node",
|
||||
});
|
||||
|
||||
const {
|
||||
accountID,
|
||||
accountSecret,
|
||||
node: creationNode,
|
||||
} = await LocalNode.withNewlyCreatedAccount({
|
||||
creationProps: {
|
||||
name: "new-account",
|
||||
},
|
||||
peersToLoadFrom: [newAccountPeer],
|
||||
crypto: Crypto,
|
||||
});
|
||||
|
||||
creationNode.gracefulShutdown();
|
||||
|
||||
const { peer: existingAccountPeer } = getSyncServerConnectedPeer({
|
||||
peerId: "existing-account",
|
||||
ourName: "auth-node",
|
||||
});
|
||||
|
||||
const node = await LocalNode.withLoadedAccount({
|
||||
accountID,
|
||||
accountSecret,
|
||||
peersToLoadFrom: [existingAccountPeer],
|
||||
sessionID: undefined,
|
||||
crypto: Crypto,
|
||||
});
|
||||
|
||||
const account = node.expectCurrentAccount("after login");
|
||||
const profile = creationNode.getCoValue(account.get("profile")!);
|
||||
|
||||
assert(profile.isAvailable());
|
||||
|
||||
expect(account.id).toBe(accountID);
|
||||
expect(node.agentSecret).toBe(accountSecret);
|
||||
|
||||
expect(
|
||||
SyncMessagesLog.getMessages({
|
||||
Account: account.core,
|
||||
Profile: profile,
|
||||
ProfileGroup: profile.getGroup().core,
|
||||
}),
|
||||
).toMatchInlineSnapshot(`
|
||||
[
|
||||
"creation-node -> server | CONTENT Account header: true new: After: 0 New: 4",
|
||||
"auth-node -> server | LOAD Account sessions: empty",
|
||||
"server -> creation-node | KNOWN Account sessions: header/4",
|
||||
"server -> auth-node | CONTENT Account header: true new: After: 0 New: 4",
|
||||
"auth-node -> server | KNOWN Account sessions: header/4",
|
||||
"auth-node -> server | LOAD Profile sessions: empty",
|
||||
"server -> auth-node | KNOWN Profile sessions: empty",
|
||||
"auth-node -> server | LOAD Profile sessions: empty",
|
||||
"server -> auth-node | KNOWN Profile sessions: empty",
|
||||
]
|
||||
`);
|
||||
});
|
||||
|
||||
163
packages/cojson/src/tests/sync.invite.test.ts
Normal file
163
packages/cojson/src/tests/sync.invite.test.ts
Normal file
@@ -0,0 +1,163 @@
|
||||
import { beforeEach, describe, expect, test } from "vitest";
|
||||
|
||||
import { expectList, expectMap } from "../coValue";
|
||||
import { WasmCrypto } from "../crypto/WasmCrypto";
|
||||
import {
|
||||
SyncMessagesLog,
|
||||
loadCoValueOrFail,
|
||||
setupTestNode,
|
||||
waitFor,
|
||||
} from "./testUtils";
|
||||
|
||||
const Crypto = await WasmCrypto.create();
|
||||
let jazzCloud = setupTestNode({ isSyncServer: true });
|
||||
|
||||
beforeEach(async () => {
|
||||
SyncMessagesLog.clear();
|
||||
jazzCloud = setupTestNode({ isSyncServer: true });
|
||||
});
|
||||
|
||||
describe("invitations sync", () => {
|
||||
test("invite a client to a group", async () => {
|
||||
const client = setupTestNode();
|
||||
client.connectToSyncServer({
|
||||
ourName: "invite-provider",
|
||||
});
|
||||
const client2 = setupTestNode();
|
||||
client2.connectToSyncServer({
|
||||
ourName: "invite-consumer",
|
||||
});
|
||||
|
||||
const group = client.node.createGroup();
|
||||
const map = group.createMap();
|
||||
map.set("hello", "world");
|
||||
|
||||
await map.core.waitForSync();
|
||||
|
||||
const invite = group.createInvite("reader");
|
||||
|
||||
await group.core.waitForSync();
|
||||
SyncMessagesLog.clear();
|
||||
|
||||
await client2.node.acceptInvite(map.id, invite);
|
||||
|
||||
const mapOnClient2 = await loadCoValueOrFail(client2.node, map.id);
|
||||
expect(mapOnClient2.get("hello")).toEqual("world");
|
||||
|
||||
expect(
|
||||
SyncMessagesLog.getMessages({
|
||||
Group: group.core,
|
||||
Map: map.core,
|
||||
}),
|
||||
).toMatchInlineSnapshot(`
|
||||
[
|
||||
"invite-consumer -> server | LOAD Map sessions: empty",
|
||||
"server -> invite-consumer | CONTENT Group header: true new: After: 0 New: 5",
|
||||
"invite-consumer -> server | KNOWN Group sessions: header/5",
|
||||
"server -> invite-consumer | CONTENT Map header: true new: After: 0 New: 1",
|
||||
"invite-consumer -> server | KNOWN Map sessions: header/1",
|
||||
]
|
||||
`);
|
||||
});
|
||||
|
||||
test("invite a client to a group with parents", async () => {
|
||||
const client = setupTestNode();
|
||||
client.connectToSyncServer({
|
||||
ourName: "invite-provider",
|
||||
});
|
||||
const client2 = setupTestNode();
|
||||
client2.connectToSyncServer({
|
||||
ourName: "invite-consumer",
|
||||
});
|
||||
|
||||
const parentGroup = client.node.createGroup();
|
||||
const group = client.node.createGroup();
|
||||
|
||||
group.extend(parentGroup);
|
||||
const map = group.createMap();
|
||||
map.set("hello", "world");
|
||||
|
||||
await map.core.waitForSync();
|
||||
|
||||
const invite = group.createInvite("reader");
|
||||
|
||||
await group.core.waitForSync();
|
||||
SyncMessagesLog.clear();
|
||||
|
||||
await client2.node.acceptInvite(map.id, invite);
|
||||
|
||||
const mapOnClient2 = await loadCoValueOrFail(client2.node, map.id);
|
||||
expect(mapOnClient2.get("hello")).toEqual("world");
|
||||
|
||||
expect(
|
||||
SyncMessagesLog.getMessages({
|
||||
Group: group.core,
|
||||
ParentGroup: parentGroup.core,
|
||||
Map: map.core,
|
||||
}),
|
||||
).toMatchInlineSnapshot(`
|
||||
[
|
||||
"invite-consumer -> server | LOAD Map sessions: empty",
|
||||
"server -> invite-consumer | CONTENT ParentGroup header: true new: After: 0 New: 4",
|
||||
"invite-consumer -> server | KNOWN ParentGroup sessions: header/4",
|
||||
"server -> invite-consumer | CONTENT Group header: true new: After: 0 New: 7",
|
||||
"invite-consumer -> server | KNOWN Group sessions: header/7",
|
||||
"server -> invite-consumer | CONTENT Map header: true new: After: 0 New: 1",
|
||||
"invite-consumer -> server | KNOWN Map sessions: header/1",
|
||||
]
|
||||
`);
|
||||
});
|
||||
|
||||
test("invite a client to a parent group", async () => {
|
||||
const client = setupTestNode();
|
||||
client.connectToSyncServer({
|
||||
ourName: "invite-provider",
|
||||
});
|
||||
const client2 = setupTestNode();
|
||||
client2.connectToSyncServer({
|
||||
ourName: "invite-consumer",
|
||||
});
|
||||
|
||||
const parentGroup = client.node.createGroup();
|
||||
const group = client.node.createGroup();
|
||||
|
||||
group.extend(parentGroup);
|
||||
const map = group.createMap();
|
||||
map.set("hello", "world");
|
||||
|
||||
await map.core.waitForSync();
|
||||
|
||||
const invite = parentGroup.createInvite("reader");
|
||||
|
||||
await parentGroup.core.waitForSync();
|
||||
SyncMessagesLog.clear();
|
||||
|
||||
await client2.node.acceptInvite(parentGroup.id, invite);
|
||||
|
||||
const mapOnClient2 = await loadCoValueOrFail(client2.node, map.id);
|
||||
expect(mapOnClient2.get("hello")).toEqual("world");
|
||||
|
||||
expect(
|
||||
SyncMessagesLog.getMessages({
|
||||
Group: group.core,
|
||||
ParentGroup: parentGroup.core,
|
||||
Map: map.core,
|
||||
}),
|
||||
).toMatchInlineSnapshot(`
|
||||
[
|
||||
"invite-consumer -> server | LOAD ParentGroup sessions: empty",
|
||||
"server -> invite-consumer | CONTENT ParentGroup header: true new: After: 0 New: 6",
|
||||
"invite-consumer -> server | KNOWN ParentGroup sessions: header/6",
|
||||
"invite-consumer -> server | LOAD Map sessions: empty",
|
||||
"server -> invite-consumer | CONTENT Group header: true new: After: 0 New: 5",
|
||||
"invite-consumer -> server | KNOWN Group sessions: header/5",
|
||||
"server -> invite-consumer | CONTENT Map header: true new: After: 0 New: 1",
|
||||
"invite-consumer -> server | CONTENT ParentGroup header: false new: After: 0 New: 2",
|
||||
"server -> invite-consumer | KNOWN ParentGroup sessions: header/8",
|
||||
"server -> invite-provider | CONTENT ParentGroup header: false new: After: 0 New: 2",
|
||||
"invite-consumer -> server | KNOWN Map sessions: header/1",
|
||||
"invite-provider -> server | KNOWN ParentGroup sessions: header/8",
|
||||
]
|
||||
`);
|
||||
});
|
||||
});
|
||||
@@ -197,8 +197,6 @@ describe("loading coValues from server", () => {
|
||||
await map.core.waitForSync();
|
||||
await mapOnClient.core.waitForSync();
|
||||
|
||||
expect(mapOnClient.get("fromServer")).toEqual("updated");
|
||||
expect(mapOnClient.get("fromClient")).toEqual("updated");
|
||||
expect(
|
||||
SyncMessagesLog.getMessages({
|
||||
Group: group.core,
|
||||
@@ -217,6 +215,9 @@ describe("loading coValues from server", () => {
|
||||
"client -> server | KNOWN Map sessions: header/3",
|
||||
]
|
||||
`);
|
||||
|
||||
expect(mapOnClient.get("fromServer")).toEqual("updated");
|
||||
expect(mapOnClient.get("fromClient")).toEqual("updated");
|
||||
});
|
||||
|
||||
test("wrong optimistic known state should be corrected", async () => {
|
||||
|
||||
@@ -448,6 +448,31 @@ export function getSyncServerConnectedPeer(opts: {
|
||||
};
|
||||
}
|
||||
|
||||
export function createMockStoragePeer(opts: {
|
||||
ourName?: string;
|
||||
peerId: string;
|
||||
}) {
|
||||
const storage = createTestNode();
|
||||
|
||||
const { peer1, peer2 } = connectedPeersWithMessagesTracking({
|
||||
peer1: { id: storage.getCurrentAgent().id, role: "storage" },
|
||||
peer2: {
|
||||
id: opts.peerId,
|
||||
role: "client",
|
||||
name: opts.ourName,
|
||||
},
|
||||
});
|
||||
|
||||
peer1.priority = 100;
|
||||
|
||||
storage.syncManager.addPeer(peer2);
|
||||
|
||||
return {
|
||||
storage,
|
||||
peer: peer1,
|
||||
};
|
||||
}
|
||||
|
||||
export function setupTestNode(
|
||||
opts: {
|
||||
isSyncServer?: boolean;
|
||||
@@ -485,26 +510,14 @@ export function setupTestNode(
|
||||
}
|
||||
|
||||
function addStoragePeer(opts: { ourName?: string } = {}) {
|
||||
const storage = createTestNode();
|
||||
|
||||
const { peer1, peer2 } = connectedPeersWithMessagesTracking({
|
||||
peer1: { id: storage.getCurrentAgent().id, role: "storage" },
|
||||
peer2: {
|
||||
id: node.getCurrentAgent().id,
|
||||
role: "client",
|
||||
name: opts.ourName,
|
||||
},
|
||||
const { peer, storage } = createMockStoragePeer({
|
||||
peerId: node.getCurrentAgent().id,
|
||||
ourName: opts.ourName,
|
||||
});
|
||||
|
||||
peer1.priority = 100;
|
||||
node.syncManager.addPeer(peer);
|
||||
|
||||
node.syncManager.addPeer(peer1);
|
||||
storage.syncManager.addPeer(peer2);
|
||||
|
||||
return {
|
||||
storage,
|
||||
peer: node.syncManager.peers[peer1.id]!,
|
||||
};
|
||||
return { peer, peerState: node.syncManager.peers[peer.id]!, storage };
|
||||
}
|
||||
|
||||
if (opts.connected) {
|
||||
@@ -575,26 +588,14 @@ export async function setupTestAccount(
|
||||
}
|
||||
|
||||
function addStoragePeer(opts: { ourName?: string } = {}) {
|
||||
const storage = createTestNode();
|
||||
|
||||
const { peer1, peer2 } = connectedPeersWithMessagesTracking({
|
||||
peer1: { id: storage.getCurrentAgent().id, role: "storage" },
|
||||
peer2: {
|
||||
id: ctx.node.getCurrentAgent().id,
|
||||
role: "client",
|
||||
name: opts.ourName,
|
||||
},
|
||||
const { peer, storage } = createMockStoragePeer({
|
||||
peerId: ctx.node.getCurrentAgent().id,
|
||||
ourName: opts.ourName,
|
||||
});
|
||||
|
||||
peer1.priority = 100;
|
||||
ctx.node.syncManager.addPeer(peer);
|
||||
|
||||
ctx.node.syncManager.addPeer(peer1);
|
||||
storage.syncManager.addPeer(peer2);
|
||||
|
||||
return {
|
||||
storage,
|
||||
peer: ctx.node.syncManager.peers[peer1.id]!,
|
||||
};
|
||||
return { peer, peerState: ctx.node.syncManager.peers[peer.id]!, storage };
|
||||
}
|
||||
|
||||
if (opts.connected) {
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
export function parseError(e: unknown): {
|
||||
message: string | null;
|
||||
stack: string | null;
|
||||
} {
|
||||
return {
|
||||
message: e instanceof Error ? e.message : null,
|
||||
stack: e instanceof Error ? (e.stack ?? null) : null,
|
||||
};
|
||||
}
|
||||
@@ -1,5 +1,45 @@
|
||||
# jazz-auth-clerk
|
||||
|
||||
## 0.13.23
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [6b781cf]
|
||||
- Updated dependencies [02a240c]
|
||||
- cojson@0.13.23
|
||||
- jazz-tools@0.13.23
|
||||
- jazz-browser@0.13.23
|
||||
|
||||
## 0.13.21
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [e14e61f]
|
||||
- cojson@0.13.21
|
||||
- jazz-browser@0.13.21
|
||||
- jazz-tools@0.13.21
|
||||
|
||||
## 0.13.20
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [adfc9a6]
|
||||
- Updated dependencies [1389207]
|
||||
- Updated dependencies [d6e143e]
|
||||
- Updated dependencies [439f0fe]
|
||||
- Updated dependencies [3e6229d]
|
||||
- cojson@0.13.20
|
||||
- jazz-tools@0.13.20
|
||||
- jazz-browser@0.13.20
|
||||
|
||||
## 0.13.19
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [80530a4]
|
||||
- jazz-tools@0.13.19
|
||||
- jazz-browser@0.13.19
|
||||
|
||||
## 0.13.18
|
||||
|
||||
### Patch Changes
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user