Compare commits

..

20 Commits

Author SHA1 Message Date
Anselm Eickhoff
934fe4d29b Merge pull request #127 from KyleAMathews/main
fix(jazz-nodejs): also set peersToLoadFrom on newly created accounts
2023-11-07 17:18:02 +00:00
Anselm Eickhoff
408012f2e5 remove redundant peer add 2023-11-07 17:17:41 +00:00
Kyle Mathews
d0078b830e fix(jazz-nodejs): also set peersToLoadFrom on newly created accounts 2023-11-03 14:58:43 -07:00
Anselm Eickhoff
e52948b2b7 Merge pull request #125 from gardencmp/jazz-nodejs
jazz-nodejs MVP
2023-11-02 12:11:56 +00:00
Anselm
53bb1b230b Fix interpolation 2023-11-02 11:53:19 +00:00
Anselm
54e83aeaaa use NOMAD_ADDR secret 2023-11-02 11:43:32 +00:00
Anselm
aa3129cab5 Provide localNode to AccountMigrations 2023-10-27 14:48:36 +01:00
Anselm
90520dddd7 Make addMember and removeMember take loaded Accounts instead of just IDs 2023-10-27 14:30:55 +01:00
Anselm
03eb77070a Allow account migrations to be async 2023-10-27 11:18:41 +01:00
Anselm
4ba5c255b6 Fewer assumptions in jazz-nodejs 2023-10-27 11:00:19 +01:00
Anselm
01817db873 Add docs for jazz-nodejs 2023-10-25 14:31:27 +01:00
Anselm
46fcbd6c01 Implement first version of jazz-nodejs 2023-10-25 14:27:55 +01:00
Anselm
aa3e3de09e Update docs 2023-10-20 11:09:19 +01:00
Anselm
af3d48764d Reset allTweets 2023-10-20 10:50:29 +01:00
Anselm
091f36b736 Update all tweets 2023-10-20 10:38:56 +01:00
Anselm
7107f79f42 Update all tweets 2023-10-20 10:32:30 +01:00
Anselm
9922db2336 Cosmetic fixes 2023-10-20 10:23:40 +01:00
Anselm
75db570198 Use DemoAuth in Twit example 2023-10-20 10:18:14 +01:00
Anselm
28a09f377b Fix weird TypeScript error 2023-10-20 10:11:31 +01:00
Anselm
fd2e0855bb Deploy twit and chat 2023-10-20 09:56:09 +01:00
60 changed files with 3679 additions and 206 deletions

View File

@@ -12,7 +12,7 @@ jobs:
strategy:
matrix:
# example: ["chat", "todo", "pets", "twit", "file-drop"]
example: ["twit"]
example: ["twit", "chat"]
steps:
- uses: actions/checkout@v3
@@ -87,7 +87,7 @@ jobs:
strategy:
matrix:
# example: ["chat", "todo", "pets", "twit", "file-drop"]
example: ["twit"]
example: ["twit", "chat"]
steps:
- uses: actions/checkout@v3
@@ -115,7 +115,7 @@ jobs:
envsubst '${DOCKER_USER} ${DOCKER_PASSWORD} ${DOCKER_TAG} ${BRANCH_SUFFIX} ${BRANCH_SUBDOMAIN}' < job-template.nomad > job-instance.nomad;
cat job-instance.nomad;
NOMAD_ADDR='http://control1v2-london:4646' nomad job run job-instance.nomad;
NOMAD_ADDR=${{ secrets.NOMAD_ADDR }} nomad job run job-instance.nomad;
working-directory: ./examples/${{ matrix.example }}
# deploy-homepage:
@@ -148,5 +148,5 @@ jobs:
# envsubst '${DOCKER_USER} ${DOCKER_PASSWORD} ${DOCKER_TAG} ${BRANCH_SUFFIX} ${BRANCH_SUBDOMAIN}' < job-template.nomad > job-instance.nomad;
# cat job-instance.nomad;
# NOMAD_ADDR='http://control1v2-london:4646' nomad job run job-instance.nomad;
# NOMAD_ADDR=${{ secrets.NOMAD_ADDR }} nomad job run job-instance.nomad;
# working-directory: ./homepage/homepage-jazz

62
DOCS.md
View File

@@ -1,5 +1,11 @@
# Overview
---
## These are work-in-progress API docs. <br/> To start learning Jazz, see [Getting Started](./README.md#getting-started)
---
## Core packages
### `jazz-react` → [API](#jazz-react)
@@ -15999,4 +16005,58 @@ export type Resolved<T extends CoValue> = T extends CoMap
} ? never : ResolvedCoStream<T>
: ResolvedAccount | ResolvedGroup | ResolvedCoMap<CoMap> | ResolvedCoList<CoList> | ResolvedCoStream<CoStream>
```
TODO: doc generator not implemented yet 2097152
TODO: doc generator not implemented yet 2097152
# jazz-nodejs
## `createOrResumeWorker({workerName, syncServer?, migration?})`
<sup>(function in `jazz-nodejs`)</sup>
```typescript
export function createOrResumeWorker<P extends Profile<ProfileShape, ProfileMeta>, R extends CoMap<{
[key: string]: JsonValue | undefined }, null | JsonObject>>({
workerName: string,
syncServer?: string,
migration?: AccountMigration<P, R>,
}): Promise<{
localNode: LocalNode,
worker: ControlledAccount<P, R, AccountMeta>,
}>
```
TODO: document
### Parameters:
| name | description |
| ----: | ---- |
| `__namedParameters.workerName` | TODO: document |
| `__namedParameters.syncServer?` | TODO: document |
| `__namedParameters.migration?` | TODO: document |
----
## `autoSub(id, node, callback)`
<sup>(function in `jazz-nodejs`)</sup>
```typescript
export function autoSub<C extends CoValue>(id: undefined | CoID<C>, node: LocalNode, callback: (resolved: undefined | Resolved<C>) => void): () => void
```
TODO: document
### Parameters:
| name | description |
| ----: | ---- |
| `id` | TODO: document |
| `node` | TODO: document |

View File

@@ -24,10 +24,15 @@ export type TodoProject = CoMap<{
export type ListOfProjects = CoList<TodoProject["id"]>;
/** The account root is an app-specific per-user private `CoMap`
* where you can store top-level objects for that user */
export type TodoAccountRoot = CoMap<{
projects: ListOfProjects["id"];
}>;
/** The account migration is run on account creation and on every log-in.
* You can use it to set up the account root and any other initial CoValues you need.
*/
export const migration: AccountMigration<Profile, TodoAccountRoot> = (account) => {
if (!account.get("root")) {
account.set(

View File

@@ -25,9 +25,11 @@ import { AccountMigration, Profile } from "cojson";
* Walkthrough: The top-level provider `<WithJazz/>`
*
* This shows how to use the top-level provider `<WithJazz/>`,
* which provides the rest of the app with a `LocalNode` (used through `useJazz` later),
* based on `LocalAuth` that uses Passkeys (aka WebAuthn) to store a user's account secret
* which provides the rest of the app with a controlled account (used through `useJazz` later).
* Here we use `LocalAuth`, which uses Passkeys (aka WebAuthn) to store a user's account secret
* - no backend needed.
*
* `<WithJazz/>` also runs our account migration
*/
const appName = "Jazz Todo List Example";

View File

@@ -22,3 +22,6 @@ dist-ssr
*.njsproj
*.sln
*.sw?
.env
TwitAllTwitsCreatorCredentials.json

View File

@@ -1,5 +1,24 @@
# twit-stresstest
## 0.2.0
### Minor Changes
- Make addMember and removeMember take loaded Accounts instead of just IDs
### Patch Changes
- Updated dependencies
- jazz-nodejs@0.6.0
## 0.1.1
### Patch Changes
- Allow account migrations to be async
- Updated dependencies
- jazz-nodejs@0.5.3
## 0.1.0
### Minor Changes

View File

@@ -9,31 +9,12 @@ import {
TwitProfile,
migration,
} from "../twit/src/1_dataModel.js";
import {
websocketReadableStream,
websocketWritableStream,
} from "cojson-transport-nodejs-ws";
import { WebSocket } from "ws";
import { autoSub } from "jazz-autosub";
import { webcrypto } from 'node:crypto'
(globalThis as any).crypto = webcrypto
import { createOrResumeWorker, autoSub } from "jazz-nodejs";
async function runner() {
await cojsonReady;
const { node } = await LocalNode.withNewlyCreatedAccount({
name: "Bot_" + Math.random().toString(36).slice(2),
migration,
});
const ws = new WebSocket("wss://sync.jazz.tools");
node.syncManager.addPeer({
id: "globalMesh",
role: "server",
incoming: websocketReadableStream(ws),
outgoing: websocketWritableStream(ws),
const { localNode: node, worker } = await createOrResumeWorker({
workerName: "TwitStressTestBot" + Math.random().toString(36).slice(2),
});
console.log(
@@ -64,8 +45,8 @@ async function runner() {
if (startedPosting) return;
startedPosting = true;
for (let i = 0; i < 10; i++) {
await new Promise((resolve) =>
setTimeout(resolve, Math.random() * 120000)
await new Promise(
(resolve) => setTimeout(resolve, Math.random() * 120000)
// setTimeout(resolve, Math.random() * 5000)
);
const audience = me.root.peopleWhoCanSeeMyContent;
@@ -89,7 +70,7 @@ async function runner() {
let blackHole = 0;
let lastUpdate = Date.now()
let lastUpdate = Date.now();
autoSub(ALL_TWEETS_LIST_ID, node, (allTwits) => {
if (Date.now() - lastUpdate < 33) return;

View File

@@ -3,30 +3,16 @@ import {
ListOfTwits,
migration,
} from "../twit/src/1_dataModel";
import {
websocketReadableStream,
websocketWritableStream,
} from "cojson-transport-nodejs-ws";
import { WebSocket } from "ws";
import { createOrResumeWorker, autoSub } from "jazz-nodejs"
await cojsonReady;
const { node } = await LocalNode.withNewlyCreatedAccount({
name: "Bot_" + Math.random().toString(36).slice(2),
migration,
const { localNode: node, worker } = await createOrResumeWorker({
workerName: "TwitAllTwitsCreator",
migration
});
const ws = new WebSocket("wss://sync.jazz.tools");
const allTweetsGroup = (node.account as ControlledAccount).createGroup();
const allTweetsGroup = worker.createGroup();
allTweetsGroup.addMember('everyone', 'writer');
const allTweets = allTweetsGroup.createList<ListOfTwits>();
console.log("allTweets", allTweets.id);
node.syncManager.addPeer({
id: "globalMesh",
role: "server",
incoming: websocketReadableStream(ws),
outgoing: websocketWritableStream(ws),
});
console.log("allTweets", allTweets.id);

View File

@@ -1,13 +1,12 @@
{
"name": "twit-stresstest",
"version": "0.1.0",
"version": "0.2.0",
"main": "dist/twit-stresstest/index.js",
"type":"module",
"type": "module",
"license": "MIT",
"private": true,
"dependencies": {
"cojson": "^0.5.0",
"cojson-transport-nodejs-ws": "^0.5.0"
"jazz-nodejs": "^0.6.0"
},
"scripts": {
"build": "rm -rf ./dist && tsc --sourceMap --outDir dist",

View File

@@ -33,7 +33,7 @@ export type TwitAccountRoot = CoMap<{
peopleWhoCanInteractWithMe: Group['id'];
}>;
export const ALL_TWEETS_LIST_ID = "co_zWjKkgPhrrrywAtEXSxiEPevmEW" as ListOfTwits['id'];
export const ALL_TWEETS_LIST_ID = "co_zQEhxDTvZt3f4vWKqVNj9TCTRs4" as ListOfTwits['id'];
export const migration: AccountMigration<TwitProfile, TwitAccountRoot> = (account, profile) => {
if (!account.get('root')) {

View File

@@ -3,11 +3,9 @@ import { RouterProvider, createHashRouter } from 'react-router-dom';
import './index.css';
import { AccountMigration } from 'cojson';
import { WithJazz, useJazz } from 'jazz-react';
import { LocalAuth } from 'jazz-react-auth-local';
import { DemoAuth, WithJazz, useJazz } from 'jazz-react';
import { Button, ThemeProvider, TitleAndLogo } from './basicComponents/index.tsx';
import { PrettyAuthUI } from './components/Auth.tsx';
import { migration } from './1_dataModel.ts';
import { AllTwitsFeed, FollowingFeed } from './3_ChronoFeed.tsx';
@@ -15,21 +13,20 @@ import { ProfilePage } from './5_ProfilePage.tsx';
const appName = 'Jazz Twit Example';
const auth = LocalAuth({
appName,
Component: PrettyAuthUI
const auth = DemoAuth({
appName
});
ReactDOM.createRoot(document.getElementById('root')!).render(
// <React.StrictMode>
<WithJazz auth={auth} migration={migration as AccountMigration}>
<ThemeProvider>
<TitleAndLogo name={appName} />
<div className="flex flex-col h-full items-stretch justify-start gap-10 pt-10 pb-10 px-5 w-full max-w-xl mx-auto">
<WithJazz auth={auth} migration={migration as AccountMigration}>
<App />
</WithJazz>
<App />
</div>
</ThemeProvider>
</WithJazz>
// </React.StrictMode>
);

View File

@@ -39,7 +39,7 @@ export function ProfilePage() {
? null
: twit?.isReplyTo
: twit
);
) || [];
}, [profile?.twits]);
const [qr, setQr] = useState<string>('');

View File

@@ -226,7 +226,7 @@ export function QuoteContainer(props: { children: React.ReactNode }) {
}
export function MainH1(props: { children: React.ReactNode }) {
return <h1 className="text-2xl mb-4 sticky top-0 p-4 -mx-4 bg-white dark:bg-black z-20">{props.children}</h1>;
return <h1 className="text-2xl mb-4 sticky top-0 p-4 -mx-4 bg-background z-20">{props.children}</h1>;
}
export function SmallInlineButton(props: { children: React.ReactNode } & ButtonProps) {

View File

@@ -9,6 +9,7 @@ export async function genDocsMd() {
"jazz-browser": "index.ts",
"jazz-browser-media-images": "index.ts",
"jazz-autosub": "index.ts",
"jazz-nodejs": "index.ts",
}).map(async ([packageName, entryPoint]) => {
const app = await Application.bootstrapWithPlugins({
entryPoints: [`packages/${packageName}/src/${entryPoint}`],

View File

@@ -1,5 +1,17 @@
# cojson-simple-sync
## 0.6.0
### Minor Changes
- Make addMember and removeMember take loaded Accounts instead of just IDs
### Patch Changes
- Updated dependencies
- cojson@0.6.0
- cojson-storage-sqlite@0.5.2
## 0.5.0
### Minor Changes

View File

@@ -4,7 +4,7 @@
"types": "src/index.ts",
"type": "module",
"license": "MIT",
"version": "0.5.0",
"version": "0.6.0",
"devDependencies": {
"@types/jest": "^29.5.3",
"@types/ws": "^8.5.5",
@@ -16,8 +16,8 @@
"typescript": "5.0.2"
},
"dependencies": {
"cojson": "^0.5.0",
"cojson-storage-sqlite": "^0.5.0",
"cojson": "^0.6.0",
"cojson-storage-sqlite": "^0.5.2",
"ws": "^8.13.0"
},
"scripts": {

View File

@@ -1,4 +1,4 @@
import { AnonymousControlledAccount, LocalNode, cojsonInternals, cojsonReady } from "cojson";
import { ControlledAgent, LocalNode, cojsonInternals, cojsonReady } from "cojson";
import { WebSocketServer } from "ws";
import { SQLiteStorage } from "cojson-storage-sqlite";
import { websocketReadableStream, websocketWritableStream } from "./websocketStreams.js";
@@ -15,7 +15,7 @@ const agentSecret = cojsonInternals.newRandomAgentSecret();
const agentID = cojsonInternals.getAgentID(agentSecret);
const localNode = new LocalNode(
new AnonymousControlledAccount(agentSecret),
new ControlledAgent(agentSecret),
cojsonInternals.newRandomSessionID(agentID)
);

View File

@@ -1,5 +1,16 @@
# cojson-storage-indexeddb
## 0.6.0
### Minor Changes
- Make addMember and removeMember take loaded Accounts instead of just IDs
### Patch Changes
- Updated dependencies
- cojson@0.6.0
## 0.5.0
### Minor Changes

View File

@@ -1,11 +1,11 @@
{
"name": "cojson-storage-indexeddb",
"version": "0.5.0",
"version": "0.6.0",
"main": "dist/index.js",
"types": "src/index.ts",
"license": "MIT",
"dependencies": {
"cojson": "^0.5.0",
"cojson": "^0.6.0",
"typescript": "^5.1.6"
},
"devDependencies": {

View File

@@ -1,12 +1,12 @@
import { expect, test } from "vitest";
import { AnonymousControlledAccount, LocalNode, cojsonInternals } from "cojson";
import { ControlledAgent, LocalNode, cojsonInternals } from "cojson";
import { IDBStorage } from ".";
test.skip("Should be able to initialize and load from empty DB", async () => {
const agentSecret = cojsonInternals.newRandomAgentSecret();
const node = new LocalNode(
new AnonymousControlledAccount(agentSecret),
new ControlledAgent(agentSecret),
cojsonInternals.newRandomSessionID(
cojsonInternals.getAgentID(agentSecret)
)
@@ -27,7 +27,7 @@ test("Should be able to sync data to database and then load that from a new node
const agentSecret = cojsonInternals.newRandomAgentSecret();
const node1 = new LocalNode(
new AnonymousControlledAccount(agentSecret),
new ControlledAgent(agentSecret),
cojsonInternals.newRandomSessionID(
cojsonInternals.getAgentID(agentSecret)
)
@@ -50,7 +50,7 @@ test("Should be able to sync data to database and then load that from a new node
await new Promise((resolve) => setTimeout(resolve, 200));
const node2 = new LocalNode(
new AnonymousControlledAccount(agentSecret),
new ControlledAgent(agentSecret),
cojsonInternals.newRandomSessionID(
cojsonInternals.getAgentID(agentSecret)
)

View File

@@ -1,5 +1,12 @@
# cojson-storage-sqlite
## 0.5.2
### Patch Changes
- Updated dependencies
- cojson@0.6.0
## 0.5.1
### Patch Changes

View File

@@ -1,13 +1,13 @@
{
"name": "cojson-storage-sqlite",
"type": "module",
"version": "0.5.1",
"version": "0.5.2",
"main": "dist/index.js",
"types": "src/index.ts",
"license": "MIT",
"dependencies": {
"better-sqlite3": "^8.5.2",
"cojson": "^0.5.0",
"cojson": "^0.6.0",
"typescript": "^5.1.6",
"@types/better-sqlite3": "^7.6.4"
},

View File

@@ -1,5 +1,12 @@
# cojson-transport-nodejs-ws
## 0.5.1
### Patch Changes
- Updated dependencies
- cojson@0.6.0
## 0.5.0
### Minor Changes

View File

@@ -1,12 +1,12 @@
{
"name": "cojson-transport-nodejs-ws",
"type": "module",
"version": "0.5.0",
"version": "0.5.1",
"main": "dist/index.js",
"types": "src/index.ts",
"license": "MIT",
"dependencies": {
"cojson": "^0.5.0",
"cojson": "^0.6.0",
"typescript": "^5.1.6",
"ws": "^8.14.2"
},

View File

@@ -1,5 +1,23 @@
# cojson
## 0.6.1
### Patch Changes
- Provide localNode to AccountMigrations
## 0.6.0
### Minor Changes
- Make addMember and removeMember take loaded Accounts instead of just IDs
## 0.5.2
### Patch Changes
- Allow account migrations to be async
## 0.5.1
### Patch Changes

View File

@@ -5,7 +5,7 @@
"types": "src/index.ts",
"type": "module",
"license": "MIT",
"version": "0.5.1",
"version": "0.6.1",
"devDependencies": {
"@noble/curves": "^1.2.0",
"@types/jest": "^29.5.3",

View File

@@ -27,7 +27,7 @@ import { Group } from "./coValues/group.js";
import { LocalNode } from "./localNode.js";
import { CoValueKnownState, NewContentMessage } from "./sync.js";
import { AgentID, RawCoID, SessionID, TransactionID } from "./ids.js";
import { AccountID, GeneralizedControlledAccount } from "./coValues/account.js";
import { AccountID, ControlledAccountOrAgent } from "./coValues/account.js";
import { Stringified, parseJSON, stableStringify } from "./jsonStringify.js";
import { coreToCoValue } from "./coreToCoValue.js";
import { expectGroup } from "./typeUtils/expectGroup.js";
@@ -133,7 +133,7 @@ export class CoValueCore {
}
testWithDifferentAccount(
account: GeneralizedControlledAccount,
account: ControlledAccountOrAgent,
currentSessionID: SessionID
): CoValueCore {
const newNode = this.node.testWithDifferentAccount(

View File

@@ -15,6 +15,7 @@ import {
import { AgentID } from "../ids.js";
import { CoMap } from "./coMap.js";
import { Group, InviteSecret } from "./group.js";
import { LocalNode } from "../index.js";
export function accountHeaderForInitialAgentSecret(
agentSecret: AgentSecret
@@ -36,7 +37,7 @@ export class Account<
R extends CoMap = CoMap,
Meta extends AccountMeta = AccountMeta
> extends Group<P, R, Meta> {
getCurrentAgentID(): AgentID {
currentAgentID(): AgentID {
const agents = this.keys().filter((k): k is AgentID =>
k.startsWith("sealer_")
);
@@ -51,7 +52,7 @@ export class Account<
}
}
export interface GeneralizedControlledAccount {
export interface ControlledAccountOrAgent {
id: AccountID | AgentID;
agentSecret: AgentSecret;
@@ -64,12 +65,12 @@ export interface GeneralizedControlledAccount {
/** @hidden */
export class ControlledAccount<
P extends Profile = Profile,
R extends CoMap = CoMap,
Meta extends AccountMeta = AccountMeta
>
P extends Profile = Profile,
R extends CoMap = CoMap,
Meta extends AccountMeta = AccountMeta
>
extends Account<P, R, Meta>
implements GeneralizedControlledAccount
implements ControlledAccountOrAgent
{
agentSecret: AgentSecret;
@@ -116,8 +117,8 @@ Meta extends AccountMeta = AccountMeta
}
/** @hidden */
export class AnonymousControlledAccount
implements GeneralizedControlledAccount
export class ControlledAgent
implements ControlledAccountOrAgent
{
agentSecret: AgentSecret;
@@ -158,10 +159,17 @@ export type ProfileShape = {
};
export type ProfileMeta = { type: "profile" };
export class Profile<Shape extends ProfileShape = ProfileShape, Meta extends ProfileMeta = ProfileMeta> extends CoMap<Shape, Meta> {
export class Profile<
Shape extends ProfileShape = ProfileShape,
Meta extends ProfileMeta = ProfileMeta
> extends CoMap<Shape, Meta> {}
}
export type AccountMigration< P extends Profile = Profile,
R extends CoMap = CoMap,
Meta extends AccountMeta = AccountMeta> = (account: ControlledAccount<P, R, Meta>, profile: P) => void;
export type AccountMigration<
P extends Profile = Profile,
R extends CoMap = CoMap,
Meta extends AccountMeta = AccountMeta
> = (
account: ControlledAccount<P, R, Meta>,
profile: P,
localNode: LocalNode
) => void | Promise<void>;

View File

@@ -18,7 +18,7 @@ import {
getAgentID,
} from "../crypto.js";
import { AgentID, isAgentID } from "../ids.js";
import { AccountID, Profile } from "./account.js";
import { Account, AccountID, ControlledAccountOrAgent, Profile } from "./account.js";
import { Role } from "../permissions.js";
import { base58 } from "@scure/base";
@@ -94,13 +94,13 @@ export class Group<
*
* @category 2. Role changing
*/
addMember(accountID: AccountID | Everyone, role: Role): this {
return this.addMemberInternal(accountID, role);
addMember(account: Account | ControlledAccountOrAgent | Everyone, role: Role): this {
return this.addMemberInternal(account, role);
}
/** @internal */
addMemberInternal(
accountID: AccountID | AgentID | Everyone,
account: Account | ControlledAccountOrAgent | AgentID | Everyone,
role: Role
): this {
return this.mutate((mutable) => {
@@ -110,15 +110,15 @@ export class Group<
throw new Error("Can't add member without read key secret");
}
if (accountID === EVERYONE) {
if (account === EVERYONE) {
if (!(role === "reader" || role === "writer")) {
throw new Error(
"Can't make everyone something other than reader or writer"
);
}
mutable.set(accountID, role, "trusting");
mutable.set(account, role, "trusting");
if (mutable.get(accountID) !== role) {
if (mutable.get(account) !== role) {
throw new Error("Failed to set role");
}
@@ -128,18 +128,16 @@ export class Group<
"trusting"
);
} else {
const agent = this.core.node.resolveAccountAgent(
accountID,
"Expected to know agent to add them to group"
);
mutable.set(accountID, role, "trusting");
const memberKey = typeof account === "string" ? account : account.id;
const agent = typeof account === "string" ? account : account.currentAgentID();
mutable.set(memberKey, role, "trusting");
if (mutable.get(accountID) !== role) {
if (mutable.get(memberKey) !== role) {
throw new Error("Failed to set role");
}
mutable.set(
`${currentReadKey.id}_for_${accountID}`,
`${currentReadKey.id}_for_${memberKey}`,
seal({
message: currentReadKey.secret,
from: this.core.node.account.currentSealerSecret(),
@@ -225,15 +223,14 @@ export class Group<
*
* @category 2. Role changing
*/
removeMember(accountID: AccountID): this {
return this.removeMemberInternal(accountID);
removeMember(account: Account | ControlledAccountOrAgent | Everyone): this {
return this.removeMemberInternal(account);
}
/** @internal */
removeMemberInternal(accountID: AccountID | AgentID): this {
const afterRevoke = this.mutate((map) => {
map.set(accountID, "revoked", "trusting");
});
removeMemberInternal(account: Account | ControlledAccountOrAgent | AgentID | Everyone): this {
const memberKey = typeof account === "string" ? account : account.id;
const afterRevoke = this.set(memberKey, "revoked", "trusting");
return afterRevoke.rotateReadKey();
}

View File

@@ -27,7 +27,7 @@ import {
} from "./crypto.js";
import { connectedPeers } from "./streamUtils.js";
import {
AnonymousControlledAccount,
ControlledAgent,
ControlledAccount,
} from "./coValues/account.js";
import type { Role } from "./permissions.js";
@@ -108,7 +108,7 @@ export {
SessionID,
Media,
CoValueCore,
AnonymousControlledAccount,
ControlledAgent,
ControlledAccount,
cryptoReady as cojsonReady,
MAX_RECOMMENDED_TX_SIZE,

View File

@@ -26,9 +26,9 @@ import {
Account,
AccountMeta,
accountHeaderForInitialAgentSecret,
GeneralizedControlledAccount,
ControlledAccountOrAgent,
ControlledAccount,
AnonymousControlledAccount,
ControlledAgent,
AccountID,
Profile,
AccountMigration,
@@ -52,7 +52,7 @@ export class LocalNode {
/** @internal */
coValues: { [key: RawCoID]: CoValueState } = {};
/** @category 3. Low-level */
account: GeneralizedControlledAccount;
account: ControlledAccountOrAgent;
/** @category 3. Low-level */
currentSessionID: SessionID;
/** @category 3. Low-level */
@@ -60,7 +60,7 @@ export class LocalNode {
/** @category 3. Low-level */
constructor(
account: GeneralizedControlledAccount,
account: ControlledAccountOrAgent,
currentSessionID: SessionID
) {
this.account = account;
@@ -68,27 +68,29 @@ export class LocalNode {
}
/** @category 2. Node Creation */
static withNewlyCreatedAccount<
static async withNewlyCreatedAccount<
P extends Profile = Profile,
R extends CoMap = CoMap,
Meta extends AccountMeta = AccountMeta
>({
name,
peersToLoadFrom,
migration,
initialAgentSecret = newRandomAgentSecret(),
}: {
name: string;
peersToLoadFrom?: Peer[];
migration?: AccountMigration<P, R, Meta>;
initialAgentSecret?: AgentSecret;
}): {
}): Promise<{
node: LocalNode;
accountID: AccountID;
accountSecret: AgentSecret;
sessionID: SessionID;
} {
}> {
const throwawayAgent = newRandomAgentSecret();
const setupNode = new LocalNode(
new AnonymousControlledAccount(throwawayAgent),
new ControlledAgent(throwawayAgent),
newRandomSessionID(getAgentID(throwawayAgent))
);
@@ -107,8 +109,14 @@ export class LocalNode {
"After creating account"
);
if (peersToLoadFrom) {
for (const peer of peersToLoadFrom) {
nodeWithAccount.syncManager.addPeer(peer);
}
}
if (migration) {
migration(accountOnNodeWithAccount, profile as P);
await migration(accountOnNodeWithAccount, profile as P, nodeWithAccount);
}
nodeWithAccount.account = new ControlledAccount(
@@ -156,7 +164,7 @@ export class LocalNode {
migration?: AccountMigration<P, R, Meta>;
}): Promise<LocalNode> {
const loadingNode = new LocalNode(
new AnonymousControlledAccount(accountSecret),
new ControlledAgent(accountSecret),
newRandomSessionID(accountID)
);
@@ -194,9 +202,10 @@ export class LocalNode {
const profile = await node.load(profileID);
if (migration) {
migration(
await migration(
controlledAccount as ControlledAccount<P, R, Meta>,
profile as P
profile as P,
node
);
node.account = new ControlledAccount(
controlledAccount.core,
@@ -370,14 +379,14 @@ export class LocalNode {
const groupAsInvite = expectGroup(
group.core
.testWithDifferentAccount(
new AnonymousControlledAccount(inviteAgentSecret),
new ControlledAgent(inviteAgentSecret),
newRandomSessionID(inviteAgentID)
)
.getCurrentContent()
);
groupAsInvite.addMemberInternal(
this.account.id,
this.account,
inviteRole === "adminInvite"
? "admin"
: inviteRole === "writerInvite"
@@ -439,7 +448,7 @@ export class LocalNode {
let account = expectGroup(
this.createCoValue(accountHeaderForInitialAgentSecret(agentSecret))
.testWithDifferentAccount(
new AnonymousControlledAccount(agentSecret),
new ControlledAgent(agentSecret),
newRandomSessionID(accountAgentID)
)
.getCurrentContent()
@@ -518,7 +527,7 @@ export class LocalNode {
);
}
return new Account(coValue).getCurrentAgentID();
return new Account(coValue).currentAgentID();
}
async resolveAccountAgentAsync(
@@ -553,7 +562,7 @@ export class LocalNode {
);
}
return new Account(coValue).getCurrentAgentID();
return new Account(coValue).currentAgentID();
}
/**
@@ -596,7 +605,7 @@ export class LocalNode {
/** @internal */
testWithDifferentAccount(
account: GeneralizedControlledAccount,
account: ControlledAccountOrAgent,
currentSessionID: SessionID
): LocalNode {
const newNode = new LocalNode(account, currentSessionID);

View File

@@ -249,7 +249,7 @@ export function determineValidTransactions(
const effectiveTransactor =
transactor === groupContent.id &&
groupAtTime instanceof Account
? groupAtTime.getCurrentAgentID()
? groupAtTime.currentAgentID()
: transactor;
const transactorRoleAtTxTime =
groupAtTime.get(effectiveTransactor) ||

View File

@@ -9,7 +9,7 @@ beforeEach(async () => {
test("Can create a node while creating a new account with profile", async () => {
const { node, accountID, accountSecret, sessionID } =
LocalNode.withNewlyCreatedAccount({ name: "Hermes Puggington" });
await LocalNode.withNewlyCreatedAccount({ name: "Hermes Puggington" });
expect(node).not.toBeNull();
expect(accountID).not.toBeNull();
@@ -22,7 +22,7 @@ test("Can create a node while creating a new account with profile", async () =>
});
test("A node with an account can create groups and and objects within them", async () => {
const { node, accountID } = LocalNode.withNewlyCreatedAccount({
const { node, accountID } = await LocalNode.withNewlyCreatedAccount({
name: "Hermes Puggington",
});
@@ -42,7 +42,7 @@ test("A node with an account can create groups and and objects within them", asy
test("Can create account with one node, and then load it on another", async () => {
const { node, accountID, accountSecret } =
LocalNode.withNewlyCreatedAccount({ name: "Hermes Puggington" });
await LocalNode.withNewlyCreatedAccount({ name: "Hermes Puggington" });
const group = await node.createGroup();
expect(group).not.toBeNull();
@@ -69,6 +69,7 @@ test("Can create account with one node, and then load it on another", async () =
});
const map2 = await node2.load(map.id);
if (map2 === "unavailable") throw new Error("Map unavailable");
expect(map2.get("foo")).toEqual("bar");
});

View File

@@ -17,7 +17,7 @@ import {
groupWithTwoAdmins,
groupWithTwoAdminsHighLevel,
} from "./testUtils.js";
import { AnonymousControlledAccount, cojsonReady } from "../index.js";
import { ControlledAgent, cojsonReady } from "../index.js";
import { expectGroup } from "../typeUtils/expectGroup.js";
beforeEach(async () => {
@@ -70,7 +70,7 @@ test("Added adming can add a third admin to a group (high level)", () => {
const thirdAdmin = groupAsOtherAdmin.core.node.createAccount("thirdAdmin");
groupAsOtherAdmin = groupAsOtherAdmin.addMember(thirdAdmin.id, "admin");
groupAsOtherAdmin = groupAsOtherAdmin.addMember(thirdAdmin, "admin");
expect(groupAsOtherAdmin.get(thirdAdmin.id)).toEqual("admin");
});
@@ -166,7 +166,7 @@ test("Admins an add writers to a group, who can't add admins, writers, or reader
const writer = node.createAccount("writer");
group = group.addMember(writer.id, "writer");
group = group.addMember(writer, "writer");
expect(group.get(writer.id)).toEqual("writer");
const groupAsWriter = expectGroup(
@@ -179,13 +179,13 @@ test("Admins an add writers to a group, who can't add admins, writers, or reader
const otherAgent = groupAsWriter.core.node.createAccount("otherAgent");
expect(() => groupAsWriter.addMember(otherAgent.id, "admin")).toThrow(
expect(() => groupAsWriter.addMember(otherAgent, "admin")).toThrow(
"Failed to set role"
);
expect(() => groupAsWriter.addMember(otherAgent.id, "writer")).toThrow(
expect(() => groupAsWriter.addMember(otherAgent, "writer")).toThrow(
"Failed to set role"
);
expect(() => groupAsWriter.addMember(otherAgent.id, "reader")).toThrow(
expect(() => groupAsWriter.addMember(otherAgent, "reader")).toThrow(
"Failed to set role"
);
@@ -235,7 +235,7 @@ test("Admins can add readers to a group, who can't add admins, writers, or reade
const reader = node.createAccount("reader");
group = group.addMember(reader.id, "reader");
group = group.addMember(reader, "reader");
expect(group.get(reader.id)).toEqual("reader");
const groupAsReader = expectGroup(
@@ -248,13 +248,13 @@ test("Admins can add readers to a group, who can't add admins, writers, or reade
const otherAgent = groupAsReader.core.node.createAccount("otherAgent");
expect(() => groupAsReader.addMember(otherAgent.id, "admin")).toThrow(
expect(() => groupAsReader.addMember(otherAgent, "admin")).toThrow(
"Failed to set role"
);
expect(() => groupAsReader.addMember(otherAgent.id, "writer")).toThrow(
expect(() => groupAsReader.addMember(otherAgent, "writer")).toThrow(
"Failed to set role"
);
expect(() => groupAsReader.addMember(otherAgent.id, "reader")).toThrow(
expect(() => groupAsReader.addMember(otherAgent, "reader")).toThrow(
"Failed to set role"
);
@@ -337,7 +337,7 @@ test("Writers can write to an object that is owned by their group (high level)",
const writer = node.createAccount("writer");
group.addMember(writer.id, "writer");
group.addMember(writer, "writer");
const childObject = group.createMap();
@@ -396,7 +396,7 @@ test("Readers can not write to an object that is owned by their group (high leve
const reader = node.createAccount("reader");
group.addMember(reader.id, "reader");
group.addMember(reader, "reader");
const childObject = group.createMap();
@@ -548,7 +548,7 @@ test("Admins can set group read key and then writers can use it to create and re
const writer = node.createAccount("writer");
group.addMember(writer.id, "writer");
group.addMember(writer, "writer");
const childObject = group.createMap();
@@ -637,7 +637,7 @@ test("Admins can set group read key and then use it to create private transactio
const reader = node.createAccount("reader");
group.addMember(reader.id, "reader");
group.addMember(reader, "reader");
let childObject = group.createMap();
@@ -757,7 +757,7 @@ test("Admins can set group read key and then use it to create private transactio
const reader2 = node.createAccount("reader2");
group.addMember(reader1.id, "reader");
group.addMember(reader1, "reader");
let childObject = group.createMap();
@@ -774,7 +774,7 @@ test("Admins can set group read key and then use it to create private transactio
expect(childContentAsReader1.get("foo")).toEqual("bar");
group.addMember(reader2.id, "reader");
group.addMember(reader2, "reader");
const childContentAsReader2 = expectMap(
childObject.core
@@ -1013,7 +1013,7 @@ test("Admins can set group read key, make a private transaction in an owned obje
const reader = node.createAccount("reader");
group.addMember(reader.id, "reader");
group.addMember(reader, "reader");
childObject = childObject.edit((editable) => {
editable.set("foo2", "bar2", "private");
@@ -1210,8 +1210,8 @@ test("Admins can set group read rey, make a private transaction in an owned obje
const reader2 = node.createAccount("reader2");
group.addMember(reader.id, "reader");
group.addMember(reader2.id, "reader");
group.addMember(reader, "reader");
group.addMember(reader2, "reader");
childObject = childObject.edit((editable) => {
editable.set("foo2", "bar2", "private");
@@ -1314,7 +1314,7 @@ test("Admins can create an adminInvite, which can add an admin", () => {
const groupAsInvite = expectGroup(
groupCore
.testWithDifferentAccount(
new AnonymousControlledAccount(inviteSecret),
new ControlledAgent(inviteSecret),
newRandomSessionID(inviteID)
)
.getCurrentContent()
@@ -1363,7 +1363,7 @@ test("Admins can create an adminInvite, which can add an admin (high-level)", as
const invitedAdminID = getAgentID(invitedAdminSecret);
const nodeAsInvitedAdmin = node.testWithDifferentAccount(
new AnonymousControlledAccount(invitedAdminSecret),
new ControlledAgent(invitedAdminSecret),
newRandomSessionID(invitedAdminID)
);
@@ -1433,7 +1433,7 @@ test("Admins can create a writerInvite, which can add a writer", () => {
const groupAsInvite = expectGroup(
groupCore
.testWithDifferentAccount(
new AnonymousControlledAccount(inviteSecret),
new ControlledAgent(inviteSecret),
newRandomSessionID(inviteID)
)
.getCurrentContent()
@@ -1482,7 +1482,7 @@ test("Admins can create a writerInvite, which can add a writer (high-level)", as
const invitedWriterID = getAgentID(invitedWriterSecret);
const nodeAsInvitedWriter = node.testWithDifferentAccount(
new AnonymousControlledAccount(invitedWriterSecret),
new ControlledAgent(invitedWriterSecret),
newRandomSessionID(invitedWriterID)
);
@@ -1542,7 +1542,7 @@ test("Admins can create a readerInvite, which can add a reader", () => {
const groupAsInvite = expectGroup(
groupCore
.testWithDifferentAccount(
new AnonymousControlledAccount(inviteSecret),
new ControlledAgent(inviteSecret),
newRandomSessionID(inviteID)
)
.getCurrentContent()
@@ -1591,7 +1591,7 @@ test("Admins can create a readerInvite, which can add a reader (high-level)", as
const invitedReaderID = getAgentID(invitedReaderSecret);
const nodeAsInvitedReader = node.testWithDifferentAccount(
new AnonymousControlledAccount(invitedReaderSecret),
new ControlledAgent(invitedReaderSecret),
newRandomSessionID(invitedReaderID)
);
@@ -1651,7 +1651,7 @@ test("WriterInvites can not invite admins", () => {
const groupAsInvite = expectGroup(
groupCore
.testWithDifferentAccount(
new AnonymousControlledAccount(inviteSecret),
new ControlledAgent(inviteSecret),
newRandomSessionID(inviteID)
)
.getCurrentContent()
@@ -1711,7 +1711,7 @@ test("ReaderInvites can not invite admins", () => {
const groupAsInvite = expectGroup(
groupCore
.testWithDifferentAccount(
new AnonymousControlledAccount(inviteSecret),
new ControlledAgent(inviteSecret),
newRandomSessionID(inviteID)
)
.getCurrentContent()
@@ -1771,7 +1771,7 @@ test("ReaderInvites can not invite writers", () => {
const groupAsInvite = expectGroup(
groupCore
.testWithDifferentAccount(
new AnonymousControlledAccount(inviteSecret),
new ControlledAgent(inviteSecret),
newRandomSessionID(inviteID)
)
.getCurrentContent()
@@ -1812,7 +1812,7 @@ test("Can give read permission to 'everyone'", () => {
expect(editable.get("foo")).toEqual("bar");
});
const newAccount = new AnonymousControlledAccount(newRandomAgentSecret());
const newAccount = new ControlledAgent(newRandomAgentSecret());
const childContent2 = expectMap(
childObject
@@ -1840,12 +1840,12 @@ test("Can give read permissions to 'everyone' (high-level)", async () => {
expect(editable.get("foo")).toEqual("bar");
});
const newAccount = new AnonymousControlledAccount(newRandomAgentSecret());
const newAccount = new ControlledAgent(newRandomAgentSecret());
const childContent2 = expectMap(
childObject.core
.testWithDifferentAccount(
new AnonymousControlledAccount(newRandomAgentSecret()),
new ControlledAgent(newRandomAgentSecret()),
newRandomSessionID(newAccount.currentAgentID())
)
.getCurrentContent()
@@ -1880,7 +1880,7 @@ test("Can give write permission to 'everyone'", () => {
expect(editable.get("foo")).toEqual("bar");
});
const newAccount = new AnonymousControlledAccount(newRandomAgentSecret());
const newAccount = new ControlledAgent(newRandomAgentSecret());
const childContent2 = expectMap(
childObject
@@ -1913,7 +1913,7 @@ test("Can give write permissions to 'everyone' (high-level)", async () => {
expect(editable.get("foo")).toEqual("bar");
});
const newAccount = new AnonymousControlledAccount(newRandomAgentSecret());
const newAccount = new ControlledAgent(newRandomAgentSecret());
const childContent2 = expectMap(
childObject.core

View File

@@ -2,17 +2,17 @@ import { AgentSecret, createdNowUnique, getAgentID, newRandomAgentSecret } from
import { newRandomSessionID } from "../coValueCore.js";
import { LocalNode } from "../localNode.js";
import { expectGroup } from "../typeUtils/expectGroup.js";
import { AnonymousControlledAccount } from "../coValues/account.js";
import { ControlledAgent } from "../coValues/account.js";
import { SessionID } from "../ids.js";
// @ts-ignore
import { expect } from "bun:test";
export function randomAnonymousAccountAndSessionID(): [AnonymousControlledAccount, SessionID] {
export function randomAnonymousAccountAndSessionID(): [ControlledAgent, SessionID] {
const agentSecret = newRandomAgentSecret();
const sessionID = newRandomSessionID(getAgentID(agentSecret));
return [new AnonymousControlledAccount(agentSecret), sessionID];
return [new ControlledAgent(agentSecret), sessionID];
}
export function newGroup() {
@@ -73,7 +73,7 @@ export function groupWithTwoAdminsHighLevel() {
const otherAdmin = node.createAccount("otherAdmin");
group = group.addMember(otherAdmin.id, "admin");
group = group.addMember(otherAdmin, "admin");
return { admin, node, group, otherAdmin };
}

View File

@@ -1,5 +1,16 @@
# jazz-autosub
## 0.6.0
### Minor Changes
- Make addMember and removeMember take loaded Accounts instead of just IDs
### Patch Changes
- Updated dependencies
- cojson@0.6.0
## 0.5.0
### Minor Changes

View File

@@ -5,9 +5,9 @@
"types": "src/index.ts",
"type": "module",
"license": "MIT",
"version": "0.5.0",
"version": "0.6.0",
"dependencies": {
"cojson": "^0.5.0"
"cojson": "^0.6.0"
},
"scripts": {
"test": "jest",

View File

@@ -1,5 +1,5 @@
import {
AccountID,
Account,
BinaryCoStream,
CoID,
CoList,
@@ -11,6 +11,7 @@ import {
Role,
} from "cojson";
import { AutoSubContext, ValueOrResolvedRef } from "../autoSub.js";
import { ControlledAccountOrAgent } from "cojson/src/coValues/account.js";
export class ResolvedGroupMeta<G extends Group> {
coValue!: G;
@@ -55,12 +56,12 @@ export class ResolvedGroup<G extends Group = Group> {
);
}
addMember(accountID: AccountID | Everyone, role: Role): G {
addMember(accountID: Account | ControlledAccountOrAgent | Everyone, role: Role): G {
return this.meta.group.addMember(accountID, role);
}
removeMember(accountID: AccountID): G {
return this.meta.group.removeMember(accountID);
removeMember(account: Account | ControlledAccountOrAgent | Everyone): G {
return this.meta.group.removeMember(account);
}
createInvite(role: "reader" | "writer" | "admin"): InviteSecret {

View File

@@ -1,5 +1,24 @@
# jazz-browser-auth-local
## 0.5.0
### Minor Changes
- Make addMember and removeMember take loaded Accounts instead of just IDs
### Patch Changes
- Updated dependencies
- jazz-browser@0.6.0
## 0.4.17
### Patch Changes
- Allow account migrations to be async
- Updated dependencies
- jazz-browser@0.5.1
## 0.4.16
### Patch Changes

View File

@@ -1,11 +1,11 @@
{
"name": "jazz-browser-auth-local",
"version": "0.4.16",
"version": "0.5.0",
"main": "dist/index.js",
"types": "src/index.ts",
"license": "MIT",
"dependencies": {
"jazz-browser": "^0.5.0",
"jazz-browser": "^0.6.0",
"typescript": "^5.1.6"
},
"scripts": {

View File

@@ -110,7 +110,7 @@ async function signUp(
const secretSeed = cojsonInternals.newRandomSecretSeed();
const { node, accountID, accountSecret } =
LocalNode.withNewlyCreatedAccount({
await LocalNode.withNewlyCreatedAccount({
name: username,
initialAgentSecret: agentSecretFromSecretSeed(secretSeed),
migration,

View File

@@ -1,5 +1,14 @@
# jazz-browser-media-images
## 0.5.1
### Patch Changes
- Updated dependencies
- jazz-autosub@0.6.0
- jazz-browser@0.6.0
- cojson@0.6.0
## 0.5.0
### Minor Changes

View File

@@ -1,15 +1,15 @@
{
"name": "jazz-browser-media-images",
"version": "0.5.0",
"version": "0.5.1",
"main": "dist/index.js",
"types": "src/index.ts",
"license": "MIT",
"dependencies": {
"@types/image-blob-reduce": "^4.1.1",
"cojson": "^0.5.0",
"cojson": "^0.6.0",
"image-blob-reduce": "^4.1.0",
"jazz-autosub": "^0.5.0",
"jazz-browser": "^0.5.0",
"jazz-autosub": "^0.6.0",
"jazz-browser": "^0.6.0",
"typescript": "^5.1.6"
},
"scripts": {

View File

@@ -1,5 +1,26 @@
# jazz-browser
## 0.6.0
### Minor Changes
- Make addMember and removeMember take loaded Accounts instead of just IDs
### Patch Changes
- Updated dependencies
- cojson-storage-indexeddb@0.6.0
- jazz-autosub@0.6.0
- cojson@0.6.0
## 0.5.1
### Patch Changes
- Allow account migrations to be async
- Updated dependencies
- cojson@0.5.2
## 0.5.0
### Minor Changes

View File

@@ -1,13 +1,13 @@
{
"name": "jazz-browser",
"version": "0.5.0",
"version": "0.6.0",
"main": "dist/index.js",
"types": "src/index.ts",
"license": "MIT",
"dependencies": {
"cojson": "^0.5.0",
"cojson-storage-indexeddb": "^0.5.0",
"jazz-autosub": "^0.5.0",
"cojson": "^0.6.0",
"cojson-storage-indexeddb": "^0.6.0",
"jazz-autosub": "^0.6.0",
"typescript": "^5.1.6"
},
"scripts": {

View File

@@ -61,7 +61,7 @@ export class BrowserDemoAuth implements AuthProvider {
this.driver.onReady({
signUp: async (username) => {
const { node, accountID, accountSecret } =
LocalNode.withNewlyCreatedAccount({
await LocalNode.withNewlyCreatedAccount({
name: username,
migration,
});

View File

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

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

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

View File

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

View File

@@ -0,0 +1,51 @@
# jazz-autosub
## 0.6.1
### Patch Changes
- Fix wrong import from cojson
## 0.6.0
### Minor Changes
- Make addMember and removeMember take loaded Accounts instead of just IDs
### Patch Changes
- Updated dependencies
- jazz-autosub@0.6.0
- cojson@0.6.0
- cojson-transport-nodejs-ws@0.5.1
## 0.5.3
### Patch Changes
- Allow account migrations to be async
- Updated dependencies
- cojson@0.5.2
## 0.5.2
### Patch Changes
- Expose migration for jazz-nodejs
## 0.5.1
### Patch Changes
- First version of jazz-nodejs
## 0.5.0
### Minor Changes
- Adding a lot of performance improvements to cojson, add a stresstest for the twit example and make that run smoother in a lot of ways.
### Patch Changes
- Updated dependencies
- cojson@0.5.0

View File

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

View File

@@ -0,0 +1,3 @@
# CoJSON
[See the top-level README](../../README.md#cojson)

View File

@@ -0,0 +1,42 @@
{
"name": "jazz-nodejs",
"module": "dist/index.js",
"main": "dist/index.js",
"types": "src/index.ts",
"type": "module",
"license": "MIT",
"version": "0.6.1",
"dependencies": {
"cojson": "^0.6.0",
"cojson-transport-nodejs-ws": "^0.5.1",
"jazz-autosub": "^0.6.0"
},
"scripts": {
"test": "jest",
"lint": "eslint src/**/*.ts",
"build": "npm run lint && rm -rf ./dist && tsc --sourceMap --outDir dist",
"prepublishOnly": "npm run build"
},
"jest": {
"preset": "ts-jest",
"testEnvironment": "node",
"transform": {
"\\.[jt]sx?$": [
"ts-jest",
{
"useESM": true
}
]
},
"moduleNameMapper": {
"(.+)\\.js": "$1"
},
"extensionsToTreatAsEsm": [
".ts"
],
"modulePathIgnorePatterns": [
"/node_modules/",
"/dist/"
]
}
}

View File

@@ -0,0 +1,161 @@
import {
websocketReadableStream,
websocketWritableStream,
} from "cojson-transport-nodejs-ws";
import { WebSocket } from "ws";
import "dotenv/config";
import { webcrypto } from "node:crypto";
import {
AccountID,
AccountMigration,
AgentSecret,
CoMap,
ControlledAccount,
LocalNode,
Peer,
Profile,
SessionID,
cojsonReady,
cojsonInternals
} from "cojson";
import { readFile, writeFile } from "node:fs/promises";
if (!("crypto" in globalThis)) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(globalThis as any).crypto = webcrypto;
}
interface WorkerCredentialStorage {
load(
workerName: string
): Promise<
{ accountID: AccountID; accountSecret: AgentSecret } | undefined
>;
save(
workerName: string,
accountID: AccountID,
accountSecret: AgentSecret
): Promise<void>;
}
export async function createOrResumeWorker<
P extends Profile = Profile,
R extends CoMap = CoMap
>({
workerName,
credentialStorage = FileCredentialStorage,
syncServer = "wss://sync.jazz.tools",
migration,
}: {
workerName: string;
credentialStorage?: WorkerCredentialStorage;
syncServer?: string;
migration?: AccountMigration<P, R>;
}) {
await cojsonReady;
const existingCredentials = await credentialStorage.load(workerName);
let localNode: LocalNode;
const ws = new WebSocket(syncServer);
const wsPeer: Peer = {
id: "globalMesh",
role: "server",
incoming: websocketReadableStream(ws),
outgoing: websocketWritableStream(ws),
};
if (existingCredentials) {
// TODO: locked sessions similar to browser
const sessionID =
process.env.JAZZ_WORKER_SESSION ||
cojsonInternals.newRandomSessionID(existingCredentials.accountID);
console.log("Loading worker", existingCredentials.accountID);
localNode = await LocalNode.withLoadedAccount({
accountID: existingCredentials.accountID,
accountSecret: existingCredentials.accountSecret,
sessionID: sessionID as SessionID,
migration,
peersToLoadFrom: [wsPeer],
});
console.log(
"Resuming worker",
existingCredentials.accountID,
localNode
.expectProfileLoaded(localNode.account.id as AccountID)
.get("name")
);
} else {
const newWorker = await LocalNode.withNewlyCreatedAccount({
name: workerName,
peersToLoadFrom: [wsPeer],
migration,
});
localNode = newWorker.node;
await credentialStorage.save(
workerName,
newWorker.accountID,
newWorker.accountSecret
);
console.log("Created worker", newWorker.accountID, workerName);
}
return { localNode, worker: localNode.account as ControlledAccount<P, R> };
}
export { autoSub } from "jazz-autosub";
export const FileCredentialStorage: WorkerCredentialStorage = {
async load(workerName: string): Promise<
| {
accountID: AccountID;
accountSecret: `sealerSecret_z${string}/signerSecret_z${string}`;
}
| undefined
> {
try {
const credentials = await readFile(
`${workerName}Credentials.json`,
"utf-8"
);
return JSON.parse(credentials);
} catch (e) {
return undefined;
}
},
async save(
workerName: string,
accountID: AccountID,
accountSecret: `sealerSecret_z${string}/signerSecret_z${string}`
): Promise<void> {
await writeFile(
`${workerName}Credentials.json`,
JSON.stringify({ accountID, accountSecret }, undefined, 2)
);
console.log(
`Saved credentials for ${workerName} to ${workerName}Credentials.json`
);
try {
const gitginore = await readFile(".gitignore", "utf-8");
if (!gitginore.includes(`${workerName}Credentials.json`)) {
await writeFile(
".gitignore",
gitginore + `\n${workerName}Credentials.json`
);
console.log(`Added ${workerName}Credentials.json to .gitignore`);
}
} catch (e) {
console.warn(`Couldn't add ${workerName}Credentials.json to .gitignore, please add it yourself.`)
}
},
};

View File

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

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,13 @@
# jazz-react-auth-local
## 0.4.17
### Patch Changes
- Updated dependencies
- jazz-browser-auth-local@0.5.0
- jazz-react@0.5.1
## 0.4.16
### Patch Changes

View File

@@ -1,12 +1,12 @@
{
"name": "jazz-react-auth-local",
"version": "0.4.16",
"version": "0.4.17",
"main": "dist/index.js",
"types": "src/index.tsx",
"license": "MIT",
"dependencies": {
"jazz-browser-auth-local": "^0.4.16",
"jazz-react": "^0.5.0",
"jazz-browser-auth-local": "^0.5.0",
"jazz-react": "^0.5.1",
"typescript": "^5.1.6"
},
"devDependencies": {

View File

@@ -1,5 +1,13 @@
# jazz-react
## 0.5.1
### Patch Changes
- Updated dependencies
- jazz-browser@0.6.0
- cojson@0.6.0
## 0.5.0
### Minor Changes

View File

@@ -1,12 +1,12 @@
{
"name": "jazz-react",
"version": "0.5.0",
"version": "0.5.1",
"main": "dist/index.js",
"types": "src/index.ts",
"license": "MIT",
"dependencies": {
"cojson": "^0.5.0",
"jazz-browser": "^0.5.0",
"cojson": "^0.6.0",
"jazz-browser": "^0.6.0",
"typescript": "^5.1.6"
},
"devDependencies": {