cojson 0.0.19 → 0.0.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/account.d.ts +1 -1
- package/dist/account.js +1 -1
- package/dist/account.js.map +1 -1
- package/dist/coValue.d.ts +3 -2
- package/dist/coValue.js +10 -9
- package/dist/coValue.js.map +1 -1
- package/dist/contentTypes/coMap.d.ts +2 -2
- package/dist/contentTypes/coMap.js +6 -6
- package/dist/contentTypes/coMap.js.map +1 -1
- package/dist/index.d.ts +5 -2
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/jsonValue.d.ts +2 -2
- package/dist/node.d.ts +2 -1
- package/dist/node.js +43 -3
- package/dist/node.js.map +1 -1
- package/dist/permissions.d.ts +4 -32
- package/dist/permissions.js +42 -106
- package/dist/permissions.js.map +1 -1
- package/dist/team.d.ts +35 -0
- package/dist/team.js +110 -0
- package/dist/team.js.map +1 -0
- package/dist/testUtils.d.ts +2 -2
- package/dist/testUtils.js +1 -1
- package/dist/testUtils.js.map +1 -1
- package/package.json +3 -3
- package/src/account.ts +1 -1
- package/src/coValue.ts +10 -11
- package/src/contentTypes/coMap.ts +8 -8
- package/src/crypto.test.ts +10 -9
- package/src/index.ts +5 -0
- package/src/jsonValue.ts +2 -2
- package/src/node.ts +113 -11
- package/src/permissions.test.ts +503 -3
- package/src/permissions.ts +70 -206
- package/src/sync.test.ts +8 -8
- package/src/team.ts +225 -0
- package/src/testUtils.ts +1 -1
- package/tsconfig.json +1 -0
- package/dist/account.test.d.ts +0 -1
- package/dist/account.test.js +0 -40
- package/dist/account.test.js.map +0 -1
- package/dist/coValue.test.d.ts +0 -1
- package/dist/coValue.test.js +0 -78
- package/dist/coValue.test.js.map +0 -1
- package/dist/contentType.test.d.ts +0 -1
- package/dist/contentType.test.js +0 -145
- package/dist/contentType.test.js.map +0 -1
- package/dist/crypto.test.d.ts +0 -1
- package/dist/crypto.test.js +0 -111
- package/dist/crypto.test.js.map +0 -1
- package/dist/permissions.test.d.ts +0 -1
- package/dist/permissions.test.js +0 -711
- package/dist/permissions.test.js.map +0 -1
- package/dist/sync.test.d.ts +0 -1
- package/dist/sync.test.js +0 -827
- package/dist/sync.test.js.map +0 -1
package/src/crypto.test.ts
CHANGED
|
@@ -20,6 +20,7 @@ import { x25519 } from "@noble/curves/ed25519";
|
|
|
20
20
|
import { xsalsa20_poly1305 } from "@noble/ciphers/salsa";
|
|
21
21
|
import { blake3 } from "@noble/hashes/blake3";
|
|
22
22
|
import stableStringify from "fast-json-stable-stringify";
|
|
23
|
+
import { SessionID } from './ids.js';
|
|
23
24
|
|
|
24
25
|
test("Signatures round-trip and use stable stringify", () => {
|
|
25
26
|
const data = { b: "world", a: "hello" };
|
|
@@ -49,7 +50,7 @@ test("encrypting round-trips, but invalid receiver can't unseal", () => {
|
|
|
49
50
|
|
|
50
51
|
const nOnceMaterial = {
|
|
51
52
|
in: "co_zTEST",
|
|
52
|
-
tx: { sessionID: "co_zTEST_session_zTEST", txIndex: 0 },
|
|
53
|
+
tx: { sessionID: "co_zTEST_session_zTEST" as SessionID, txIndex: 0 },
|
|
53
54
|
} as const;
|
|
54
55
|
|
|
55
56
|
const sealed = seal(
|
|
@@ -101,22 +102,22 @@ test("Encryption for transactions round-trips", () => {
|
|
|
101
102
|
|
|
102
103
|
const encrypted1 = encryptForTransaction({ a: "hello" }, secret, {
|
|
103
104
|
in: "co_zTEST",
|
|
104
|
-
tx: { sessionID: "co_zTEST_session_zTEST", txIndex: 0 },
|
|
105
|
+
tx: { sessionID: "co_zTEST_session_zTEST" as SessionID, txIndex: 0 },
|
|
105
106
|
});
|
|
106
107
|
|
|
107
108
|
const encrypted2 = encryptForTransaction({ b: "world" }, secret, {
|
|
108
109
|
in: "co_zTEST",
|
|
109
|
-
tx: { sessionID: "co_zTEST_session_zTEST", txIndex: 1 },
|
|
110
|
+
tx: { sessionID: "co_zTEST_session_zTEST" as SessionID, txIndex: 1 },
|
|
110
111
|
});
|
|
111
112
|
|
|
112
113
|
const decrypted1 = decryptForTransaction(encrypted1, secret, {
|
|
113
114
|
in: "co_zTEST",
|
|
114
|
-
tx: { sessionID: "co_zTEST_session_zTEST", txIndex: 0 },
|
|
115
|
+
tx: { sessionID: "co_zTEST_session_zTEST" as SessionID, txIndex: 0 },
|
|
115
116
|
});
|
|
116
117
|
|
|
117
118
|
const decrypted2 = decryptForTransaction(encrypted2, secret, {
|
|
118
119
|
in: "co_zTEST",
|
|
119
|
-
tx: { sessionID: "co_zTEST_session_zTEST", txIndex: 1 },
|
|
120
|
+
tx: { sessionID: "co_zTEST_session_zTEST" as SessionID, txIndex: 1 },
|
|
120
121
|
});
|
|
121
122
|
|
|
122
123
|
expect([decrypted1, decrypted2]).toEqual([{ a: "hello" }, { b: "world" }]);
|
|
@@ -128,22 +129,22 @@ test("Encryption for transactions doesn't decrypt with a wrong key", () => {
|
|
|
128
129
|
|
|
129
130
|
const encrypted1 = encryptForTransaction({ a: "hello" }, secret, {
|
|
130
131
|
in: "co_zTEST",
|
|
131
|
-
tx: { sessionID: "co_zTEST_session_zTEST", txIndex: 0 },
|
|
132
|
+
tx: { sessionID: "co_zTEST_session_zTEST" as SessionID, txIndex: 0 },
|
|
132
133
|
});
|
|
133
134
|
|
|
134
135
|
const encrypted2 = encryptForTransaction({ b: "world" }, secret, {
|
|
135
136
|
in: "co_zTEST",
|
|
136
|
-
tx: { sessionID: "co_zTEST_session_zTEST", txIndex: 1 },
|
|
137
|
+
tx: { sessionID: "co_zTEST_session_zTEST" as SessionID, txIndex: 1 },
|
|
137
138
|
});
|
|
138
139
|
|
|
139
140
|
const decrypted1 = decryptForTransaction(encrypted1, secret2, {
|
|
140
141
|
in: "co_zTEST",
|
|
141
|
-
tx: { sessionID: "co_zTEST_session_zTEST", txIndex: 0 },
|
|
142
|
+
tx: { sessionID: "co_zTEST_session_zTEST" as SessionID, txIndex: 0 },
|
|
142
143
|
});
|
|
143
144
|
|
|
144
145
|
const decrypted2 = decryptForTransaction(encrypted2, secret2, {
|
|
145
146
|
in: "co_zTEST",
|
|
146
|
-
tx: { sessionID: "co_zTEST_session_zTEST", txIndex: 1 },
|
|
147
|
+
tx: { sessionID: "co_zTEST_session_zTEST" as SessionID, txIndex: 1 },
|
|
147
148
|
});
|
|
148
149
|
|
|
149
150
|
expect([decrypted1, decrypted2]).toEqual([undefined, undefined]);
|
package/src/index.ts
CHANGED
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
import { connectedPeers } from "./streamUtils.js";
|
|
15
15
|
import { AnonymousControlledAccount, ControlledAccount } from "./account.js";
|
|
16
16
|
import { rawCoIDtoBytes, rawCoIDfromBytes } from "./ids.js";
|
|
17
|
+
import { Team, expectTeamContent } from "./team.js"
|
|
17
18
|
|
|
18
19
|
import type { SessionID, AgentID } from "./ids.js";
|
|
19
20
|
import type { CoID, ContentType } from "./contentType.js";
|
|
@@ -26,6 +27,7 @@ import type {
|
|
|
26
27
|
ProfileContent,
|
|
27
28
|
Profile,
|
|
28
29
|
} from "./account.js";
|
|
30
|
+
import type { InviteSecret } from "./team.js";
|
|
29
31
|
|
|
30
32
|
type Value = JsonValue | ContentType;
|
|
31
33
|
|
|
@@ -42,6 +44,7 @@ export const cojsonInternals = {
|
|
|
42
44
|
agentSecretFromSecretSeed,
|
|
43
45
|
secretSeedLength,
|
|
44
46
|
shortHashLength,
|
|
47
|
+
expectTeamContent
|
|
45
48
|
};
|
|
46
49
|
|
|
47
50
|
export {
|
|
@@ -50,6 +53,7 @@ export {
|
|
|
50
53
|
CoMap,
|
|
51
54
|
AnonymousControlledAccount,
|
|
52
55
|
ControlledAccount,
|
|
56
|
+
Team
|
|
53
57
|
};
|
|
54
58
|
|
|
55
59
|
export type {
|
|
@@ -66,6 +70,7 @@ export type {
|
|
|
66
70
|
AccountContent,
|
|
67
71
|
Profile,
|
|
68
72
|
ProfileContent,
|
|
73
|
+
InviteSecret
|
|
69
74
|
};
|
|
70
75
|
|
|
71
76
|
// eslint-disable-next-line @typescript-eslint/no-namespace
|
package/src/jsonValue.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { RawCoID } from './ids.js';
|
|
2
2
|
|
|
3
3
|
export type JsonAtom = string | number | boolean | null;
|
|
4
|
-
export type JsonValue = JsonAtom | JsonArray | JsonObject |
|
|
4
|
+
export type JsonValue = JsonAtom | JsonArray | JsonObject | RawCoID;
|
|
5
5
|
export type JsonArray = JsonValue[];
|
|
6
6
|
export type JsonObject = { [key: string]: JsonValue; };
|
package/src/node.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
AgentSecret,
|
|
3
|
+
agentSecretFromSecretSeed,
|
|
3
4
|
createdNowUnique,
|
|
4
5
|
getAgentID,
|
|
5
6
|
getAgentSealerID,
|
|
@@ -9,7 +10,13 @@ import {
|
|
|
9
10
|
seal,
|
|
10
11
|
} from "./crypto.js";
|
|
11
12
|
import { CoValue, CoValueHeader, newRandomSessionID } from "./coValue.js";
|
|
12
|
-
import {
|
|
13
|
+
import {
|
|
14
|
+
InviteSecret,
|
|
15
|
+
Team,
|
|
16
|
+
TeamContent,
|
|
17
|
+
expectTeamContent,
|
|
18
|
+
secretSeedFromInviteSecret,
|
|
19
|
+
} from "./team.js";
|
|
13
20
|
import { Peer, SyncManager } from "./sync.js";
|
|
14
21
|
import { AgentID, RawCoID, SessionID, isAgentID } from "./ids.js";
|
|
15
22
|
import { CoID, ContentType } from "./contentType.js";
|
|
@@ -43,7 +50,10 @@ export class LocalNode {
|
|
|
43
50
|
this.ownSessionID = ownSessionID;
|
|
44
51
|
}
|
|
45
52
|
|
|
46
|
-
static withNewlyCreatedAccount(
|
|
53
|
+
static withNewlyCreatedAccount(
|
|
54
|
+
name: string,
|
|
55
|
+
initialAgentSecret = newRandomAgentSecret()
|
|
56
|
+
): {
|
|
47
57
|
node: LocalNode;
|
|
48
58
|
accountID: AccountID;
|
|
49
59
|
accountSecret: AgentSecret;
|
|
@@ -70,8 +80,16 @@ export class LocalNode {
|
|
|
70
80
|
};
|
|
71
81
|
}
|
|
72
82
|
|
|
73
|
-
static async withLoadedAccount(
|
|
74
|
-
|
|
83
|
+
static async withLoadedAccount(
|
|
84
|
+
accountID: AccountID,
|
|
85
|
+
accountSecret: AgentSecret,
|
|
86
|
+
sessionID: SessionID,
|
|
87
|
+
peersToLoadFrom: Peer[]
|
|
88
|
+
): Promise<LocalNode> {
|
|
89
|
+
const loadingNode = new LocalNode(
|
|
90
|
+
new AnonymousControlledAccount(accountSecret),
|
|
91
|
+
newRandomSessionID(accountID)
|
|
92
|
+
);
|
|
75
93
|
|
|
76
94
|
const accountPromise = loadingNode.load(accountID);
|
|
77
95
|
|
|
@@ -82,7 +100,10 @@ export class LocalNode {
|
|
|
82
100
|
const account = await accountPromise;
|
|
83
101
|
|
|
84
102
|
// since this is all synchronous, we can just swap out nodes for the SyncManager
|
|
85
|
-
const node = loadingNode.testWithDifferentAccount(
|
|
103
|
+
const node = loadingNode.testWithDifferentAccount(
|
|
104
|
+
new ControlledAccount(accountSecret, account, loadingNode),
|
|
105
|
+
sessionID
|
|
106
|
+
);
|
|
86
107
|
node.sync = loadingNode.sync;
|
|
87
108
|
node.sync.local = node;
|
|
88
109
|
|
|
@@ -124,7 +145,75 @@ export class LocalNode {
|
|
|
124
145
|
if (!profileID) {
|
|
125
146
|
throw new Error(`Account ${id} has no profile`);
|
|
126
147
|
}
|
|
127
|
-
return (
|
|
148
|
+
return (
|
|
149
|
+
await this.loadCoValue(profileID)
|
|
150
|
+
).getCurrentContent() as Profile;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
async acceptInvite<T extends ContentType>(
|
|
154
|
+
teamOrOwnedValueID: CoID<T>,
|
|
155
|
+
inviteSecret: InviteSecret
|
|
156
|
+
): Promise<void> {
|
|
157
|
+
const teamOrOwnedValue = await this.load(teamOrOwnedValueID);
|
|
158
|
+
|
|
159
|
+
if (teamOrOwnedValue.coValue.header.ruleset.type === "ownedByTeam") {
|
|
160
|
+
return this.acceptInvite(
|
|
161
|
+
teamOrOwnedValue.coValue.header.ruleset.team as CoID<
|
|
162
|
+
CoMap<TeamContent>
|
|
163
|
+
>,
|
|
164
|
+
inviteSecret
|
|
165
|
+
);
|
|
166
|
+
} else if (teamOrOwnedValue.coValue.header.ruleset.type !== "team") {
|
|
167
|
+
throw new Error("Can only accept invites to teams");
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const team = new Team(expectTeamContent(teamOrOwnedValue), this);
|
|
171
|
+
|
|
172
|
+
const inviteAgentSecret = agentSecretFromSecretSeed(
|
|
173
|
+
secretSeedFromInviteSecret(inviteSecret)
|
|
174
|
+
);
|
|
175
|
+
const inviteAgentID = getAgentID(inviteAgentSecret);
|
|
176
|
+
|
|
177
|
+
const invitationRole = await new Promise((resolve, reject) => {
|
|
178
|
+
team.teamMap.subscribe((teamMap) => {
|
|
179
|
+
const role = teamMap.get(inviteAgentID);
|
|
180
|
+
if (role) {
|
|
181
|
+
resolve(role);
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
setTimeout(() => reject(new Error("Couldn't find invitation before timeout")), 1000);
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
if (!invitationRole) {
|
|
188
|
+
throw new Error("No invitation found");
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const existingRole = team.teamMap.get(this.account.id);
|
|
192
|
+
|
|
193
|
+
if (
|
|
194
|
+
existingRole === "admin" ||
|
|
195
|
+
(existingRole === "writer" && invitationRole === "reader")
|
|
196
|
+
) {
|
|
197
|
+
console.debug("Not accepting invite that would downgrade role");
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const teamAsInvite = team.testWithDifferentAccount(
|
|
202
|
+
new AnonymousControlledAccount(inviteAgentSecret),
|
|
203
|
+
newRandomSessionID(inviteAgentID)
|
|
204
|
+
);
|
|
205
|
+
|
|
206
|
+
teamAsInvite.addMember(
|
|
207
|
+
this.account.id,
|
|
208
|
+
invitationRole === "adminInvite"
|
|
209
|
+
? "admin"
|
|
210
|
+
: invitationRole === "writerInvite"
|
|
211
|
+
? "writer"
|
|
212
|
+
: "reader"
|
|
213
|
+
);
|
|
214
|
+
|
|
215
|
+
team.teamMap.coValue.sessions = teamAsInvite.teamMap.coValue.sessions;
|
|
216
|
+
team.teamMap.coValue._cachedContent = undefined;
|
|
128
217
|
}
|
|
129
218
|
|
|
130
219
|
expectCoValueLoaded(id: RawCoID, expectation?: string): CoValue {
|
|
@@ -146,7 +235,9 @@ export class LocalNode {
|
|
|
146
235
|
|
|
147
236
|
expectProfileLoaded(id: AccountID, expectation?: string): Profile {
|
|
148
237
|
const account = this.expectCoValueLoaded(id, expectation);
|
|
149
|
-
const profileID = expectTeamContent(account.getCurrentContent()).get(
|
|
238
|
+
const profileID = expectTeamContent(account.getCurrentContent()).get(
|
|
239
|
+
"profile"
|
|
240
|
+
);
|
|
150
241
|
if (!profileID) {
|
|
151
242
|
throw new Error(
|
|
152
243
|
`${
|
|
@@ -154,10 +245,16 @@ export class LocalNode {
|
|
|
154
245
|
}Account ${id} has no profile`
|
|
155
246
|
);
|
|
156
247
|
}
|
|
157
|
-
return this.expectCoValueLoaded(
|
|
248
|
+
return this.expectCoValueLoaded(
|
|
249
|
+
profileID,
|
|
250
|
+
expectation
|
|
251
|
+
).getCurrentContent() as Profile;
|
|
158
252
|
}
|
|
159
253
|
|
|
160
|
-
createAccount(
|
|
254
|
+
createAccount(
|
|
255
|
+
name: string,
|
|
256
|
+
agentSecret = newRandomAgentSecret()
|
|
257
|
+
): ControlledAccount {
|
|
161
258
|
const account = this.createCoValue(
|
|
162
259
|
accountHeaderForInitialAgentSecret(agentSecret)
|
|
163
260
|
).testWithDifferentAccount(
|
|
@@ -165,7 +262,10 @@ export class LocalNode {
|
|
|
165
262
|
newRandomSessionID(getAgentID(agentSecret))
|
|
166
263
|
);
|
|
167
264
|
|
|
168
|
-
const accountAsTeam = new Team(
|
|
265
|
+
const accountAsTeam = new Team(
|
|
266
|
+
expectTeamContent(account.getCurrentContent()),
|
|
267
|
+
account.node
|
|
268
|
+
);
|
|
169
269
|
|
|
170
270
|
accountAsTeam.teamMap.edit((editable) => {
|
|
171
271
|
editable.set(getAgentID(agentSecret), "admin", "trusting");
|
|
@@ -195,7 +295,9 @@ export class LocalNode {
|
|
|
195
295
|
account.node
|
|
196
296
|
);
|
|
197
297
|
|
|
198
|
-
const profile = accountAsTeam.createMap<ProfileContent, ProfileMeta>({
|
|
298
|
+
const profile = accountAsTeam.createMap<ProfileContent, ProfileMeta>({
|
|
299
|
+
type: "profile",
|
|
300
|
+
});
|
|
199
301
|
|
|
200
302
|
profile.edit((editable) => {
|
|
201
303
|
editable.set("name", name, "trusting");
|