@drakkar.software/octospaces-sdk 0.1.0 → 0.4.1
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/CHANGELOG.md +200 -0
- package/dist/index.d.ts +337 -273
- package/dist/index.js +812 -469
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/core/types.ts +47 -81
- package/src/index.ts +42 -28
- package/src/objects/objects.test.ts +55 -95
- package/src/objects/objects.ts +23 -136
- package/src/spaces/members.test.ts +10 -3
- package/src/spaces/members.ts +86 -49
- package/src/spaces/nodes.test.ts +225 -0
- package/src/spaces/nodes.ts +427 -0
- package/src/spaces/object-index.test.ts +127 -71
- package/src/spaces/object-index.ts +61 -107
- package/src/spaces/registry.test.ts +59 -46
- package/src/spaces/registry.ts +28 -47
- package/src/sync/client.ts +20 -15
- package/src/sync/pairing.ts +10 -12
- package/src/sync/paths.test.ts +124 -16
- package/src/sync/paths.ts +73 -32
- package/src/sync/space-access-store.ts +17 -0
- package/src/sync/space-access.ts +112 -67
package/dist/index.js
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
3
|
+
var __esm = (fn, res) => function __init() {
|
|
4
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
5
|
+
};
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
|
|
1
11
|
// src/core/config.ts
|
|
2
|
-
var cfg = null;
|
|
3
12
|
function configureOctoSpaces(config) {
|
|
4
13
|
if ("namespace" in config && !config.syncNamespace) {
|
|
5
14
|
throw new Error(
|
|
@@ -24,17 +33,23 @@ function req() {
|
|
|
24
33
|
if (!cfg) throw new Error("octospaces-sdk: configureOctoSpaces() not called \u2014 wire it at app boot.");
|
|
25
34
|
return cfg;
|
|
26
35
|
}
|
|
27
|
-
var getSyncBase
|
|
28
|
-
var
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
36
|
+
var cfg, getSyncBase, getSyncNamespace, getSyncPrefix, getSharedSpacesNamespace, getOnServerReachable;
|
|
37
|
+
var init_config = __esm({
|
|
38
|
+
"src/core/config.ts"() {
|
|
39
|
+
"use strict";
|
|
40
|
+
cfg = null;
|
|
41
|
+
getSyncBase = () => req().syncBase;
|
|
42
|
+
getSyncNamespace = () => req().syncNamespace;
|
|
43
|
+
getSyncPrefix = () => {
|
|
44
|
+
const ns = req().syncNamespace;
|
|
45
|
+
return ns ? `/v1/${ns}` : "";
|
|
46
|
+
};
|
|
47
|
+
getSharedSpacesNamespace = () => cfg?.sharedSpacesNamespace;
|
|
48
|
+
getOnServerReachable = () => cfg?.onServerReachable;
|
|
49
|
+
}
|
|
50
|
+
});
|
|
35
51
|
|
|
36
52
|
// src/core/adapters.ts
|
|
37
|
-
var kv = null;
|
|
38
53
|
function configureKv(adapter) {
|
|
39
54
|
kv = adapter;
|
|
40
55
|
}
|
|
@@ -42,9 +57,16 @@ function getKv() {
|
|
|
42
57
|
if (!kv) throw new Error("octospaces-sdk: configureKv() not called \u2014 wire it at app boot.");
|
|
43
58
|
return kv;
|
|
44
59
|
}
|
|
45
|
-
var kvGet
|
|
46
|
-
var
|
|
47
|
-
|
|
60
|
+
var kv, kvGet, kvSet, kvRemove;
|
|
61
|
+
var init_adapters = __esm({
|
|
62
|
+
"src/core/adapters.ts"() {
|
|
63
|
+
"use strict";
|
|
64
|
+
kv = null;
|
|
65
|
+
kvGet = (key2) => getKv().get(key2);
|
|
66
|
+
kvSet = (key2, value) => getKv().set(key2, value);
|
|
67
|
+
kvRemove = (key2) => getKv().remove(key2);
|
|
68
|
+
}
|
|
69
|
+
});
|
|
48
70
|
|
|
49
71
|
// src/core/ids.ts
|
|
50
72
|
function randomId() {
|
|
@@ -57,49 +79,13 @@ function randomId() {
|
|
|
57
79
|
function roomSlug(name) {
|
|
58
80
|
return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 40) || "room";
|
|
59
81
|
}
|
|
82
|
+
var init_ids = __esm({
|
|
83
|
+
"src/core/ids.ts"() {
|
|
84
|
+
"use strict";
|
|
85
|
+
}
|
|
86
|
+
});
|
|
60
87
|
|
|
61
88
|
// src/sync/paths.ts
|
|
62
|
-
var pull = (rest) => `/pull/${rest}`;
|
|
63
|
-
var push = (rest) => `/push/${rest}`;
|
|
64
|
-
var spaceIdFromRoomId = (roomId) => roomId.split("-").slice(0, 2).join("-");
|
|
65
|
-
var keyringName = (spaceId) => `spaces/${spaceId}`;
|
|
66
|
-
var keyringPull = (spaceId) => pull(`${keyringName(spaceId)}/_keyring`);
|
|
67
|
-
var keyringPush = (spaceId) => push(`${keyringName(spaceId)}/_keyring`);
|
|
68
|
-
var attachmentName = (roomId, blobId) => `spaces/${spaceIdFromRoomId(roomId)}/attachments/${roomId}/${blobId}`;
|
|
69
|
-
var attachmentPull = (roomId, blobId) => pull(attachmentName(roomId, blobId));
|
|
70
|
-
var attachmentPush = (roomId, blobId) => push(attachmentName(roomId, blobId));
|
|
71
|
-
var profilePull = (userId) => pull(`user/${userId}/profile`);
|
|
72
|
-
var profilePush = (userId) => push(`user/${userId}/profile`);
|
|
73
|
-
var spacesPull = (userId) => pull(`user/${userId}/_spaces`);
|
|
74
|
-
var spacesPush = (userId) => push(`user/${userId}/_spaces`);
|
|
75
|
-
var roomsRegistryPull = (spaceId) => pull(`spaces/${spaceId}/_rooms`);
|
|
76
|
-
var roomsRegistryPush = (spaceId) => push(`spaces/${spaceId}/_rooms`);
|
|
77
|
-
var objIndexName = (spaceId) => `spaces/${spaceId}/objects/_index`;
|
|
78
|
-
var objIndexPull = (spaceId) => pull(objIndexName(spaceId));
|
|
79
|
-
var objIndexPush = (spaceId) => push(objIndexName(spaceId));
|
|
80
|
-
var objLogName = (spaceId, objectId) => `spaces/${spaceId}/objects/logs/${objectId}`;
|
|
81
|
-
var objLogPull = (spaceId, objectId) => pull(objLogName(spaceId, objectId));
|
|
82
|
-
var objLogPush = (spaceId, objectId) => push(objLogName(spaceId, objectId));
|
|
83
|
-
var objDocName = (spaceId, objectId) => `spaces/${spaceId}/objects/docs/${objectId}`;
|
|
84
|
-
var objDocPull = (spaceId, objectId) => pull(objDocName(spaceId, objectId));
|
|
85
|
-
var objDocPush = (spaceId, objectId) => push(objDocName(spaceId, objectId));
|
|
86
|
-
var objectBlobName = (spaceId, blobId) => `spaces/${spaceId}/objects/blobs/${blobId}`;
|
|
87
|
-
var objectBlobPull = (spaceId, blobId) => pull(objectBlobName(spaceId, blobId));
|
|
88
|
-
var objectBlobPush = (spaceId, blobId) => push(objectBlobName(spaceId, blobId));
|
|
89
|
-
var typesIndexName = (spaceId) => `spaces/${spaceId}/types/_index`;
|
|
90
|
-
var typesIndexPull = (spaceId) => pull(typesIndexName(spaceId));
|
|
91
|
-
var typesIndexPush = (spaceId) => push(typesIndexName(spaceId));
|
|
92
|
-
var spaceIndexName = (shard) => `_index/spaces/${shard}`;
|
|
93
|
-
var spaceIndexPull = (shard) => pull(spaceIndexName(shard));
|
|
94
|
-
var OBJECT_COLLECTIONS = [
|
|
95
|
-
"keyring",
|
|
96
|
-
"objindex",
|
|
97
|
-
"objlog",
|
|
98
|
-
"objsnap",
|
|
99
|
-
"objdoc",
|
|
100
|
-
"objblob",
|
|
101
|
-
"typeindex"
|
|
102
|
-
];
|
|
103
89
|
function ownerScope() {
|
|
104
90
|
return {
|
|
105
91
|
ops: ["read", "list", "write"],
|
|
@@ -115,10 +101,18 @@ function spaceMemberScope(spaceId, canWrite) {
|
|
|
115
101
|
paths: [`spaces/${spaceId}/**`]
|
|
116
102
|
};
|
|
117
103
|
}
|
|
104
|
+
function nodeMemberScope(spaceId, nodeId, canWrite) {
|
|
105
|
+
const ops = canWrite ? ["read", "list", "write"] : ["read", "list"];
|
|
106
|
+
return {
|
|
107
|
+
ops,
|
|
108
|
+
collections: ["objinv"],
|
|
109
|
+
paths: [`spaces/${spaceId}/objects/n/${nodeId}/**`]
|
|
110
|
+
};
|
|
111
|
+
}
|
|
118
112
|
function accountScope(userId) {
|
|
119
113
|
return {
|
|
120
114
|
ops: ["read", "list", "write"],
|
|
121
|
-
collections: ["profile", "devices", "spaces", "
|
|
115
|
+
collections: ["profile", "devices", "spaces", "spaceregistry"],
|
|
122
116
|
paths: [
|
|
123
117
|
`user/${userId}/profile`,
|
|
124
118
|
`users/${userId}/_devices`,
|
|
@@ -130,7 +124,7 @@ function accountScope(userId) {
|
|
|
130
124
|
function linkedDeviceScope(userId) {
|
|
131
125
|
return {
|
|
132
126
|
ops: ["read", "list", "write"],
|
|
133
|
-
collections: [...OBJECT_COLLECTIONS, "profile", "devices", "spaces", "
|
|
127
|
+
collections: [...OBJECT_COLLECTIONS, "profile", "devices", "spaces", "spaceregistry"],
|
|
134
128
|
paths: [
|
|
135
129
|
"spaces/**",
|
|
136
130
|
`user/${userId}/profile`,
|
|
@@ -150,14 +144,62 @@ async function userIdFromEdPub(edPubHex) {
|
|
|
150
144
|
const digest = await globalThis.crypto.subtle.digest("SHA-256", bytes);
|
|
151
145
|
return bytesToHex(new Uint8Array(digest)).slice(0, 32);
|
|
152
146
|
}
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
147
|
+
var pull, push, spaceIdFromRoomId, keyringName, keyringPull, keyringPush, attachmentName, attachmentPull, attachmentPush, profilePull, profilePush, spacesPull, spacesPush, spaceAccessPull, spaceAccessPush, objIndexName, objIndexPull, objIndexPush, objLogName, objLogPull, objLogPush, objDocName, objDocPull, objDocPush, objectBlobName, objectBlobPull, objectBlobPush, objPubName, objPubPull, objPubPush, objInvName, objInvPull, objInvPush, typesIndexName, typesIndexPull, typesIndexPush, objectDirName, objectDirPull, OBJECT_COLLECTIONS;
|
|
148
|
+
var init_paths = __esm({
|
|
149
|
+
"src/sync/paths.ts"() {
|
|
150
|
+
"use strict";
|
|
151
|
+
pull = (rest) => `/pull/${rest}`;
|
|
152
|
+
push = (rest) => `/push/${rest}`;
|
|
153
|
+
spaceIdFromRoomId = (roomId) => roomId.split("-").slice(0, 2).join("-");
|
|
154
|
+
keyringName = (spaceId) => `spaces/${spaceId}`;
|
|
155
|
+
keyringPull = (spaceId) => pull(`${keyringName(spaceId)}/_keyring`);
|
|
156
|
+
keyringPush = (spaceId) => push(`${keyringName(spaceId)}/_keyring`);
|
|
157
|
+
attachmentName = (roomId, blobId) => `spaces/${spaceIdFromRoomId(roomId)}/attachments/${roomId}/${blobId}`;
|
|
158
|
+
attachmentPull = (roomId, blobId) => pull(attachmentName(roomId, blobId));
|
|
159
|
+
attachmentPush = (roomId, blobId) => push(attachmentName(roomId, blobId));
|
|
160
|
+
profilePull = (userId) => pull(`user/${userId}/profile`);
|
|
161
|
+
profilePush = (userId) => push(`user/${userId}/profile`);
|
|
162
|
+
spacesPull = (userId) => pull(`user/${userId}/_spaces`);
|
|
163
|
+
spacesPush = (userId) => push(`user/${userId}/_spaces`);
|
|
164
|
+
spaceAccessPull = (spaceId) => pull(`spaces/${spaceId}/_access`);
|
|
165
|
+
spaceAccessPush = (spaceId) => push(`spaces/${spaceId}/_access`);
|
|
166
|
+
objIndexName = (spaceId) => `spaces/${spaceId}/objects/_index`;
|
|
167
|
+
objIndexPull = (spaceId) => pull(objIndexName(spaceId));
|
|
168
|
+
objIndexPush = (spaceId) => push(objIndexName(spaceId));
|
|
169
|
+
objLogName = (spaceId, objectId) => `spaces/${spaceId}/objects/logs/${objectId}`;
|
|
170
|
+
objLogPull = (spaceId, objectId) => pull(objLogName(spaceId, objectId));
|
|
171
|
+
objLogPush = (spaceId, objectId) => push(objLogName(spaceId, objectId));
|
|
172
|
+
objDocName = (spaceId, objectId) => `spaces/${spaceId}/objects/docs/${objectId}`;
|
|
173
|
+
objDocPull = (spaceId, objectId) => pull(objDocName(spaceId, objectId));
|
|
174
|
+
objDocPush = (spaceId, objectId) => push(objDocName(spaceId, objectId));
|
|
175
|
+
objectBlobName = (spaceId, blobId) => `spaces/${spaceId}/objects/blobs/${blobId}`;
|
|
176
|
+
objectBlobPull = (spaceId, blobId) => pull(objectBlobName(spaceId, blobId));
|
|
177
|
+
objectBlobPush = (spaceId, blobId) => push(objectBlobName(spaceId, blobId));
|
|
178
|
+
objPubName = (spaceId, nodeId) => `spaces/${spaceId}/objects/pub/${nodeId}`;
|
|
179
|
+
objPubPull = (spaceId, nodeId) => pull(objPubName(spaceId, nodeId));
|
|
180
|
+
objPubPush = (spaceId, nodeId) => push(objPubName(spaceId, nodeId));
|
|
181
|
+
objInvName = (spaceId, nodeId) => `spaces/${spaceId}/objects/n/${nodeId}/content`;
|
|
182
|
+
objInvPull = (spaceId, nodeId) => pull(objInvName(spaceId, nodeId));
|
|
183
|
+
objInvPush = (spaceId, nodeId) => push(objInvName(spaceId, nodeId));
|
|
184
|
+
typesIndexName = (spaceId) => `spaces/${spaceId}/types/_index`;
|
|
185
|
+
typesIndexPull = (spaceId) => pull(typesIndexName(spaceId));
|
|
186
|
+
typesIndexPush = (spaceId) => push(typesIndexName(spaceId));
|
|
187
|
+
objectDirName = (shard = "public") => `_index/objects/${shard}`;
|
|
188
|
+
objectDirPull = (shard = "public") => pull(objectDirName(shard));
|
|
189
|
+
OBJECT_COLLECTIONS = [
|
|
190
|
+
"spacekeyring",
|
|
191
|
+
"objindex",
|
|
192
|
+
"objlog",
|
|
193
|
+
"objsnap",
|
|
194
|
+
"objdoc",
|
|
195
|
+
"objblob",
|
|
196
|
+
"typeindex",
|
|
197
|
+
"objpub"
|
|
198
|
+
];
|
|
199
|
+
}
|
|
200
|
+
});
|
|
158
201
|
|
|
159
202
|
// src/sync/fetch-timeout.ts
|
|
160
|
-
var CONNECT_TIMEOUT_MS = 12e3;
|
|
161
203
|
function fetchWithTimeout(timeoutMs = CONNECT_TIMEOUT_MS) {
|
|
162
204
|
return (input, init) => {
|
|
163
205
|
const ctrl = new AbortController();
|
|
@@ -170,20 +212,32 @@ function fetchWithTimeout(timeoutMs = CONNECT_TIMEOUT_MS) {
|
|
|
170
212
|
return fetch(input, { ...init, signal: ctrl.signal }).finally(() => clearTimeout(timer));
|
|
171
213
|
};
|
|
172
214
|
}
|
|
215
|
+
var CONNECT_TIMEOUT_MS;
|
|
216
|
+
var init_fetch_timeout = __esm({
|
|
217
|
+
"src/sync/fetch-timeout.ts"() {
|
|
218
|
+
"use strict";
|
|
219
|
+
CONNECT_TIMEOUT_MS = 12e3;
|
|
220
|
+
}
|
|
221
|
+
});
|
|
173
222
|
|
|
174
223
|
// src/sync/pull-cache.ts
|
|
175
|
-
var PREFIX = "octospaces.pullcache.";
|
|
176
|
-
var PULL_CACHE_MAX_AGE_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
177
|
-
var shared;
|
|
178
224
|
function pullCache() {
|
|
179
225
|
return shared ?? (shared = {
|
|
180
226
|
get: (key2) => kvGet(PREFIX + key2),
|
|
181
227
|
set: (key2, value) => kvSet(PREFIX + key2, value)
|
|
182
228
|
});
|
|
183
229
|
}
|
|
230
|
+
var PREFIX, PULL_CACHE_MAX_AGE_MS, shared;
|
|
231
|
+
var init_pull_cache = __esm({
|
|
232
|
+
"src/sync/pull-cache.ts"() {
|
|
233
|
+
"use strict";
|
|
234
|
+
init_adapters();
|
|
235
|
+
PREFIX = "octospaces.pullcache.";
|
|
236
|
+
PULL_CACHE_MAX_AGE_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
237
|
+
}
|
|
238
|
+
});
|
|
184
239
|
|
|
185
240
|
// src/sync/profile-cache.ts
|
|
186
|
-
var key = (userId) => `octospaces.profile.v1.${userId}`;
|
|
187
241
|
function cacheProfile(userId, profile) {
|
|
188
242
|
void kvSet(key(userId), JSON.stringify(profile)).catch(() => {
|
|
189
243
|
});
|
|
@@ -203,16 +257,33 @@ async function loadCachedProfile(userId) {
|
|
|
203
257
|
return null;
|
|
204
258
|
}
|
|
205
259
|
}
|
|
260
|
+
var key;
|
|
261
|
+
var init_profile_cache = __esm({
|
|
262
|
+
"src/sync/profile-cache.ts"() {
|
|
263
|
+
"use strict";
|
|
264
|
+
init_adapters();
|
|
265
|
+
key = (userId) => `octospaces.profile.v1.${userId}`;
|
|
266
|
+
}
|
|
267
|
+
});
|
|
206
268
|
|
|
207
269
|
// src/core/space-access-error.ts
|
|
208
|
-
var SpaceAccessError
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
270
|
+
var SpaceAccessError;
|
|
271
|
+
var init_space_access_error = __esm({
|
|
272
|
+
"src/core/space-access-error.ts"() {
|
|
273
|
+
"use strict";
|
|
274
|
+
SpaceAccessError = class extends Error {
|
|
275
|
+
constructor(message) {
|
|
276
|
+
super(message);
|
|
277
|
+
this.name = "SpaceAccessError";
|
|
278
|
+
}
|
|
279
|
+
};
|
|
212
280
|
}
|
|
213
|
-
};
|
|
281
|
+
});
|
|
214
282
|
|
|
215
283
|
// src/sync/client.ts
|
|
284
|
+
import { StarfishClient } from "@drakkar.software/starfish-client";
|
|
285
|
+
import { createKeyring, createKeyringEncryptor } from "@drakkar.software/starfish-keyring";
|
|
286
|
+
import { signRequest, stableStringify } from "@drakkar.software/starfish-protocol";
|
|
216
287
|
function capProviderFor(cap, devEdPrivHex) {
|
|
217
288
|
return {
|
|
218
289
|
async getCap() {
|
|
@@ -232,13 +303,13 @@ function makeClient(cap, devEdPrivHex, namespaceOverride) {
|
|
|
232
303
|
onRevalidated: () => getOnServerReachable()?.()
|
|
233
304
|
});
|
|
234
305
|
}
|
|
235
|
-
async function openEncryptor(client, keys,
|
|
236
|
-
const res = await client.pull(
|
|
237
|
-
throw new Error("Could not reach the server to fetch
|
|
306
|
+
async function openEncryptor(client, keys, keyringPullPath, trustedAdders) {
|
|
307
|
+
const res = await client.pull(keyringPullPath).catch(() => {
|
|
308
|
+
throw new Error("Could not reach the server to fetch node keys.");
|
|
238
309
|
});
|
|
239
310
|
const keyring = res?.data;
|
|
240
311
|
if (!keyring || !keyring.epochs) {
|
|
241
|
-
throw new SpaceAccessError("This
|
|
312
|
+
throw new SpaceAccessError("This node has no keyring yet \u2014 ask the owner to create it first.");
|
|
242
313
|
}
|
|
243
314
|
try {
|
|
244
315
|
const enc = await createKeyringEncryptor(
|
|
@@ -248,25 +319,25 @@ async function openEncryptor(client, keys, spaceId, trustedAdders) {
|
|
|
248
319
|
);
|
|
249
320
|
return enc;
|
|
250
321
|
} catch {
|
|
251
|
-
throw new SpaceAccessError("You're not a recipient of this
|
|
322
|
+
throw new SpaceAccessError("You're not a recipient of this node's keyring yet \u2014 ask the owner to invite you.");
|
|
252
323
|
}
|
|
253
324
|
}
|
|
254
|
-
async function buildEncryptor(client, keys,
|
|
325
|
+
async function buildEncryptor(client, keys, keyringPullPath, trustedAdders) {
|
|
255
326
|
try {
|
|
256
|
-
return await openEncryptor(client, keys,
|
|
327
|
+
return await openEncryptor(client, keys, keyringPullPath, trustedAdders);
|
|
257
328
|
} catch {
|
|
258
329
|
return null;
|
|
259
330
|
}
|
|
260
331
|
}
|
|
261
|
-
async function ownerEnsureKeyring(client, keys,
|
|
262
|
-
const krRes = await client.pull(
|
|
332
|
+
async function ownerEnsureKeyring(client, keys, keyringPullPath, keyringPushPath, trustedAdders = [keys.edPub]) {
|
|
333
|
+
const krRes = await client.pull(keyringPullPath).catch(() => null);
|
|
263
334
|
let keyring = krRes?.data;
|
|
264
335
|
if (!keyring || !keyring.epochs) {
|
|
265
336
|
const created = await createKeyring({ edPrivHex: keys.edPriv, edPubHex: keys.edPub }, [
|
|
266
337
|
{ subKemHex: keys.kemPub }
|
|
267
338
|
]);
|
|
268
339
|
keyring = created.keyring;
|
|
269
|
-
await client.push(
|
|
340
|
+
await client.push(keyringPushPath, keyring, krRes?.hash ?? null);
|
|
270
341
|
}
|
|
271
342
|
const enc = await createKeyringEncryptor(
|
|
272
343
|
keyring,
|
|
@@ -296,14 +367,12 @@ async function readProfile(userId) {
|
|
|
296
367
|
async function readPseudo(userId) {
|
|
297
368
|
return (await readProfile(userId)).pseudo;
|
|
298
369
|
}
|
|
299
|
-
var profileBatchClient;
|
|
300
370
|
function getProfileBatchClient() {
|
|
301
371
|
if (!profileBatchClient) {
|
|
302
372
|
profileBatchClient = new StarfishClient({ baseUrl: getSyncBase(), namespace: getSyncNamespace(), fetch: fetchWithTimeout() });
|
|
303
373
|
}
|
|
304
374
|
return profileBatchClient;
|
|
305
375
|
}
|
|
306
|
-
var PROFILE_BATCH_CHUNK = 24;
|
|
307
376
|
async function readProfiles(ids) {
|
|
308
377
|
const out = /* @__PURE__ */ new Map();
|
|
309
378
|
const client = getProfileBatchClient();
|
|
@@ -399,6 +468,19 @@ async function ensurePseudo(client, userId, fallback) {
|
|
|
399
468
|
await writeProfile(client, userId, { pseudo: fallback });
|
|
400
469
|
return fallback;
|
|
401
470
|
}
|
|
471
|
+
var profileBatchClient, PROFILE_BATCH_CHUNK;
|
|
472
|
+
var init_client = __esm({
|
|
473
|
+
"src/sync/client.ts"() {
|
|
474
|
+
"use strict";
|
|
475
|
+
init_config();
|
|
476
|
+
init_fetch_timeout();
|
|
477
|
+
init_pull_cache();
|
|
478
|
+
init_profile_cache();
|
|
479
|
+
init_paths();
|
|
480
|
+
init_space_access_error();
|
|
481
|
+
PROFILE_BATCH_CHUNK = 24;
|
|
482
|
+
}
|
|
483
|
+
});
|
|
402
484
|
|
|
403
485
|
// src/sync/identity.ts
|
|
404
486
|
import { generateMnemonic, validateMnemonic } from "@scure/bip39";
|
|
@@ -474,62 +556,16 @@ async function deriveSession(seedWords, name) {
|
|
|
474
556
|
function rootIdentityOf(s) {
|
|
475
557
|
return { userId: s.userId, keys: s.keys };
|
|
476
558
|
}
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
} from "@drakkar.software/starfish-keyring";
|
|
486
|
-
var SELF_EPOCH = 0;
|
|
487
|
-
var subtle = () => globalThis.crypto.subtle;
|
|
488
|
-
async function seal(session, recipientKemPub, plaintext) {
|
|
489
|
-
const cek = globalThis.crypto.getRandomValues(new Uint8Array(32));
|
|
490
|
-
const entry = await wrapForRecipient(cek, recipientKemPub, {
|
|
491
|
-
adderEdPrivHex: session.keys.edPriv,
|
|
492
|
-
adderEdPubHex: session.keys.edPub,
|
|
493
|
-
addedAt: Math.floor(Date.now() / 1e3),
|
|
494
|
-
epoch: SELF_EPOCH
|
|
495
|
-
});
|
|
496
|
-
const iv = globalThis.crypto.getRandomValues(new Uint8Array(12));
|
|
497
|
-
const key2 = await subtle().importKey("raw", cek, { name: "AES-GCM" }, false, ["encrypt"]);
|
|
498
|
-
const ctBuf = await subtle().encrypt({ name: "AES-GCM", iv }, key2, new TextEncoder().encode(plaintext));
|
|
499
|
-
const packed = new Uint8Array(iv.length + ctBuf.byteLength);
|
|
500
|
-
packed.set(iv, 0);
|
|
501
|
-
packed.set(new Uint8Array(ctBuf), iv.length);
|
|
502
|
-
return { entry, ct: bytesToHex2(packed) };
|
|
503
|
-
}
|
|
504
|
-
async function open(session, blob) {
|
|
505
|
-
const cek = await unwrapFromEntry(blob.entry, session.keys.kemPriv);
|
|
506
|
-
const packed = hexToBytes(blob.ct);
|
|
507
|
-
const iv = new Uint8Array(packed.subarray(0, 12));
|
|
508
|
-
const ctBytes = new Uint8Array(packed.subarray(12));
|
|
509
|
-
const key2 = await subtle().importKey("raw", new Uint8Array(cek), { name: "AES-GCM" }, false, ["decrypt"]);
|
|
510
|
-
const out = await subtle().decrypt({ name: "AES-GCM", iv }, key2, ctBytes);
|
|
511
|
-
return new TextDecoder().decode(out);
|
|
512
|
-
}
|
|
513
|
-
function sealToSelf(session, plaintext) {
|
|
514
|
-
return seal(session, session.keys.kemPub, plaintext);
|
|
515
|
-
}
|
|
516
|
-
async function unsealFromSelf(session, blob) {
|
|
517
|
-
if (blob.entry.addedBy !== session.keys.edPub) throw new Error("sealed blob not self-signed");
|
|
518
|
-
if (!await verifyEntrySignature(blob.entry, SELF_EPOCH)) throw new Error("sealed blob signature invalid");
|
|
519
|
-
return open(session, blob);
|
|
520
|
-
}
|
|
521
|
-
function sealToRecipient(session, recipientKemPub, plaintext) {
|
|
522
|
-
return seal(session, recipientKemPub, plaintext);
|
|
523
|
-
}
|
|
524
|
-
async function unsealFromRecipient(session, blob) {
|
|
525
|
-
if (!await verifyEntrySignature(blob.entry, SELF_EPOCH)) throw new Error("sealed blob signature invalid");
|
|
526
|
-
return open(session, blob);
|
|
527
|
-
}
|
|
559
|
+
var init_identity = __esm({
|
|
560
|
+
"src/sync/identity.ts"() {
|
|
561
|
+
"use strict";
|
|
562
|
+
init_client();
|
|
563
|
+
init_paths();
|
|
564
|
+
init_config();
|
|
565
|
+
}
|
|
566
|
+
});
|
|
528
567
|
|
|
529
568
|
// src/sync/space-access-store.ts
|
|
530
|
-
var keyFor = (userId) => `octospaces.spaceaccess.${userId}`;
|
|
531
|
-
var cache = {};
|
|
532
|
-
var activeKey = null;
|
|
533
569
|
async function hydrateSpaceAccessStore(userId, serverCaps, serverLinkAccess) {
|
|
534
570
|
const key2 = keyFor(userId);
|
|
535
571
|
if (activeKey === key2) return;
|
|
@@ -572,6 +608,15 @@ function removeSpaceAccessEntry(spaceId) {
|
|
|
572
608
|
cache = next;
|
|
573
609
|
persist();
|
|
574
610
|
}
|
|
611
|
+
function getNodeAccessEntry(spaceId, nodeId) {
|
|
612
|
+
return cache[`${spaceId}:${nodeId}`] ?? null;
|
|
613
|
+
}
|
|
614
|
+
function saveNodeAccessEntry(spaceId, nodeId, entry) {
|
|
615
|
+
saveSpaceAccessEntry(`${spaceId}:${nodeId}`, entry);
|
|
616
|
+
}
|
|
617
|
+
function removeNodeAccessEntry(spaceId, nodeId) {
|
|
618
|
+
removeSpaceAccessEntry(`${spaceId}:${nodeId}`);
|
|
619
|
+
}
|
|
575
620
|
function localSpaceAccessEntries() {
|
|
576
621
|
return cache;
|
|
577
622
|
}
|
|
@@ -591,310 +636,153 @@ function clearSpaceAccessStore() {
|
|
|
591
636
|
cache = {};
|
|
592
637
|
activeKey = null;
|
|
593
638
|
}
|
|
639
|
+
var keyFor, cache, activeKey;
|
|
640
|
+
var init_space_access_store = __esm({
|
|
641
|
+
"src/sync/space-access-store.ts"() {
|
|
642
|
+
"use strict";
|
|
643
|
+
init_adapters();
|
|
644
|
+
keyFor = (userId) => `octospaces.spaceaccess.${userId}`;
|
|
645
|
+
cache = {};
|
|
646
|
+
activeKey = null;
|
|
647
|
+
}
|
|
648
|
+
});
|
|
594
649
|
|
|
595
650
|
// src/sync/space-access.ts
|
|
596
|
-
|
|
597
|
-
function clearSpaceAccessCache() {
|
|
651
|
+
function clearNodeAccessCache() {
|
|
598
652
|
cache2.clear();
|
|
599
653
|
}
|
|
600
|
-
function
|
|
601
|
-
const
|
|
654
|
+
function getSpaceClient(spaceId, session) {
|
|
655
|
+
const entry = getSpaceAccessEntry(spaceId);
|
|
656
|
+
if (entry?.kind === "link") return makeClient(entry.cap, entry.key);
|
|
657
|
+
if (entry?.kind === "member") {
|
|
658
|
+
const cap = JSON.parse(entry.cap);
|
|
659
|
+
return makeClient(cap, session.keys.edPriv);
|
|
660
|
+
}
|
|
661
|
+
return session.chatClient;
|
|
662
|
+
}
|
|
663
|
+
function getNodeAccess(spaceId, nodeId, node, session, reg) {
|
|
664
|
+
const cacheKey = `${spaceId}:${nodeId}`;
|
|
665
|
+
const hit = cache2.get(cacheKey);
|
|
602
666
|
if (hit) return hit;
|
|
603
667
|
const p = (async () => {
|
|
604
|
-
const
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
668
|
+
const nodeEntry = getNodeAccessEntry(spaceId, nodeId);
|
|
669
|
+
const spaceEntry = getSpaceAccessEntry(spaceId);
|
|
670
|
+
const activeEntry = nodeEntry ?? spaceEntry;
|
|
671
|
+
let client;
|
|
672
|
+
let capIss;
|
|
673
|
+
if (activeEntry?.kind === "link") {
|
|
674
|
+
client = makeClient(activeEntry.cap, activeEntry.key);
|
|
675
|
+
} else if (activeEntry?.kind === "member") {
|
|
676
|
+
const cap = JSON.parse(activeEntry.cap);
|
|
677
|
+
capIss = cap.iss;
|
|
678
|
+
client = makeClient(cap, session.keys.edPriv);
|
|
679
|
+
} else {
|
|
680
|
+
client = session.chatClient;
|
|
609
681
|
}
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
const encryptor2 = await openEncryptor(client, session.keys, spaceId, cap.iss ? [cap.iss] : []);
|
|
614
|
-
return { encryptor: encryptor2, client, isOwnerOpen: false };
|
|
682
|
+
const isOwnerOpen = reg != null ? reg.owner === session.userId : activeEntry == null;
|
|
683
|
+
if (!node.enc) {
|
|
684
|
+
return { encryptor: null, client, isOwnerOpen };
|
|
615
685
|
}
|
|
616
|
-
const
|
|
617
|
-
|
|
618
|
-
|
|
686
|
+
const spacePullPath = keyringPull(spaceId);
|
|
687
|
+
const trustedAdders = capIss ? [capIss] : reg?.owner ? [reg.owner] : ownerTrustedAdders(session);
|
|
688
|
+
if (activeEntry?.kind === "member" || activeEntry?.kind === "link") {
|
|
689
|
+
const encryptor2 = await openEncryptor(client, session.keys, spacePullPath, trustedAdders);
|
|
690
|
+
return { encryptor: encryptor2, client, isOwnerOpen: false };
|
|
619
691
|
}
|
|
620
692
|
const owner = reg?.owner ?? null;
|
|
621
693
|
const members = reg?.members ?? [];
|
|
622
694
|
if (owner !== null && owner !== session.userId) {
|
|
623
695
|
throw new SpaceAccessError(
|
|
624
|
-
members.includes(session.userId) ? "You're a member of this space, but
|
|
696
|
+
members.includes(session.userId) ? "You're a member of this space, but the space key isn't on this device yet \u2014 ask the owner to invite you." : "You don't have access to this node."
|
|
625
697
|
);
|
|
626
698
|
}
|
|
627
699
|
const encryptor = await ownerEnsureKeyring(
|
|
628
700
|
session.chatClient,
|
|
629
701
|
session.keys,
|
|
630
|
-
|
|
702
|
+
spacePullPath,
|
|
703
|
+
keyringPush(spaceId),
|
|
631
704
|
ownerTrustedAdders(session)
|
|
632
705
|
);
|
|
633
706
|
return { encryptor, client: session.chatClient, isOwnerOpen: true };
|
|
634
707
|
})();
|
|
635
|
-
cache2.set(
|
|
636
|
-
p.catch(() => cache2.delete(
|
|
708
|
+
cache2.set(cacheKey, p);
|
|
709
|
+
p.catch(() => cache2.delete(cacheKey));
|
|
637
710
|
return p;
|
|
638
711
|
}
|
|
639
|
-
async function
|
|
640
|
-
const
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
}
|
|
645
|
-
let client = session.chatClient;
|
|
712
|
+
async function buildNodeAccess(session, spaceId, nodeId, node) {
|
|
713
|
+
const nodeEntry = getNodeAccessEntry(spaceId, nodeId);
|
|
714
|
+
const spaceEntry = getSpaceAccessEntry(spaceId);
|
|
715
|
+
const activeEntry = nodeEntry ?? spaceEntry;
|
|
716
|
+
let client;
|
|
646
717
|
let trustedAdders = ownerTrustedAdders(session);
|
|
647
|
-
if (
|
|
648
|
-
|
|
718
|
+
if (activeEntry?.kind === "link") {
|
|
719
|
+
client = makeClient(activeEntry.cap, activeEntry.key);
|
|
720
|
+
} else if (activeEntry?.kind === "member") {
|
|
721
|
+
const cap = JSON.parse(activeEntry.cap);
|
|
649
722
|
client = makeClient(cap, session.keys.edPriv);
|
|
650
723
|
if (cap.iss) trustedAdders = [cap.iss];
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
}
|
|
654
|
-
if (hint?.visibility === "public") {
|
|
655
|
-
return { client, encryptor: null };
|
|
724
|
+
} else {
|
|
725
|
+
client = session.chatClient;
|
|
656
726
|
}
|
|
657
|
-
|
|
727
|
+
if (!node.enc) return { client, encryptor: null };
|
|
728
|
+
const spacePullPath = keyringPull(spaceId);
|
|
729
|
+
const encryptor = await buildEncryptor(client, session.keys, spacePullPath, trustedAdders);
|
|
658
730
|
return encryptor ? { client, encryptor } : null;
|
|
659
731
|
}
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
return "dm";
|
|
671
|
-
case "automated":
|
|
672
|
-
return "automation";
|
|
673
|
-
default:
|
|
674
|
-
return "channel";
|
|
675
|
-
}
|
|
676
|
-
}
|
|
677
|
-
function subtypeToRoomKind(subtype) {
|
|
678
|
-
switch (subtype) {
|
|
679
|
-
case "dm":
|
|
680
|
-
return "dm";
|
|
681
|
-
case "automation":
|
|
682
|
-
return "automated";
|
|
683
|
-
default:
|
|
684
|
-
return "channel";
|
|
685
|
-
}
|
|
686
|
-
}
|
|
687
|
-
function compareSiblings(a, b) {
|
|
688
|
-
if (a.order !== b.order) return a.order - b.order;
|
|
689
|
-
return a.id < b.id ? -1 : a.id > b.id ? 1 : 0;
|
|
690
|
-
}
|
|
691
|
-
function nextOrder(siblings) {
|
|
692
|
-
let max = 0;
|
|
693
|
-
for (const s of siblings) if (s.order > max) max = s.order;
|
|
694
|
-
return max + 1;
|
|
695
|
-
}
|
|
696
|
-
function buildTree(nodes, includeArchived = false) {
|
|
697
|
-
const live = includeArchived ? nodes : nodes.filter((n) => !n.archived);
|
|
698
|
-
const byId = new Map(live.map((n) => [n.id, n]));
|
|
699
|
-
const effectiveParent = (n) => {
|
|
700
|
-
if (n.parentId == null) return null;
|
|
701
|
-
if (!byId.has(n.parentId)) return null;
|
|
702
|
-
const seen = /* @__PURE__ */ new Set([n.id]);
|
|
703
|
-
let cur = n.parentId;
|
|
704
|
-
while (cur != null) {
|
|
705
|
-
if (seen.has(cur)) return null;
|
|
706
|
-
seen.add(cur);
|
|
707
|
-
const parent = byId.get(cur);
|
|
708
|
-
if (!parent) return null;
|
|
709
|
-
cur = parent.parentId;
|
|
710
|
-
}
|
|
711
|
-
return n.parentId;
|
|
712
|
-
};
|
|
713
|
-
const childrenOf = /* @__PURE__ */ new Map();
|
|
714
|
-
for (const n of live) {
|
|
715
|
-
const p = effectiveParent(n);
|
|
716
|
-
const bucket = childrenOf.get(p) ?? [];
|
|
717
|
-
bucket.push(n);
|
|
718
|
-
childrenOf.set(p, bucket);
|
|
719
|
-
}
|
|
720
|
-
function attach(parent, depth) {
|
|
721
|
-
return (childrenOf.get(parent) ?? []).slice().sort(compareSiblings).map((n) => ({ ...n, depth, children: attach(n.id, depth + 1) }));
|
|
722
|
-
}
|
|
723
|
-
return attach(null, 0);
|
|
724
|
-
}
|
|
725
|
-
function breadcrumbs(nodes, id) {
|
|
726
|
-
const byId = new Map(nodes.map((n) => [n.id, n]));
|
|
727
|
-
const trail = [];
|
|
728
|
-
const seen = /* @__PURE__ */ new Set();
|
|
729
|
-
let cur = id;
|
|
730
|
-
while (cur != null && byId.has(cur) && !seen.has(cur)) {
|
|
731
|
-
seen.add(cur);
|
|
732
|
-
const node = byId.get(cur);
|
|
733
|
-
trail.unshift(node);
|
|
734
|
-
cur = node.parentId;
|
|
735
|
-
}
|
|
736
|
-
return trail;
|
|
737
|
-
}
|
|
738
|
-
function ancestors(nodes, id) {
|
|
739
|
-
return breadcrumbs(nodes, id).slice(0, -1);
|
|
740
|
-
}
|
|
741
|
-
function subtreeIds(nodes, rootId) {
|
|
742
|
-
const childrenOf = /* @__PURE__ */ new Map();
|
|
743
|
-
for (const n of nodes) {
|
|
744
|
-
const bucket = childrenOf.get(n.parentId) ?? [];
|
|
745
|
-
bucket.push(n.id);
|
|
746
|
-
childrenOf.set(n.parentId, bucket);
|
|
747
|
-
}
|
|
748
|
-
const out = /* @__PURE__ */ new Set();
|
|
749
|
-
const walk = (id) => {
|
|
750
|
-
if (out.has(id)) return;
|
|
751
|
-
out.add(id);
|
|
752
|
-
for (const child of childrenOf.get(id) ?? []) walk(child);
|
|
753
|
-
};
|
|
754
|
-
walk(rootId);
|
|
755
|
-
return out;
|
|
756
|
-
}
|
|
757
|
-
function addObject(nodes, input, now) {
|
|
758
|
-
const parentId = input.parentId ?? null;
|
|
759
|
-
const siblings = nodes.filter((n) => n.parentId === parentId);
|
|
760
|
-
const node = {
|
|
761
|
-
id: input.id ?? `obj-${randomId()}`,
|
|
762
|
-
type: input.type,
|
|
763
|
-
...input.subtype ? { subtype: input.subtype } : {},
|
|
764
|
-
parentId,
|
|
765
|
-
order: nextOrder(siblings),
|
|
766
|
-
title: input.title,
|
|
767
|
-
...input.emoji ? { emoji: input.emoji } : {},
|
|
768
|
-
updatedAt: now,
|
|
769
|
-
...input.automation ? { automation: input.automation } : {}
|
|
770
|
-
};
|
|
771
|
-
return { nodes: [...nodes, node], node };
|
|
772
|
-
}
|
|
773
|
-
function patchObject(nodes, id, patch, now) {
|
|
774
|
-
return nodes.map((n) => n.id === id ? { ...n, ...patch, updatedAt: now } : n);
|
|
775
|
-
}
|
|
776
|
-
function reparentObject(nodes, id, parentId, now) {
|
|
777
|
-
if (id === parentId) return nodes;
|
|
778
|
-
if (parentId != null && subtreeIds(nodes, id).has(parentId)) return nodes;
|
|
779
|
-
const siblings = nodes.filter((n) => n.parentId === parentId && n.id !== id);
|
|
780
|
-
return nodes.map((n) => n.id === id ? { ...n, parentId, order: nextOrder(siblings), updatedAt: now } : n);
|
|
781
|
-
}
|
|
782
|
-
function reorderObjects(nodes, orderById, now) {
|
|
783
|
-
return nodes.map((n) => n.id in orderById ? { ...n, order: orderById[n.id], updatedAt: now } : n);
|
|
784
|
-
}
|
|
785
|
-
function archiveObject(nodes, id, now) {
|
|
786
|
-
const ids = subtreeIds(nodes, id);
|
|
787
|
-
return nodes.map((n) => ids.has(n.id) ? { ...n, archived: true, updatedAt: now } : n);
|
|
788
|
-
}
|
|
789
|
-
function objectsToRoomCategories(nodes, spaceId, fallbackCategory) {
|
|
790
|
-
const live = nodes.filter((n) => !n.archived);
|
|
791
|
-
const cats = live.filter((n) => n.type === "category").slice().sort(compareSiblings);
|
|
792
|
-
const rooms = live.filter((n) => n.type === "room");
|
|
793
|
-
if (cats.length === 0 && rooms.length === 0) return null;
|
|
794
|
-
const titleById = new Map(cats.map((c) => [c.id, c.title]));
|
|
795
|
-
const buckets = /* @__PURE__ */ new Map();
|
|
796
|
-
for (const c of cats) buckets.set(c.title, []);
|
|
797
|
-
const toRoom = (n, category) => ({
|
|
798
|
-
id: n.id,
|
|
799
|
-
spaceId,
|
|
800
|
-
category,
|
|
801
|
-
name: n.title,
|
|
802
|
-
kind: subtypeToRoomKind(n.subtype),
|
|
803
|
-
...n.automation ? { automation: n.automation } : {}
|
|
804
|
-
});
|
|
805
|
-
for (const n of rooms.slice().sort(compareSiblings)) {
|
|
806
|
-
const category = n.parentId != null && titleById.get(n.parentId) || fallbackCategory;
|
|
807
|
-
if (!buckets.has(category)) buckets.set(category, []);
|
|
808
|
-
buckets.get(category).push(toRoom(n, category));
|
|
809
|
-
}
|
|
810
|
-
return [...buckets.entries()].map(([name, rs]) => ({ name, rooms: rs }));
|
|
811
|
-
}
|
|
812
|
-
function excludeAutomatedRooms(categories) {
|
|
813
|
-
return categories.map((c) => ({ ...c, rooms: c.rooms.filter((r) => r.kind !== "automated") })).filter((c, i) => c.rooms.length > 0 || categories[i].rooms.length === 0);
|
|
814
|
-
}
|
|
815
|
-
function seedIndexNodes(rooms, now) {
|
|
816
|
-
const out = [];
|
|
817
|
-
const catId = /* @__PURE__ */ new Map();
|
|
818
|
-
let catOrder = 0;
|
|
819
|
-
for (const r of rooms) {
|
|
820
|
-
if (catId.has(r.category)) continue;
|
|
821
|
-
const id = categoryId(r.category);
|
|
822
|
-
catId.set(r.category, id);
|
|
823
|
-
out.push({ id, type: "category", parentId: null, order: catOrder++, title: r.category, updatedAt: now });
|
|
824
|
-
}
|
|
825
|
-
const orderInCat = /* @__PURE__ */ new Map();
|
|
826
|
-
for (const r of rooms) {
|
|
827
|
-
const parentId = catId.get(r.category);
|
|
828
|
-
const order = (orderInCat.get(parentId) ?? 0) + 1;
|
|
829
|
-
orderInCat.set(parentId, order);
|
|
830
|
-
out.push({ id: r.id, type: "room", subtype: roomKindToSubtype(r.kind), parentId, order, title: r.name, updatedAt: now });
|
|
732
|
+
var cache2;
|
|
733
|
+
var init_space_access = __esm({
|
|
734
|
+
"src/sync/space-access.ts"() {
|
|
735
|
+
"use strict";
|
|
736
|
+
init_client();
|
|
737
|
+
init_identity();
|
|
738
|
+
init_space_access_store();
|
|
739
|
+
init_space_access_error();
|
|
740
|
+
init_paths();
|
|
741
|
+
cache2 = /* @__PURE__ */ new Map();
|
|
831
742
|
}
|
|
832
|
-
|
|
833
|
-
}
|
|
743
|
+
});
|
|
834
744
|
|
|
835
745
|
// src/spaces/object-index.ts
|
|
836
746
|
import { ConflictError } from "@drakkar.software/starfish-client";
|
|
837
|
-
function
|
|
838
|
-
|
|
839
|
-
}
|
|
840
|
-
|
|
841
|
-
try {
|
|
842
|
-
const res = await client.pull(indexPath).catch(() => null);
|
|
843
|
-
if (!res?.data) return null;
|
|
844
|
-
const plain = encryptor ? await encryptor.decrypt(res.data) : res.data;
|
|
845
|
-
const cats = objectsToRoomCategories(indexNodes(plain), spaceId, DEFAULT_CATEGORY);
|
|
846
|
-
if (!cats) return null;
|
|
847
|
-
return { rooms: cats.flatMap((c) => c.rooms), categories: cats.map((c) => c.name) };
|
|
848
|
-
} catch {
|
|
849
|
-
return null;
|
|
850
|
-
}
|
|
851
|
-
}
|
|
852
|
-
async function readSpaceIndexRooms(session, spaceId, reg) {
|
|
853
|
-
if (reg.owner === null && reg.visibility !== "public") return null;
|
|
854
|
-
try {
|
|
855
|
-
const { encryptor, client } = await getSpaceAccess(spaceId, session, reg);
|
|
856
|
-
return await readIndexRooms(client, encryptor, objIndexPull(spaceId), spaceId);
|
|
857
|
-
} catch {
|
|
858
|
-
return null;
|
|
747
|
+
function serializeForIndex(node) {
|
|
748
|
+
if (node.access === "invite") {
|
|
749
|
+
const { emoji: _e, ...rest } = node;
|
|
750
|
+
return { ...rest, title: "" };
|
|
859
751
|
}
|
|
752
|
+
return node;
|
|
860
753
|
}
|
|
861
|
-
async function
|
|
862
|
-
const access = await buildSpaceAccess(session, spaceId, hint).catch(() => null);
|
|
863
|
-
if (!access) return [];
|
|
864
|
-
const idx = await readIndexRooms(access.client, access.encryptor, objIndexPull(spaceId), spaceId);
|
|
865
|
-
return idx?.rooms ?? [];
|
|
866
|
-
}
|
|
867
|
-
async function pushIndexSeed(client, encryptor, spaceId, rooms) {
|
|
754
|
+
async function pushIndexSeed(client, spaceId, nodes = []) {
|
|
868
755
|
const res = await client.pull(objIndexPull(spaceId)).catch(() => null);
|
|
869
756
|
const existing = res?.data;
|
|
870
|
-
if (
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
});
|
|
881
|
-
await pushIndexSeed(client, encryptor, spaceId, rooms);
|
|
757
|
+
if (Array.isArray(existing?.objects)) return;
|
|
758
|
+
await client.push(
|
|
759
|
+
objIndexPush(spaceId),
|
|
760
|
+
{ v: 2, objects: nodes.map(serializeForIndex), updatedAt: Date.now() },
|
|
761
|
+
res?.hash ?? null
|
|
762
|
+
);
|
|
763
|
+
}
|
|
764
|
+
async function seedSpaceObjectIndex(session, spaceId, nodes = []) {
|
|
765
|
+
const client = getSpaceClient(spaceId, session);
|
|
766
|
+
await pushIndexSeed(client, spaceId, nodes);
|
|
882
767
|
}
|
|
883
768
|
async function updateObjectIndex(session, spaceId, mutator, reg) {
|
|
884
|
-
|
|
769
|
+
void reg;
|
|
770
|
+
const client = getSpaceClient(spaceId, session);
|
|
885
771
|
const pullPath = objIndexPull(spaceId);
|
|
886
772
|
const pushPath = objIndexPush(spaceId);
|
|
887
773
|
const MAX_ATTEMPTS = 3;
|
|
888
774
|
for (let attempt = 0; attempt < MAX_ATTEMPTS; attempt++) {
|
|
889
775
|
const res = await client.pull(pullPath).catch(() => null);
|
|
890
776
|
const raw = res?.data;
|
|
891
|
-
const
|
|
892
|
-
const cur = Array.isArray(plain.objects) ? plain.objects : [];
|
|
777
|
+
const cur = Array.isArray(raw?.objects) ? raw.objects : [];
|
|
893
778
|
const next = mutator(cur, Date.now());
|
|
894
779
|
if (!next) return;
|
|
895
|
-
const payload = encryptor ? await encryptor.encrypt({ objects: next }) : { objects: next };
|
|
896
780
|
try {
|
|
897
|
-
await client.push(
|
|
781
|
+
await client.push(
|
|
782
|
+
pushPath,
|
|
783
|
+
{ v: 2, objects: next.map(serializeForIndex), updatedAt: Date.now() },
|
|
784
|
+
res?.hash ?? null
|
|
785
|
+
);
|
|
898
786
|
return;
|
|
899
787
|
} catch (err) {
|
|
900
788
|
if (err instanceof ConflictError && attempt < MAX_ATTEMPTS - 1) continue;
|
|
@@ -902,9 +790,46 @@ async function updateObjectIndex(session, spaceId, mutator, reg) {
|
|
|
902
790
|
}
|
|
903
791
|
}
|
|
904
792
|
}
|
|
793
|
+
async function readObjectTree(session, spaceId) {
|
|
794
|
+
const client = getSpaceClient(spaceId, session);
|
|
795
|
+
const res = await client.pull(objIndexPull(spaceId)).catch(() => null);
|
|
796
|
+
const raw = res?.data;
|
|
797
|
+
return Array.isArray(raw?.objects) ? raw.objects : [];
|
|
798
|
+
}
|
|
799
|
+
var init_object_index = __esm({
|
|
800
|
+
"src/spaces/object-index.ts"() {
|
|
801
|
+
"use strict";
|
|
802
|
+
init_paths();
|
|
803
|
+
init_space_access();
|
|
804
|
+
}
|
|
805
|
+
});
|
|
905
806
|
|
|
906
807
|
// src/spaces/registry.ts
|
|
907
|
-
var
|
|
808
|
+
var registry_exports = {};
|
|
809
|
+
__export(registry_exports, {
|
|
810
|
+
addJoinedSpace: () => addJoinedSpace,
|
|
811
|
+
addJoinedSpaceWithCap: () => addJoinedSpaceWithCap,
|
|
812
|
+
addJoinedSpaceWithLinkAccess: () => addJoinedSpaceWithLinkAccess,
|
|
813
|
+
addSpaceMember: () => addSpaceMember,
|
|
814
|
+
broadcastSpaceMeta: () => broadcastSpaceMeta,
|
|
815
|
+
createSpace: () => createSpace,
|
|
816
|
+
onSpaceMeta: () => onSpaceMeta,
|
|
817
|
+
readSpaceAccess: () => readSpaceAccess,
|
|
818
|
+
readSpaces: () => readSpaces,
|
|
819
|
+
reconcileSpaceMeta: () => reconcileSpaceMeta,
|
|
820
|
+
removeSpaceMember: () => removeSpaceMember,
|
|
821
|
+
reorderSpaces: () => reorderSpaces,
|
|
822
|
+
setDmMapping: () => setDmMapping,
|
|
823
|
+
updateArchivedDmsDoc: () => updateArchivedDmsDoc,
|
|
824
|
+
updateDmsDoc: () => updateDmsDoc,
|
|
825
|
+
updateMutesDoc: () => updateMutesDoc,
|
|
826
|
+
updateQuickReactionsDoc: () => updateQuickReactionsDoc,
|
|
827
|
+
updateReadsDoc: () => updateReadsDoc,
|
|
828
|
+
updateSpacesDoc: () => updateSpacesDoc,
|
|
829
|
+
writeSpaceAccess: () => writeSpaceAccess,
|
|
830
|
+
writeSpaces: () => writeSpaces
|
|
831
|
+
});
|
|
832
|
+
import { ConflictError as ConflictError2, StarfishHttpError } from "@drakkar.software/starfish-client";
|
|
908
833
|
function onSpaceMeta(fn) {
|
|
909
834
|
spaceMetaListeners.add(fn);
|
|
910
835
|
return () => {
|
|
@@ -1098,17 +1023,8 @@ async function reorderSpaces(client, userId, order) {
|
|
|
1098
1023
|
function newSpaceId() {
|
|
1099
1024
|
return `sp-${randomId()}`;
|
|
1100
1025
|
}
|
|
1101
|
-
function
|
|
1102
|
-
const
|
|
1103
|
-
for (const r of rooms) if (r.category && !distinct.includes(r.category)) distinct.push(r.category);
|
|
1104
|
-
const list = Array.isArray(stored) ? stored.filter((c) => typeof c === "string") : [];
|
|
1105
|
-
if (!list.length) return distinct;
|
|
1106
|
-
const result = [...list];
|
|
1107
|
-
for (const c of distinct) if (!result.includes(c)) result.push(c);
|
|
1108
|
-
return result;
|
|
1109
|
-
}
|
|
1110
|
-
async function readRooms(client, spaceId) {
|
|
1111
|
-
const res = await client.pull(roomsRegistryPull(spaceId)).catch((err) => {
|
|
1026
|
+
async function readSpaceAccess(client, spaceId) {
|
|
1027
|
+
const res = await client.pull(spaceAccessPull(spaceId)).catch((err) => {
|
|
1112
1028
|
if (err instanceof StarfishHttpError && err.status === 404) return null;
|
|
1113
1029
|
throw err;
|
|
1114
1030
|
});
|
|
@@ -1116,31 +1032,35 @@ async function readRooms(client, spaceId) {
|
|
|
1116
1032
|
return {
|
|
1117
1033
|
owner: typeof data?.owner === "string" ? data.owner : null,
|
|
1118
1034
|
members: Array.isArray(data?.members) ? data.members.filter((m) => typeof m === "string") : [],
|
|
1119
|
-
visibility: data?.visibility === "public" ? "public" : null,
|
|
1120
1035
|
name: typeof data?.name === "string" ? data.name : null,
|
|
1121
1036
|
image: typeof data?.image === "string" ? data.image : null,
|
|
1122
1037
|
hash: res?.hash ?? null
|
|
1123
1038
|
};
|
|
1124
1039
|
}
|
|
1125
|
-
async function
|
|
1040
|
+
async function writeSpaceAccess(client, spaceId, owner, members, hash, meta) {
|
|
1126
1041
|
const name = meta?.name?.trim() || void 0;
|
|
1127
1042
|
const image = meta?.image || void 0;
|
|
1128
|
-
const visibility = meta?.visibility === "public" ? "public" : void 0;
|
|
1129
1043
|
await client.push(
|
|
1130
|
-
|
|
1131
|
-
{
|
|
1044
|
+
spaceAccessPush(spaceId),
|
|
1045
|
+
{
|
|
1046
|
+
v: 1,
|
|
1047
|
+
owner,
|
|
1048
|
+
members,
|
|
1049
|
+
...name ? { name } : {},
|
|
1050
|
+
...image ? { image } : {}
|
|
1051
|
+
},
|
|
1132
1052
|
hash
|
|
1133
1053
|
);
|
|
1134
1054
|
}
|
|
1135
1055
|
async function addSpaceMember(client, spaceId, ownerUserId, memberUserId) {
|
|
1136
|
-
const { owner, members,
|
|
1056
|
+
const { owner, members, name, image, hash } = await readSpaceAccess(client, spaceId);
|
|
1137
1057
|
if (memberUserId === (owner ?? ownerUserId) || members.includes(memberUserId)) return;
|
|
1138
|
-
await
|
|
1058
|
+
await writeSpaceAccess(client, spaceId, owner ?? ownerUserId, [...members, memberUserId], hash, { name, image });
|
|
1139
1059
|
}
|
|
1140
1060
|
async function removeSpaceMember(client, spaceId, memberUserId) {
|
|
1141
|
-
const { owner, members,
|
|
1061
|
+
const { owner, members, name, image, hash } = await readSpaceAccess(client, spaceId);
|
|
1142
1062
|
if (!members.includes(memberUserId)) return;
|
|
1143
|
-
await
|
|
1063
|
+
await writeSpaceAccess(client, spaceId, owner ?? memberUserId, members.filter((m) => m !== memberUserId), hash, { name, image });
|
|
1144
1064
|
}
|
|
1145
1065
|
async function addJoinedSpace(client, userId, space) {
|
|
1146
1066
|
await updateSpacesDoc(
|
|
@@ -1163,26 +1083,22 @@ async function addJoinedSpaceWithLinkAccess(client, userId, space, sealed) {
|
|
|
1163
1083
|
pubAccess: { ...cur.pubAccess, [space.id]: sealed }
|
|
1164
1084
|
}));
|
|
1165
1085
|
}
|
|
1166
|
-
async function createSpace(session, name
|
|
1086
|
+
async function createSpace(session, name) {
|
|
1167
1087
|
const { accountClient, userId } = session;
|
|
1168
1088
|
const { spaces, hash } = await readSpaces(accountClient, userId);
|
|
1169
1089
|
const trimmed = name.trim() || "New Space";
|
|
1170
|
-
const visibility = opts?.visibility ?? "private";
|
|
1171
1090
|
const id = newSpaceId();
|
|
1172
1091
|
const space = {
|
|
1173
1092
|
id,
|
|
1174
1093
|
name: trimmed,
|
|
1175
1094
|
short: trimmed.slice(0, 2).toUpperCase(),
|
|
1176
|
-
members: 1
|
|
1177
|
-
...visibility === "public" ? { visibility: "public", ownerId: userId, write: true } : {}
|
|
1095
|
+
members: 1
|
|
1178
1096
|
};
|
|
1179
|
-
await
|
|
1180
|
-
await seedSpaceObjectIndex(session, id
|
|
1097
|
+
await writeSpaceAccess(accountClient, id, userId, [], null, { name: trimmed });
|
|
1098
|
+
await seedSpaceObjectIndex(session, id);
|
|
1181
1099
|
await writeSpaces(accountClient, userId, [...spaces, space], hash);
|
|
1182
1100
|
return space;
|
|
1183
1101
|
}
|
|
1184
|
-
var CategoryError = class extends Error {
|
|
1185
|
-
};
|
|
1186
1102
|
async function reconcileSpaceMeta(client, userId, spaceId, shared2, knownSpaces) {
|
|
1187
1103
|
const sharedName = typeof shared2.name === "string" && shared2.name.trim() ? shared2.name : null;
|
|
1188
1104
|
const sharedImage = typeof shared2.image === "string" && shared2.image ? shared2.image : null;
|
|
@@ -1205,8 +1121,85 @@ async function reconcileSpaceMeta(client, userId, spaceId, shared2, knownSpaces)
|
|
|
1205
1121
|
await writeSpaces(client, userId, next, hash);
|
|
1206
1122
|
broadcastSpaceMeta(spaceId, { name, short, image });
|
|
1207
1123
|
}
|
|
1124
|
+
var spaceMetaListeners;
|
|
1125
|
+
var init_registry = __esm({
|
|
1126
|
+
"src/spaces/registry.ts"() {
|
|
1127
|
+
"use strict";
|
|
1128
|
+
init_ids();
|
|
1129
|
+
init_object_index();
|
|
1130
|
+
init_paths();
|
|
1131
|
+
spaceMetaListeners = /* @__PURE__ */ new Set();
|
|
1132
|
+
}
|
|
1133
|
+
});
|
|
1134
|
+
|
|
1135
|
+
// src/index.ts
|
|
1136
|
+
init_config();
|
|
1137
|
+
init_adapters();
|
|
1138
|
+
init_ids();
|
|
1139
|
+
init_paths();
|
|
1140
|
+
init_client();
|
|
1141
|
+
init_identity();
|
|
1142
|
+
|
|
1143
|
+
// src/sync/account-seal.ts
|
|
1144
|
+
import {
|
|
1145
|
+
bytesToHex as bytesToHex2,
|
|
1146
|
+
hexToBytes,
|
|
1147
|
+
unwrapFromEntry,
|
|
1148
|
+
verifyEntrySignature,
|
|
1149
|
+
wrapForRecipient
|
|
1150
|
+
} from "@drakkar.software/starfish-keyring";
|
|
1151
|
+
var SELF_EPOCH = 0;
|
|
1152
|
+
var subtle = () => globalThis.crypto.subtle;
|
|
1153
|
+
async function seal(session, recipientKemPub, plaintext) {
|
|
1154
|
+
const cek = globalThis.crypto.getRandomValues(new Uint8Array(32));
|
|
1155
|
+
const entry = await wrapForRecipient(cek, recipientKemPub, {
|
|
1156
|
+
adderEdPrivHex: session.keys.edPriv,
|
|
1157
|
+
adderEdPubHex: session.keys.edPub,
|
|
1158
|
+
addedAt: Math.floor(Date.now() / 1e3),
|
|
1159
|
+
epoch: SELF_EPOCH
|
|
1160
|
+
});
|
|
1161
|
+
const iv = globalThis.crypto.getRandomValues(new Uint8Array(12));
|
|
1162
|
+
const key2 = await subtle().importKey("raw", cek, { name: "AES-GCM" }, false, ["encrypt"]);
|
|
1163
|
+
const ctBuf = await subtle().encrypt({ name: "AES-GCM", iv }, key2, new TextEncoder().encode(plaintext));
|
|
1164
|
+
const packed = new Uint8Array(iv.length + ctBuf.byteLength);
|
|
1165
|
+
packed.set(iv, 0);
|
|
1166
|
+
packed.set(new Uint8Array(ctBuf), iv.length);
|
|
1167
|
+
return { entry, ct: bytesToHex2(packed) };
|
|
1168
|
+
}
|
|
1169
|
+
async function open(session, blob) {
|
|
1170
|
+
const cek = await unwrapFromEntry(blob.entry, session.keys.kemPriv);
|
|
1171
|
+
const packed = hexToBytes(blob.ct);
|
|
1172
|
+
const iv = new Uint8Array(packed.subarray(0, 12));
|
|
1173
|
+
const ctBytes = new Uint8Array(packed.subarray(12));
|
|
1174
|
+
const key2 = await subtle().importKey("raw", new Uint8Array(cek), { name: "AES-GCM" }, false, ["decrypt"]);
|
|
1175
|
+
const out = await subtle().decrypt({ name: "AES-GCM", iv }, key2, ctBytes);
|
|
1176
|
+
return new TextDecoder().decode(out);
|
|
1177
|
+
}
|
|
1178
|
+
function sealToSelf(session, plaintext) {
|
|
1179
|
+
return seal(session, session.keys.kemPub, plaintext);
|
|
1180
|
+
}
|
|
1181
|
+
async function unsealFromSelf(session, blob) {
|
|
1182
|
+
if (blob.entry.addedBy !== session.keys.edPub) throw new Error("sealed blob not self-signed");
|
|
1183
|
+
if (!await verifyEntrySignature(blob.entry, SELF_EPOCH)) throw new Error("sealed blob signature invalid");
|
|
1184
|
+
return open(session, blob);
|
|
1185
|
+
}
|
|
1186
|
+
function sealToRecipient(session, recipientKemPub, plaintext) {
|
|
1187
|
+
return seal(session, recipientKemPub, plaintext);
|
|
1188
|
+
}
|
|
1189
|
+
async function unsealFromRecipient(session, blob) {
|
|
1190
|
+
if (!await verifyEntrySignature(blob.entry, SELF_EPOCH)) throw new Error("sealed blob signature invalid");
|
|
1191
|
+
return open(session, blob);
|
|
1192
|
+
}
|
|
1193
|
+
|
|
1194
|
+
// src/index.ts
|
|
1195
|
+
init_space_access();
|
|
1196
|
+
init_space_access_store();
|
|
1197
|
+
init_registry();
|
|
1208
1198
|
|
|
1209
1199
|
// src/spaces/members.ts
|
|
1200
|
+
init_space_access_store();
|
|
1201
|
+
init_paths();
|
|
1202
|
+
init_registry();
|
|
1210
1203
|
import { generateDeviceKeys } from "@drakkar.software/starfish-identities";
|
|
1211
1204
|
import { addCollectionRecipient } from "@drakkar.software/starfish-keyring";
|
|
1212
1205
|
import { mintMemberCap } from "@drakkar.software/starfish-sharing";
|
|
@@ -1238,24 +1231,26 @@ function makeJoinRequest(session) {
|
|
|
1238
1231
|
function isAlreadyPresentRecipient(err) {
|
|
1239
1232
|
return err instanceof Error && /already present in epoch/.test(err.message);
|
|
1240
1233
|
}
|
|
1241
|
-
|
|
1234
|
+
function isKeyringMissing(err) {
|
|
1235
|
+
return err instanceof Error && /not found|404|does not exist/i.test(err.message);
|
|
1236
|
+
}
|
|
1237
|
+
async function inviteToSpace(session, spaceId, requestJson, canWrite = true, spaceName) {
|
|
1238
|
+
const req2 = JSON.parse(requestJson);
|
|
1239
|
+
if (!req2.edPub || !req2.kemPub || !req2.userId) throw new Error("That is not a valid join request.");
|
|
1240
|
+
await addSpaceMember(session.accountClient, spaceId, session.userId, req2.userId);
|
|
1242
1241
|
try {
|
|
1243
1242
|
await addCollectionRecipient(
|
|
1244
1243
|
session.chatClient,
|
|
1245
1244
|
keyringName(spaceId),
|
|
1246
|
-
{ subKem:
|
|
1245
|
+
{ subKem: req2.kemPub, userId: req2.userId, label: req2.userId.slice(0, 8) },
|
|
1247
1246
|
{ edPriv: session.keys.edPriv, edPub: session.keys.edPub, kemPriv: session.keys.kemPriv },
|
|
1248
1247
|
{ trustedAdders: [session.keys.edPub] }
|
|
1249
1248
|
);
|
|
1250
1249
|
} catch (err) {
|
|
1251
|
-
if (!isAlreadyPresentRecipient(err)
|
|
1250
|
+
if (!isAlreadyPresentRecipient(err) && !isKeyringMissing(err)) {
|
|
1251
|
+
console.warn("[octospaces] inviteToSpace: keyring add skipped", err);
|
|
1252
|
+
}
|
|
1252
1253
|
}
|
|
1253
|
-
}
|
|
1254
|
-
async function inviteToSpace(session, spaceId, requestJson, canWrite = true, spaceName) {
|
|
1255
|
-
const req2 = JSON.parse(requestJson);
|
|
1256
|
-
if (!req2.edPub || !req2.kemPub || !req2.userId) throw new Error("That is not a valid join request.");
|
|
1257
|
-
await addDeviceToSpaceKeyring(session, spaceId, { kemPub: req2.kemPub, userId: req2.userId });
|
|
1258
|
-
await addSpaceMember(session.accountClient, spaceId, session.userId, req2.userId);
|
|
1259
1254
|
const cap = await mintMemberCap(
|
|
1260
1255
|
session.keys.edPriv,
|
|
1261
1256
|
session.keys.edPub,
|
|
@@ -1279,11 +1274,7 @@ async function acceptSpaceInvite(session, inviteJson) {
|
|
|
1279
1274
|
if (!cap.sub || cap.sub !== session.keys.edPub) {
|
|
1280
1275
|
throw new Error("This invite was issued for a different identity.");
|
|
1281
1276
|
}
|
|
1282
|
-
if (!cap.iss) throw new Error("This invite is missing its issuer.");
|
|
1283
1277
|
const spaceId = inv.spaceId;
|
|
1284
|
-
const client = makeClient(cap, session.keys.edPriv);
|
|
1285
|
-
const enc = await buildEncryptor(client, session.keys, spaceId, [cap.iss]);
|
|
1286
|
-
if (!enc) throw new Error("Accepted, but you're not in the space keyring yet \u2014 ask the owner to re-invite.");
|
|
1287
1278
|
const capJson = JSON.stringify(cap);
|
|
1288
1279
|
const name = inv.spaceName?.trim() || `space-${spaceId.slice(-6)}`;
|
|
1289
1280
|
const space = { id: spaceId, name, short: name.slice(0, 2).toUpperCase(), members: 1 };
|
|
@@ -1321,21 +1312,29 @@ async function createSpaceInviteLink(session, spaceId, spaceName, write, origin)
|
|
|
1321
1312
|
spaceMemberScope(spaceId, write)
|
|
1322
1313
|
);
|
|
1323
1314
|
await addSpaceMember(session.accountClient, spaceId, session.userId, ephemeralUserId);
|
|
1315
|
+
try {
|
|
1316
|
+
await addCollectionRecipient(
|
|
1317
|
+
session.chatClient,
|
|
1318
|
+
keyringName(spaceId),
|
|
1319
|
+
{ subKem: ek.kemPub, userId: ephemeralUserId, label: ephemeralUserId.slice(0, 8) },
|
|
1320
|
+
{ edPriv: session.keys.edPriv, edPub: session.keys.edPub, kemPriv: session.keys.kemPriv },
|
|
1321
|
+
{ trustedAdders: [session.keys.edPub] }
|
|
1322
|
+
);
|
|
1323
|
+
} catch (err) {
|
|
1324
|
+
if (!isAlreadyPresentRecipient(err) && !isKeyringMissing(err)) {
|
|
1325
|
+
console.warn("[octospaces] createSpaceInviteLink: keyring add skipped", err);
|
|
1326
|
+
}
|
|
1327
|
+
}
|
|
1324
1328
|
const token = { v: 1, spaceId, spaceName, cap, key: ek.edPriv, write };
|
|
1325
1329
|
return { token, link: encodeSpaceInviteLink(origin, token) };
|
|
1326
1330
|
}
|
|
1327
1331
|
async function joinSpaceByLink(session, token) {
|
|
1328
|
-
const cap = token.cap;
|
|
1329
|
-
const ownerId = cap.iss ? await userIdFromEdPub(cap.iss) : void 0;
|
|
1330
1332
|
const name = token.spaceName.trim() || `space-${token.spaceId.slice(-6)}`;
|
|
1331
1333
|
const space = {
|
|
1332
1334
|
id: token.spaceId,
|
|
1333
1335
|
name,
|
|
1334
1336
|
short: name.slice(0, 2).toUpperCase(),
|
|
1335
|
-
members: 1
|
|
1336
|
-
visibility: "public",
|
|
1337
|
-
...ownerId ? { ownerId } : {},
|
|
1338
|
-
write: token.write
|
|
1337
|
+
members: 1
|
|
1339
1338
|
};
|
|
1340
1339
|
const accessPayload = { cap: token.cap, key: token.key, write: token.write };
|
|
1341
1340
|
const sealed = await sealToSelf(session, JSON.stringify(accessPayload));
|
|
@@ -1343,6 +1342,19 @@ async function joinSpaceByLink(session, token) {
|
|
|
1343
1342
|
saveSpaceAccessEntry(token.spaceId, { kind: "link", cap: token.cap, key: token.key, write: token.write });
|
|
1344
1343
|
return space;
|
|
1345
1344
|
}
|
|
1345
|
+
async function addDeviceToSpaceKeyring(session, spaceId, device) {
|
|
1346
|
+
try {
|
|
1347
|
+
await addCollectionRecipient(
|
|
1348
|
+
session.chatClient,
|
|
1349
|
+
keyringName(spaceId),
|
|
1350
|
+
{ subKem: device.kemPub, userId: device.userId, label: device.userId.slice(0, 8) },
|
|
1351
|
+
{ edPriv: session.keys.edPriv, edPub: session.keys.edPub, kemPriv: session.keys.kemPriv },
|
|
1352
|
+
{ trustedAdders: [session.keys.edPub] }
|
|
1353
|
+
);
|
|
1354
|
+
} catch (err) {
|
|
1355
|
+
if (!isAlreadyPresentRecipient(err) && !isKeyringMissing(err)) throw err;
|
|
1356
|
+
}
|
|
1357
|
+
}
|
|
1346
1358
|
async function recoverSpaceAccess(session, server) {
|
|
1347
1359
|
const linkAccess = {};
|
|
1348
1360
|
for (const [spaceId, sealed] of Object.entries(server.pubAccess)) {
|
|
@@ -1378,7 +1390,333 @@ async function recoverSpaceAccess(session, server) {
|
|
|
1378
1390
|
}
|
|
1379
1391
|
}
|
|
1380
1392
|
|
|
1393
|
+
// src/spaces/nodes.ts
|
|
1394
|
+
init_client();
|
|
1395
|
+
init_identity();
|
|
1396
|
+
init_paths();
|
|
1397
|
+
init_space_access();
|
|
1398
|
+
init_space_access_store();
|
|
1399
|
+
import { generateDeviceKeys as generateDeviceKeys2 } from "@drakkar.software/starfish-identities";
|
|
1400
|
+
import { addCollectionRecipient as addCollectionRecipient2 } from "@drakkar.software/starfish-keyring";
|
|
1401
|
+
import { mintMemberCap as mintMemberCap2 } from "@drakkar.software/starfish-sharing";
|
|
1402
|
+
|
|
1403
|
+
// src/objects/objects.ts
|
|
1404
|
+
init_ids();
|
|
1405
|
+
function compareSiblings(a, b) {
|
|
1406
|
+
if (a.order !== b.order) return a.order - b.order;
|
|
1407
|
+
return a.id < b.id ? -1 : a.id > b.id ? 1 : 0;
|
|
1408
|
+
}
|
|
1409
|
+
function nextOrder(siblings) {
|
|
1410
|
+
let max = 0;
|
|
1411
|
+
for (const s of siblings) if (s.order > max) max = s.order;
|
|
1412
|
+
return max + 1;
|
|
1413
|
+
}
|
|
1414
|
+
function buildTree(nodes, includeArchived = false) {
|
|
1415
|
+
const live = includeArchived ? nodes : nodes.filter((n) => !n.archived);
|
|
1416
|
+
const byId = new Map(live.map((n) => [n.id, n]));
|
|
1417
|
+
const effectiveParent = (n) => {
|
|
1418
|
+
if (n.parentId == null) return null;
|
|
1419
|
+
if (!byId.has(n.parentId)) return null;
|
|
1420
|
+
const seen = /* @__PURE__ */ new Set([n.id]);
|
|
1421
|
+
let cur = n.parentId;
|
|
1422
|
+
while (cur != null) {
|
|
1423
|
+
if (seen.has(cur)) return null;
|
|
1424
|
+
seen.add(cur);
|
|
1425
|
+
const parent = byId.get(cur);
|
|
1426
|
+
if (!parent) return null;
|
|
1427
|
+
cur = parent.parentId;
|
|
1428
|
+
}
|
|
1429
|
+
return n.parentId;
|
|
1430
|
+
};
|
|
1431
|
+
const childrenOf = /* @__PURE__ */ new Map();
|
|
1432
|
+
for (const n of live) {
|
|
1433
|
+
const p = effectiveParent(n);
|
|
1434
|
+
const bucket = childrenOf.get(p) ?? [];
|
|
1435
|
+
bucket.push(n);
|
|
1436
|
+
childrenOf.set(p, bucket);
|
|
1437
|
+
}
|
|
1438
|
+
function attach(parent, depth) {
|
|
1439
|
+
return (childrenOf.get(parent) ?? []).slice().sort(compareSiblings).map((n) => ({ ...n, depth, children: attach(n.id, depth + 1) }));
|
|
1440
|
+
}
|
|
1441
|
+
return attach(null, 0);
|
|
1442
|
+
}
|
|
1443
|
+
function breadcrumbs(nodes, id) {
|
|
1444
|
+
const byId = new Map(nodes.map((n) => [n.id, n]));
|
|
1445
|
+
const trail = [];
|
|
1446
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1447
|
+
let cur = id;
|
|
1448
|
+
while (cur != null && byId.has(cur) && !seen.has(cur)) {
|
|
1449
|
+
seen.add(cur);
|
|
1450
|
+
const node = byId.get(cur);
|
|
1451
|
+
trail.unshift(node);
|
|
1452
|
+
cur = node.parentId;
|
|
1453
|
+
}
|
|
1454
|
+
return trail;
|
|
1455
|
+
}
|
|
1456
|
+
function ancestors(nodes, id) {
|
|
1457
|
+
return breadcrumbs(nodes, id).slice(0, -1);
|
|
1458
|
+
}
|
|
1459
|
+
function subtreeIds(nodes, rootId) {
|
|
1460
|
+
const childrenOf = /* @__PURE__ */ new Map();
|
|
1461
|
+
for (const n of nodes) {
|
|
1462
|
+
const bucket = childrenOf.get(n.parentId) ?? [];
|
|
1463
|
+
bucket.push(n.id);
|
|
1464
|
+
childrenOf.set(n.parentId, bucket);
|
|
1465
|
+
}
|
|
1466
|
+
const out = /* @__PURE__ */ new Set();
|
|
1467
|
+
const walk = (id) => {
|
|
1468
|
+
if (out.has(id)) return;
|
|
1469
|
+
out.add(id);
|
|
1470
|
+
for (const child of childrenOf.get(id) ?? []) walk(child);
|
|
1471
|
+
};
|
|
1472
|
+
walk(rootId);
|
|
1473
|
+
return out;
|
|
1474
|
+
}
|
|
1475
|
+
function addObject(nodes, input, now) {
|
|
1476
|
+
const parentId = input.parentId ?? null;
|
|
1477
|
+
const siblings = nodes.filter((n) => n.parentId === parentId);
|
|
1478
|
+
const node = {
|
|
1479
|
+
id: input.id ?? `obj-${randomId()}`,
|
|
1480
|
+
type: input.type,
|
|
1481
|
+
parentId,
|
|
1482
|
+
order: nextOrder(siblings),
|
|
1483
|
+
title: input.title,
|
|
1484
|
+
...input.emoji ? { emoji: input.emoji } : {},
|
|
1485
|
+
updatedAt: now,
|
|
1486
|
+
...input.meta ? { meta: input.meta } : {},
|
|
1487
|
+
...input.access && input.access !== "space" ? { access: input.access } : {},
|
|
1488
|
+
...input.enc ? { enc: true } : {}
|
|
1489
|
+
};
|
|
1490
|
+
return { nodes: [...nodes, node], node };
|
|
1491
|
+
}
|
|
1492
|
+
function patchObject(nodes, id, patch, now) {
|
|
1493
|
+
return nodes.map((n) => n.id === id ? { ...n, ...patch, updatedAt: now } : n);
|
|
1494
|
+
}
|
|
1495
|
+
function reparentObject(nodes, id, parentId, now) {
|
|
1496
|
+
if (id === parentId) return nodes;
|
|
1497
|
+
if (parentId != null && subtreeIds(nodes, id).has(parentId)) return nodes;
|
|
1498
|
+
const siblings = nodes.filter((n) => n.parentId === parentId && n.id !== id);
|
|
1499
|
+
return nodes.map((n) => n.id === id ? { ...n, parentId, order: nextOrder(siblings), updatedAt: now } : n);
|
|
1500
|
+
}
|
|
1501
|
+
function reorderObjects(nodes, orderById, now) {
|
|
1502
|
+
return nodes.map((n) => n.id in orderById ? { ...n, order: orderById[n.id], updatedAt: now } : n);
|
|
1503
|
+
}
|
|
1504
|
+
function archiveObject(nodes, id, now) {
|
|
1505
|
+
const ids = subtreeIds(nodes, id);
|
|
1506
|
+
return nodes.map((n) => ids.has(n.id) ? { ...n, archived: true, updatedAt: now } : n);
|
|
1507
|
+
}
|
|
1508
|
+
|
|
1509
|
+
// src/spaces/nodes.ts
|
|
1510
|
+
init_object_index();
|
|
1511
|
+
init_registry();
|
|
1512
|
+
init_ids();
|
|
1513
|
+
function isAlreadyPresentRecipient2(err) {
|
|
1514
|
+
return err instanceof Error && /already present in epoch/.test(err.message);
|
|
1515
|
+
}
|
|
1516
|
+
async function createNode(session, spaceId, input, reg) {
|
|
1517
|
+
const access = input.access ?? "space";
|
|
1518
|
+
const enc = input.enc ?? false;
|
|
1519
|
+
if (access === "public" && enc) throw new Error("public+enc is not a valid combination.");
|
|
1520
|
+
const nodeId = `obj-${randomId()}`;
|
|
1521
|
+
if (enc) {
|
|
1522
|
+
const client = getSpaceClient(spaceId, session);
|
|
1523
|
+
await ownerEnsureKeyring(
|
|
1524
|
+
client,
|
|
1525
|
+
session.keys,
|
|
1526
|
+
keyringPull(spaceId),
|
|
1527
|
+
keyringPush(spaceId),
|
|
1528
|
+
ownerTrustedAdders(session)
|
|
1529
|
+
);
|
|
1530
|
+
}
|
|
1531
|
+
let createdNode = null;
|
|
1532
|
+
await updateObjectIndex(session, spaceId, (nodes, now) => {
|
|
1533
|
+
const { nodes: next, node } = addObject(nodes, {
|
|
1534
|
+
id: nodeId,
|
|
1535
|
+
type: input.type,
|
|
1536
|
+
title: input.title,
|
|
1537
|
+
...input.emoji ? { emoji: input.emoji } : {},
|
|
1538
|
+
parentId: input.parentId ?? null,
|
|
1539
|
+
...input.meta ? { meta: input.meta } : {},
|
|
1540
|
+
access,
|
|
1541
|
+
enc: enc || void 0
|
|
1542
|
+
}, now);
|
|
1543
|
+
createdNode = next.find((n) => n.id === nodeId) ?? node;
|
|
1544
|
+
return next;
|
|
1545
|
+
}, reg);
|
|
1546
|
+
if (!createdNode) throw new Error("createNode: index update did not produce a node");
|
|
1547
|
+
return createdNode;
|
|
1548
|
+
}
|
|
1549
|
+
async function setNodeAccess(session, spaceId, nodeId, patch, reg) {
|
|
1550
|
+
if (patch.access === "public" && patch.enc) throw new Error("public+enc is not valid.");
|
|
1551
|
+
if (patch.enc) {
|
|
1552
|
+
const client = getSpaceClient(spaceId, session);
|
|
1553
|
+
await ownerEnsureKeyring(
|
|
1554
|
+
client,
|
|
1555
|
+
session.keys,
|
|
1556
|
+
keyringPull(spaceId),
|
|
1557
|
+
keyringPush(spaceId),
|
|
1558
|
+
ownerTrustedAdders(session)
|
|
1559
|
+
);
|
|
1560
|
+
}
|
|
1561
|
+
await updateObjectIndex(session, spaceId, (nodes, now) => {
|
|
1562
|
+
const idx = nodes.findIndex((n) => n.id === nodeId);
|
|
1563
|
+
if (idx < 0) return null;
|
|
1564
|
+
const cur = nodes[idx];
|
|
1565
|
+
const next = { ...cur, updatedAt: now };
|
|
1566
|
+
if (patch.access !== void 0) {
|
|
1567
|
+
if (patch.access === "space") {
|
|
1568
|
+
delete next.access;
|
|
1569
|
+
} else {
|
|
1570
|
+
next.access = patch.access;
|
|
1571
|
+
}
|
|
1572
|
+
}
|
|
1573
|
+
if (patch.enc !== void 0) {
|
|
1574
|
+
if (!patch.enc) {
|
|
1575
|
+
delete next.enc;
|
|
1576
|
+
} else {
|
|
1577
|
+
next.enc = true;
|
|
1578
|
+
}
|
|
1579
|
+
}
|
|
1580
|
+
if (next.access === "public" && next.enc) throw new Error("public+enc is not valid.");
|
|
1581
|
+
const unchanged = next.access === cur.access && (next.enc ?? false) === (cur.enc ?? false);
|
|
1582
|
+
if (unchanged) return null;
|
|
1583
|
+
return nodes.map((n, i) => i === idx ? next : n);
|
|
1584
|
+
}, reg);
|
|
1585
|
+
}
|
|
1586
|
+
async function inviteToNode(session, spaceId, nodeId, requestJson, node, nodeName) {
|
|
1587
|
+
const req2 = JSON.parse(requestJson);
|
|
1588
|
+
if (!req2.edPub || !req2.kemPub || !req2.userId) throw new Error("Invalid join request.");
|
|
1589
|
+
if (node.enc) {
|
|
1590
|
+
try {
|
|
1591
|
+
await addCollectionRecipient2(
|
|
1592
|
+
session.chatClient,
|
|
1593
|
+
keyringName(spaceId),
|
|
1594
|
+
{ subKem: req2.kemPub, userId: req2.userId, label: req2.userId.slice(0, 8) },
|
|
1595
|
+
{ edPriv: session.keys.edPriv, edPub: session.keys.edPub, kemPriv: session.keys.kemPriv },
|
|
1596
|
+
{ trustedAdders: [session.keys.edPub] }
|
|
1597
|
+
);
|
|
1598
|
+
} catch (err) {
|
|
1599
|
+
if (!isAlreadyPresentRecipient2(err)) throw err;
|
|
1600
|
+
}
|
|
1601
|
+
}
|
|
1602
|
+
await addSpaceMember(session.accountClient, spaceId, session.userId, req2.userId);
|
|
1603
|
+
const spaceCap = await mintMemberCap2(
|
|
1604
|
+
session.keys.edPriv,
|
|
1605
|
+
session.keys.edPub,
|
|
1606
|
+
{ edPubHex: req2.edPub, kemPubHex: req2.kemPub, userIdHex: req2.userId },
|
|
1607
|
+
"chat",
|
|
1608
|
+
spaceMemberScope(spaceId, true)
|
|
1609
|
+
);
|
|
1610
|
+
const bundle = {
|
|
1611
|
+
spaceId,
|
|
1612
|
+
nodeId,
|
|
1613
|
+
nodeName: nodeName ?? nodeId,
|
|
1614
|
+
cap: spaceCap
|
|
1615
|
+
};
|
|
1616
|
+
if (!node.enc) {
|
|
1617
|
+
const perNodeCap = await mintMemberCap2(
|
|
1618
|
+
session.keys.edPriv,
|
|
1619
|
+
session.keys.edPub,
|
|
1620
|
+
{ edPubHex: req2.edPub, kemPubHex: req2.kemPub, userIdHex: req2.userId },
|
|
1621
|
+
"chat",
|
|
1622
|
+
nodeMemberScope(spaceId, nodeId, true)
|
|
1623
|
+
);
|
|
1624
|
+
bundle.nodeCap = perNodeCap;
|
|
1625
|
+
}
|
|
1626
|
+
return JSON.stringify(bundle);
|
|
1627
|
+
}
|
|
1628
|
+
async function acceptNodeInvite(session, bundleJson) {
|
|
1629
|
+
const bundle = JSON.parse(bundleJson);
|
|
1630
|
+
const cap = bundle.cap;
|
|
1631
|
+
if (!cap || !bundle.spaceId || !bundle.nodeId) throw new Error("Invalid node invite.");
|
|
1632
|
+
if (cap.kind !== "member") throw new Error("Invalid node invite.");
|
|
1633
|
+
if (!cap.sub || cap.sub !== session.keys.edPub) {
|
|
1634
|
+
throw new Error("This invite was issued for a different identity.");
|
|
1635
|
+
}
|
|
1636
|
+
const capJson = JSON.stringify(cap);
|
|
1637
|
+
saveSpaceAccessEntry(bundle.spaceId, { kind: "member", cap: capJson });
|
|
1638
|
+
if (bundle.nodeCap) {
|
|
1639
|
+
const nodeCapJson = JSON.stringify(bundle.nodeCap);
|
|
1640
|
+
saveNodeAccessEntry(bundle.spaceId, bundle.nodeId, { kind: "member", cap: nodeCapJson });
|
|
1641
|
+
}
|
|
1642
|
+
return bundle.nodeId;
|
|
1643
|
+
}
|
|
1644
|
+
function encodeNodeInviteLink(origin, token) {
|
|
1645
|
+
const base = origin.replace(/\/+$/, "");
|
|
1646
|
+
return `${base}/join/node#${toBase64Url(JSON.stringify(token))}`;
|
|
1647
|
+
}
|
|
1648
|
+
function decodeNodeInviteLink(fragment) {
|
|
1649
|
+
const frag = fragment.startsWith("#") ? fragment.slice(1) : fragment;
|
|
1650
|
+
const tok = JSON.parse(fromBase64Url(frag));
|
|
1651
|
+
if (!tok || !tok.spaceId || !tok.nodeId || !tok.cap || !tok.key) {
|
|
1652
|
+
throw new Error("That node invite link is malformed or incomplete.");
|
|
1653
|
+
}
|
|
1654
|
+
return {
|
|
1655
|
+
v: 1,
|
|
1656
|
+
spaceId: tok.spaceId,
|
|
1657
|
+
nodeId: tok.nodeId,
|
|
1658
|
+
nodeName: tok.nodeName ?? tok.nodeId,
|
|
1659
|
+
cap: tok.cap,
|
|
1660
|
+
key: tok.key,
|
|
1661
|
+
write: !!tok.write
|
|
1662
|
+
};
|
|
1663
|
+
}
|
|
1664
|
+
async function createNodeInviteLink(session, spaceId, nodeId, nodeName, node, write, origin) {
|
|
1665
|
+
const ek = generateDeviceKeys2();
|
|
1666
|
+
const ephemeralUserId = await userIdFromEdPub(ek.edPub);
|
|
1667
|
+
await addSpaceMember(session.accountClient, spaceId, session.userId, ephemeralUserId);
|
|
1668
|
+
if (node.enc) {
|
|
1669
|
+
try {
|
|
1670
|
+
await addCollectionRecipient2(
|
|
1671
|
+
session.chatClient,
|
|
1672
|
+
keyringName(spaceId),
|
|
1673
|
+
{ subKem: ek.kemPub, userId: ephemeralUserId, label: ephemeralUserId.slice(0, 8) },
|
|
1674
|
+
{ edPriv: session.keys.edPriv, edPub: session.keys.edPub, kemPriv: session.keys.kemPriv },
|
|
1675
|
+
{ trustedAdders: [session.keys.edPub] }
|
|
1676
|
+
);
|
|
1677
|
+
} catch (err) {
|
|
1678
|
+
if (!isAlreadyPresentRecipient2(err)) throw err;
|
|
1679
|
+
}
|
|
1680
|
+
}
|
|
1681
|
+
const cap = await mintMemberCap2(
|
|
1682
|
+
session.keys.edPriv,
|
|
1683
|
+
session.keys.edPub,
|
|
1684
|
+
{ edPubHex: ek.edPub, kemPubHex: ek.kemPub, userIdHex: ephemeralUserId },
|
|
1685
|
+
"chat",
|
|
1686
|
+
node.enc ? spaceMemberScope(spaceId, write) : nodeMemberScope(spaceId, nodeId, write)
|
|
1687
|
+
);
|
|
1688
|
+
const token = { v: 1, spaceId, nodeId, nodeName, cap, key: ek.edPriv, write };
|
|
1689
|
+
return { token, link: encodeNodeInviteLink(origin, token) };
|
|
1690
|
+
}
|
|
1691
|
+
async function joinNodeByLink(session, token) {
|
|
1692
|
+
const accessPayload = { cap: token.cap, key: token.key, write: token.write };
|
|
1693
|
+
const sealed = await sealToSelf(session, JSON.stringify(accessPayload));
|
|
1694
|
+
const { updateSpacesDoc: updateSpacesDoc2 } = await Promise.resolve().then(() => (init_registry(), registry_exports));
|
|
1695
|
+
await updateSpacesDoc2(session.accountClient, session.userId, (cur) => ({
|
|
1696
|
+
spaces: cur.spaces,
|
|
1697
|
+
caps: cur.caps,
|
|
1698
|
+
pubAccess: {
|
|
1699
|
+
...cur.pubAccess,
|
|
1700
|
+
[`${token.spaceId}:${token.nodeId}`]: sealed
|
|
1701
|
+
}
|
|
1702
|
+
}));
|
|
1703
|
+
saveNodeAccessEntry(token.spaceId, token.nodeId, {
|
|
1704
|
+
kind: "link",
|
|
1705
|
+
cap: token.cap,
|
|
1706
|
+
key: token.key,
|
|
1707
|
+
write: token.write
|
|
1708
|
+
});
|
|
1709
|
+
return token.nodeId;
|
|
1710
|
+
}
|
|
1711
|
+
|
|
1712
|
+
// src/index.ts
|
|
1713
|
+
init_object_index();
|
|
1714
|
+
|
|
1381
1715
|
// src/sync/pairing.ts
|
|
1716
|
+
init_config();
|
|
1717
|
+
init_fetch_timeout();
|
|
1718
|
+
init_identity();
|
|
1719
|
+
init_paths();
|
|
1382
1720
|
import { StarfishClient as StarfishClient2 } from "@drakkar.software/starfish-client";
|
|
1383
1721
|
import {
|
|
1384
1722
|
installPairingBundle,
|
|
@@ -1401,15 +1739,6 @@ async function startDevicePairing(session, pin) {
|
|
|
1401
1739
|
{ edPriv: session.keys.edPriv, edPub: session.keys.edPub },
|
|
1402
1740
|
{ scope: linkedDeviceScope(session.userId), ttlSec: LINKED_DEVICE_TTL_SEC }
|
|
1403
1741
|
);
|
|
1404
|
-
const { spaces, caps } = await readSpaces(session.accountClient, session.userId);
|
|
1405
|
-
for (const space of spaces) {
|
|
1406
|
-
if (caps[space.id]) continue;
|
|
1407
|
-
try {
|
|
1408
|
-
await addDeviceToSpaceKeyring(session, space.id, { kemPub: deviceKeys.kemPub, userId: session.userId });
|
|
1409
|
-
} catch (err) {
|
|
1410
|
-
console.log("[pairing] keyring grant failed", { spaceId: space.id, error: String(err?.message ?? err) });
|
|
1411
|
-
}
|
|
1412
|
-
}
|
|
1413
1742
|
const blob = JSON.stringify({ v: 1, keys: deviceKeys, bundle });
|
|
1414
1743
|
const sealed = await sealWithPassphrase(pin, new TextEncoder().encode(blob));
|
|
1415
1744
|
const nonce = randomNonce();
|
|
@@ -1444,6 +1773,11 @@ async function completeDevicePairing(payload, pin) {
|
|
|
1444
1773
|
};
|
|
1445
1774
|
}
|
|
1446
1775
|
|
|
1776
|
+
// src/index.ts
|
|
1777
|
+
init_pull_cache();
|
|
1778
|
+
init_profile_cache();
|
|
1779
|
+
init_fetch_timeout();
|
|
1780
|
+
|
|
1447
1781
|
// src/sync/base64.ts
|
|
1448
1782
|
var CHUNK = 24576;
|
|
1449
1783
|
var ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
@@ -1509,12 +1843,11 @@ function decodePure(encoded) {
|
|
|
1509
1843
|
var starfishBase64 = nativeCodec ? { encode: encodeViaBtoa, decode: decodeViaAtob } : { encode: encodePure, decode: decodePure };
|
|
1510
1844
|
export {
|
|
1511
1845
|
CONNECT_TIMEOUT_MS,
|
|
1512
|
-
CategoryError,
|
|
1513
|
-
DEFAULT_CATEGORY,
|
|
1514
1846
|
OBJECT_COLLECTIONS,
|
|
1515
1847
|
PAIR_PREFIX,
|
|
1516
1848
|
PULL_CACHE_MAX_AGE_MS,
|
|
1517
1849
|
SpaceAccessError,
|
|
1850
|
+
acceptNodeInvite,
|
|
1518
1851
|
acceptSpaceInvite,
|
|
1519
1852
|
accountScope,
|
|
1520
1853
|
addDeviceToSpaceKeyring,
|
|
@@ -1532,39 +1865,45 @@ export {
|
|
|
1532
1865
|
buildAuthHeaders,
|
|
1533
1866
|
buildEncryptor,
|
|
1534
1867
|
buildLinkedSession,
|
|
1868
|
+
buildNodeAccess,
|
|
1535
1869
|
buildSession,
|
|
1536
|
-
buildSpaceAccess,
|
|
1537
1870
|
buildTree,
|
|
1538
1871
|
bytesToHex,
|
|
1539
1872
|
cacheProfile,
|
|
1540
1873
|
capProviderFor,
|
|
1541
|
-
|
|
1542
|
-
clearSpaceAccessCache,
|
|
1874
|
+
clearNodeAccessCache,
|
|
1543
1875
|
clearSpaceAccessStore,
|
|
1544
1876
|
completeDevicePairing,
|
|
1545
1877
|
configureKv,
|
|
1546
1878
|
configureOctoSpaces,
|
|
1879
|
+
createNode,
|
|
1880
|
+
createNodeInviteLink,
|
|
1547
1881
|
createSpace,
|
|
1548
1882
|
createSpaceInviteLink,
|
|
1883
|
+
decodeNodeInviteLink,
|
|
1549
1884
|
decodeSpaceInviteLink,
|
|
1550
1885
|
deriveSession,
|
|
1886
|
+
encodeNodeInviteLink,
|
|
1551
1887
|
encodeSpaceInviteLink,
|
|
1552
1888
|
ensureProfileKeys,
|
|
1553
1889
|
ensurePseudo,
|
|
1554
|
-
excludeAutomatedRooms,
|
|
1555
1890
|
fetchWithTimeout,
|
|
1556
1891
|
fingerprintFromUserId,
|
|
1557
1892
|
fromBase64Url,
|
|
1558
1893
|
generateSeedWords,
|
|
1894
|
+
getNodeAccess,
|
|
1895
|
+
getNodeAccessEntry,
|
|
1559
1896
|
getSharedSpacesNamespace,
|
|
1560
|
-
getSpaceAccess,
|
|
1561
1897
|
getSpaceAccessEntry,
|
|
1898
|
+
getSpaceClient,
|
|
1562
1899
|
getSyncBase,
|
|
1563
1900
|
getSyncNamespace,
|
|
1564
1901
|
getSyncPrefix,
|
|
1565
1902
|
hydrateSpaceAccessStore,
|
|
1903
|
+
inviteToNode,
|
|
1566
1904
|
inviteToSpace,
|
|
1567
1905
|
isValidSeed,
|
|
1906
|
+
joinNodeByLink,
|
|
1568
1907
|
joinSpaceByLink,
|
|
1569
1908
|
keyringName,
|
|
1570
1909
|
keyringPull,
|
|
@@ -1580,16 +1919,23 @@ export {
|
|
|
1580
1919
|
makeJoinRequest,
|
|
1581
1920
|
memberCapsFromStore,
|
|
1582
1921
|
nextOrder,
|
|
1583
|
-
|
|
1922
|
+
nodeMemberScope,
|
|
1584
1923
|
objDocPull,
|
|
1585
1924
|
objDocPush,
|
|
1586
1925
|
objIndexPull,
|
|
1587
1926
|
objIndexPush,
|
|
1927
|
+
objInvName,
|
|
1928
|
+
objInvPull,
|
|
1929
|
+
objInvPush,
|
|
1588
1930
|
objLogPull,
|
|
1589
1931
|
objLogPush,
|
|
1932
|
+
objPubName,
|
|
1933
|
+
objPubPull,
|
|
1934
|
+
objPubPush,
|
|
1590
1935
|
objectBlobPull,
|
|
1591
1936
|
objectBlobPush,
|
|
1592
|
-
|
|
1937
|
+
objectDirName,
|
|
1938
|
+
objectDirPull,
|
|
1593
1939
|
onSpaceMeta,
|
|
1594
1940
|
openEncryptor,
|
|
1595
1941
|
ownerEnsureKeyring,
|
|
@@ -1601,40 +1947,37 @@ export {
|
|
|
1601
1947
|
pullCache,
|
|
1602
1948
|
pushIndexSeed,
|
|
1603
1949
|
randomId,
|
|
1604
|
-
|
|
1950
|
+
readObjectTree,
|
|
1605
1951
|
readProfile,
|
|
1606
1952
|
readProfiles,
|
|
1607
1953
|
readPseudo,
|
|
1608
|
-
|
|
1609
|
-
readSpaceIndexRooms,
|
|
1610
|
-
readSpaceRooms,
|
|
1954
|
+
readSpaceAccess,
|
|
1611
1955
|
readSpaces,
|
|
1612
1956
|
reconcileSpaceMeta,
|
|
1613
1957
|
recoverSpaceAccess,
|
|
1958
|
+
removeNodeAccessEntry,
|
|
1614
1959
|
removeSpaceAccessEntry,
|
|
1615
1960
|
removeSpaceMember,
|
|
1616
1961
|
reorderObjects,
|
|
1617
1962
|
reorderSpaces,
|
|
1618
1963
|
reparentObject,
|
|
1619
|
-
roomKindToSubtype,
|
|
1620
1964
|
roomSlug,
|
|
1621
|
-
roomsRegistryPull,
|
|
1622
|
-
roomsRegistryPush,
|
|
1623
1965
|
rootIdentityOf,
|
|
1966
|
+
saveNodeAccessEntry,
|
|
1624
1967
|
saveSpaceAccessEntry,
|
|
1625
1968
|
sealToRecipient,
|
|
1626
1969
|
sealToSelf,
|
|
1627
|
-
seedIndexNodes,
|
|
1628
1970
|
seedSpaceObjectIndex,
|
|
1629
1971
|
setDmMapping,
|
|
1630
|
-
|
|
1972
|
+
setNodeAccess,
|
|
1973
|
+
spaceAccessPull,
|
|
1974
|
+
spaceAccessPush,
|
|
1631
1975
|
spaceMemberScope,
|
|
1632
1976
|
spacesPull,
|
|
1633
1977
|
spacesPush,
|
|
1634
1978
|
starfishBase64,
|
|
1635
1979
|
startDevicePairing,
|
|
1636
1980
|
subtreeIds,
|
|
1637
|
-
subtypeToRoomKind,
|
|
1638
1981
|
toBase64Url,
|
|
1639
1982
|
typesIndexPull,
|
|
1640
1983
|
typesIndexPush,
|
|
@@ -1650,7 +1993,7 @@ export {
|
|
|
1650
1993
|
userIdFromEdPub,
|
|
1651
1994
|
writeProfile,
|
|
1652
1995
|
writePseudo,
|
|
1653
|
-
|
|
1996
|
+
writeSpaceAccess,
|
|
1654
1997
|
writeSpaces
|
|
1655
1998
|
};
|
|
1656
1999
|
//# sourceMappingURL=index.js.map
|