jazz-tools 0.8.44 → 0.8.45
Sign up to get free protection for your applications and to get access to all the features.
- package/.turbo/turbo-build.log +16 -10
- package/CHANGELOG.md +12 -0
- package/dist/chunk-5WLKIDKU.js +2844 -0
- package/dist/chunk-5WLKIDKU.js.map +1 -0
- package/dist/index.native.js +63 -0
- package/dist/index.native.js.map +1 -0
- package/dist/index.web.js +61 -0
- package/dist/index.web.js.map +1 -0
- package/package.json +9 -12
- package/src/coValues/account.ts +21 -9
- package/src/coValues/coFeed.ts +4 -3
- package/src/coValues/coList.ts +8 -5
- package/src/coValues/coMap.ts +6 -3
- package/src/coValues/deepLoading.ts +6 -12
- package/src/coValues/extensions/imageDef.ts +3 -1
- package/src/coValues/group.ts +8 -11
- package/src/coValues/inbox.ts +377 -0
- package/src/coValues/interfaces.ts +9 -6
- package/src/coValues/profile.ts +11 -0
- package/src/coValues/registeredSchemas.ts +12 -0
- package/src/exports.ts +21 -15
- package/src/implementation/anonymousJazzAgent.ts +6 -0
- package/src/implementation/createContext.ts +6 -7
- package/src/implementation/refs.ts +1 -1
- package/src/implementation/subscriptionScope.ts +1 -1
- package/src/index.native.ts +0 -1
- package/src/internal.ts +1 -9
- package/src/tests/account.test.ts +2 -2
- package/src/tests/coFeed.test.ts +0 -9
- package/src/tests/coMap.test.ts +2 -2
- package/src/tests/groupsAndAccounts.test.ts +1 -1
- package/src/tests/inbox.test.ts +256 -0
- package/src/tests/schemaUnion.test.ts +1 -1
- package/src/tests/utils.ts +14 -0
- package/tsup.config.ts +13 -0
- package/dist/native/coValues/account.js +0 -221
- package/dist/native/coValues/account.js.map +0 -1
- package/dist/native/coValues/coFeed.js +0 -571
- package/dist/native/coValues/coFeed.js.map +0 -1
- package/dist/native/coValues/coList.js +0 -404
- package/dist/native/coValues/coList.js.map +0 -1
- package/dist/native/coValues/coMap.js +0 -537
- package/dist/native/coValues/coMap.js.map +0 -1
- package/dist/native/coValues/deepLoading.js +0 -63
- package/dist/native/coValues/deepLoading.js.map +0 -1
- package/dist/native/coValues/extensions/imageDef.js +0 -42
- package/dist/native/coValues/extensions/imageDef.js.map +0 -1
- package/dist/native/coValues/group.js +0 -136
- package/dist/native/coValues/group.js.map +0 -1
- package/dist/native/coValues/interfaces.js +0 -135
- package/dist/native/coValues/interfaces.js.map +0 -1
- package/dist/native/coValues/schemaUnion.js +0 -89
- package/dist/native/coValues/schemaUnion.js.map +0 -1
- package/dist/native/exports.js +0 -5
- package/dist/native/exports.js.map +0 -1
- package/dist/native/implementation/createContext.js +0 -157
- package/dist/native/implementation/createContext.js.map +0 -1
- package/dist/native/implementation/devtoolsFormatters.js +0 -95
- package/dist/native/implementation/devtoolsFormatters.js.map +0 -1
- package/dist/native/implementation/errors.js +0 -2
- package/dist/native/implementation/errors.js.map +0 -1
- package/dist/native/implementation/inspect.js +0 -2
- package/dist/native/implementation/inspect.js.map +0 -1
- package/dist/native/implementation/refs.js +0 -115
- package/dist/native/implementation/refs.js.map +0 -1
- package/dist/native/implementation/schema.js +0 -96
- package/dist/native/implementation/schema.js.map +0 -1
- package/dist/native/implementation/subscriptionScope.js +0 -91
- package/dist/native/implementation/subscriptionScope.js.map +0 -1
- package/dist/native/implementation/symbols.js +0 -4
- package/dist/native/implementation/symbols.js.map +0 -1
- package/dist/native/index.native.js +0 -3
- package/dist/native/index.native.js.map +0 -1
- package/dist/native/internal.js +0 -18
- package/dist/native/internal.js.map +0 -1
- package/dist/native/lib/cache.js +0 -13
- package/dist/native/lib/cache.js.map +0 -1
- package/dist/native/lib/cache.test.js +0 -52
- package/dist/native/lib/cache.test.js.map +0 -1
- package/dist/web/coValues/account.js +0 -221
- package/dist/web/coValues/account.js.map +0 -1
- package/dist/web/coValues/coFeed.js +0 -571
- package/dist/web/coValues/coFeed.js.map +0 -1
- package/dist/web/coValues/coList.js +0 -404
- package/dist/web/coValues/coList.js.map +0 -1
- package/dist/web/coValues/coMap.js +0 -537
- package/dist/web/coValues/coMap.js.map +0 -1
- package/dist/web/coValues/deepLoading.js +0 -63
- package/dist/web/coValues/deepLoading.js.map +0 -1
- package/dist/web/coValues/extensions/imageDef.js +0 -42
- package/dist/web/coValues/extensions/imageDef.js.map +0 -1
- package/dist/web/coValues/group.js +0 -136
- package/dist/web/coValues/group.js.map +0 -1
- package/dist/web/coValues/interfaces.js +0 -135
- package/dist/web/coValues/interfaces.js.map +0 -1
- package/dist/web/coValues/schemaUnion.js +0 -89
- package/dist/web/coValues/schemaUnion.js.map +0 -1
- package/dist/web/exports.js +0 -5
- package/dist/web/exports.js.map +0 -1
- package/dist/web/implementation/createContext.js +0 -157
- package/dist/web/implementation/createContext.js.map +0 -1
- package/dist/web/implementation/devtoolsFormatters.js +0 -95
- package/dist/web/implementation/devtoolsFormatters.js.map +0 -1
- package/dist/web/implementation/errors.js +0 -2
- package/dist/web/implementation/errors.js.map +0 -1
- package/dist/web/implementation/inspect.js +0 -2
- package/dist/web/implementation/inspect.js.map +0 -1
- package/dist/web/implementation/refs.js +0 -115
- package/dist/web/implementation/refs.js.map +0 -1
- package/dist/web/implementation/schema.js +0 -96
- package/dist/web/implementation/schema.js.map +0 -1
- package/dist/web/implementation/subscriptionScope.js +0 -91
- package/dist/web/implementation/subscriptionScope.js.map +0 -1
- package/dist/web/implementation/symbols.js +0 -4
- package/dist/web/implementation/symbols.js.map +0 -1
- package/dist/web/index.web.js +0 -3
- package/dist/web/index.web.js.map +0 -1
- package/dist/web/internal.js +0 -18
- package/dist/web/internal.js.map +0 -1
- package/dist/web/lib/cache.js +0 -13
- package/dist/web/lib/cache.js.map +0 -1
- package/dist/web/lib/cache.test.js +0 -52
- package/dist/web/lib/cache.test.js.map +0 -1
package/package.json
CHANGED
@@ -6,25 +6,25 @@
|
|
6
6
|
"react-native": "dist/native/index.native.js",
|
7
7
|
"exports": {
|
8
8
|
".": {
|
9
|
-
"react-native": "./dist/
|
9
|
+
"react-native": "./dist/index.native.js",
|
10
10
|
"types": "./src/index.web.ts",
|
11
|
-
"default": "./dist/
|
11
|
+
"default": "./dist/index.web.js"
|
12
12
|
},
|
13
13
|
"./native": {
|
14
|
-
"react-native": "./dist/
|
14
|
+
"react-native": "./dist/index.native.js",
|
15
15
|
"types": "./src/index.native.ts",
|
16
|
-
"default": "./dist/
|
16
|
+
"default": "./dist/index.native.js"
|
17
17
|
},
|
18
18
|
"./src/*": "./src/*"
|
19
19
|
},
|
20
20
|
"type": "module",
|
21
21
|
"license": "MIT",
|
22
|
-
"version": "0.8.
|
22
|
+
"version": "0.8.45",
|
23
23
|
"dependencies": {
|
24
|
-
"
|
25
|
-
"cojson": "0.8.44"
|
24
|
+
"cojson": "0.8.45"
|
26
25
|
},
|
27
26
|
"devDependencies": {
|
27
|
+
"tsup": "8.3.5",
|
28
28
|
"typescript": "~5.6.2",
|
29
29
|
"vitest": "1.5.3"
|
30
30
|
},
|
@@ -38,12 +38,9 @@
|
|
38
38
|
"scripts": {
|
39
39
|
"format-and-lint": "biome check .",
|
40
40
|
"format-and-lint:fix": "biome check . --write",
|
41
|
-
"dev
|
42
|
-
"dev:native": "tsc --sourceMap --watch --outDir dist/native -p tsconfig.native.json",
|
41
|
+
"dev": "tsup --watch",
|
43
42
|
"test": "vitest --run --root ../../ --project jazz-tools",
|
44
43
|
"test:watch": "vitest --watch --root ../../ --project jazz-tools",
|
45
|
-
"build
|
46
|
-
"build:native": "tsc --sourceMap --outDir dist/native -p tsconfig.native.json",
|
47
|
-
"build": "rm -rf ./dist && pnpm run build:web && pnpm run build:native"
|
44
|
+
"build": "tsup"
|
48
45
|
}
|
49
46
|
}
|
package/src/coValues/account.ts
CHANGED
@@ -14,16 +14,13 @@ import {
|
|
14
14
|
} from "cojson";
|
15
15
|
import {
|
16
16
|
AnonymousJazzAgent,
|
17
|
-
CoMap,
|
18
17
|
type CoValue,
|
19
18
|
CoValueBase,
|
20
19
|
CoValueClass,
|
21
20
|
DeeplyLoaded,
|
22
21
|
DepthsIn,
|
23
|
-
Group,
|
24
22
|
ID,
|
25
23
|
MembersSym,
|
26
|
-
Profile,
|
27
24
|
Ref,
|
28
25
|
type RefEncoded,
|
29
26
|
RefIfCoValue,
|
@@ -37,6 +34,11 @@ import {
|
|
37
34
|
subscriptionsScopes,
|
38
35
|
} from "../internal.js";
|
39
36
|
import { coValuesCache } from "../lib/cache.js";
|
37
|
+
import { type CoMap } from "./coMap.js";
|
38
|
+
import { type Group } from "./group.js";
|
39
|
+
import { createInboxRoot } from "./inbox.js";
|
40
|
+
import { Profile } from "./profile.js";
|
41
|
+
import { RegisteredSchemas } from "./registeredSchemas.js";
|
40
42
|
|
41
43
|
/** @category Identity & Permissions */
|
42
44
|
export class Account extends CoValueBase implements CoValue {
|
@@ -59,7 +61,7 @@ export class Account extends CoValueBase implements CoValue {
|
|
59
61
|
optional: false,
|
60
62
|
} satisfies RefEncoded<Profile>,
|
61
63
|
root: {
|
62
|
-
ref: () => CoMap,
|
64
|
+
ref: () => RegisteredSchemas["CoMap"],
|
63
65
|
optional: true,
|
64
66
|
} satisfies RefEncoded<CoMap>,
|
65
67
|
};
|
@@ -234,18 +236,26 @@ export class Account extends CoValueBase implements CoValue {
|
|
234
236
|
return this.toJSON();
|
235
237
|
}
|
236
238
|
|
237
|
-
migrate(
|
238
|
-
this: Account,
|
239
|
-
creationProps?: { name: string },
|
240
|
-
): void | Promise<void> {
|
239
|
+
migrate(this: Account, creationProps?: { name: string }) {
|
241
240
|
if (creationProps) {
|
242
|
-
const profileGroup = Group.create({ owner: this });
|
241
|
+
const profileGroup = RegisteredSchemas["Group"].create({ owner: this });
|
243
242
|
profileGroup.addMember("everyone", "reader");
|
244
243
|
this.profile = Profile.create(
|
245
244
|
{ name: creationProps.name },
|
246
245
|
{ owner: profileGroup },
|
247
246
|
);
|
248
247
|
}
|
248
|
+
|
249
|
+
const node = this._raw.core.node;
|
250
|
+
const profile = node
|
251
|
+
.expectCoValueLoaded(this._raw.get("profile")!)
|
252
|
+
.getCurrentContent() as RawCoMap;
|
253
|
+
|
254
|
+
if (!profile.get("inbox")) {
|
255
|
+
const inboxRoot = createInboxRoot(this);
|
256
|
+
profile.set("inbox", inboxRoot.id);
|
257
|
+
profile.set("inboxInvite", inboxRoot.inviteLink);
|
258
|
+
}
|
249
259
|
}
|
250
260
|
|
251
261
|
/** @category Subscription & Loading */
|
@@ -388,3 +398,5 @@ export function isControlledAccount(account: Account): account is Account & {
|
|
388
398
|
export type AccountClass<Acc extends Account> = CoValueClass<Acc> & {
|
389
399
|
fromNode: (typeof Account)["fromNode"];
|
390
400
|
};
|
401
|
+
|
402
|
+
RegisteredSchemas["Account"] = Account;
|
package/src/coValues/coFeed.ts
CHANGED
@@ -16,7 +16,6 @@ import type {
|
|
16
16
|
CoValueClass,
|
17
17
|
DeeplyLoaded,
|
18
18
|
DepthsIn,
|
19
|
-
Group,
|
20
19
|
ID,
|
21
20
|
IfCo,
|
22
21
|
Schema,
|
@@ -24,7 +23,6 @@ import type {
|
|
24
23
|
UnCo,
|
25
24
|
} from "../internal.js";
|
26
25
|
import {
|
27
|
-
Account,
|
28
26
|
CoValueBase,
|
29
27
|
ItemsSym,
|
30
28
|
Ref,
|
@@ -37,6 +35,9 @@ import {
|
|
37
35
|
subscribeToCoValue,
|
38
36
|
subscribeToExistingCoValue,
|
39
37
|
} from "../internal.js";
|
38
|
+
import { type Account } from "./account.js";
|
39
|
+
import { type Group } from "./group.js";
|
40
|
+
import { RegisteredSchemas } from "./registeredSchemas.js";
|
40
41
|
|
41
42
|
/** @deprecated Use CoFeedEntry instead */
|
42
43
|
export type CoStreamEntry<Item> = CoFeedEntry<Item>;
|
@@ -436,7 +437,7 @@ function entryFromRawEntry<Item>(
|
|
436
437
|
return (
|
437
438
|
accountID &&
|
438
439
|
new Ref<Account>(accountID as unknown as ID<Account>, loadedAs, {
|
439
|
-
ref: Account,
|
440
|
+
ref: RegisteredSchemas["Account"],
|
440
441
|
optional: false,
|
441
442
|
})?.accessFrom(
|
442
443
|
accessFrom,
|
package/src/coValues/coList.ts
CHANGED
@@ -13,9 +13,7 @@ import type {
|
|
13
13
|
UnCo,
|
14
14
|
} from "../internal.js";
|
15
15
|
import {
|
16
|
-
Account,
|
17
16
|
AnonymousJazzAgent,
|
18
|
-
Group,
|
19
17
|
ItemsSym,
|
20
18
|
Ref,
|
21
19
|
SchemaInit,
|
@@ -30,6 +28,9 @@ import {
|
|
30
28
|
subscriptionsScopes,
|
31
29
|
} from "../internal.js";
|
32
30
|
import { coValuesCache } from "../lib/cache.js";
|
31
|
+
import { type Account } from "./account.js";
|
32
|
+
import { type Group } from "./group.js";
|
33
|
+
import { RegisteredSchemas } from "./registeredSchemas.js";
|
33
34
|
|
34
35
|
/**
|
35
36
|
* CoLists are collaborative versions of plain arrays.
|
@@ -108,8 +109,8 @@ export class CoList<Item = any> extends Array<Item> implements CoValue {
|
|
108
109
|
/** @category Collaboration */
|
109
110
|
get _owner(): Account | Group {
|
110
111
|
return this._raw.group instanceof RawAccount
|
111
|
-
? Account.fromRaw(this._raw.group)
|
112
|
-
: Group.fromRaw(this._raw.group);
|
112
|
+
? RegisteredSchemas["Account"].fromRaw(this._raw.group)
|
113
|
+
: RegisteredSchemas["Group"].fromRaw(this._raw.group);
|
113
114
|
}
|
114
115
|
|
115
116
|
/**
|
@@ -162,7 +163,9 @@ export class CoList<Item = any> extends Array<Item> implements CoValue {
|
|
162
163
|
const rawAccount = this._raw.core.node.account;
|
163
164
|
|
164
165
|
if (rawAccount instanceof RawAccount) {
|
165
|
-
return coValuesCache.get(rawAccount, () =>
|
166
|
+
return coValuesCache.get(rawAccount, () =>
|
167
|
+
RegisteredSchemas["Account"].fromRaw(rawAccount),
|
168
|
+
);
|
166
169
|
}
|
167
170
|
|
168
171
|
return new AnonymousJazzAgent(this._raw.core.node);
|
package/src/coValues/coMap.ts
CHANGED
@@ -13,7 +13,6 @@ import type {
|
|
13
13
|
CoValueClass,
|
14
14
|
DeeplyLoaded,
|
15
15
|
DepthsIn,
|
16
|
-
Group,
|
17
16
|
ID,
|
18
17
|
IfCo,
|
19
18
|
RefEncoded,
|
@@ -22,7 +21,6 @@ import type {
|
|
22
21
|
co,
|
23
22
|
} from "../internal.js";
|
24
23
|
import {
|
25
|
-
Account,
|
26
24
|
CoValueBase,
|
27
25
|
ItemsSym,
|
28
26
|
Ref,
|
@@ -36,6 +34,9 @@ import {
|
|
36
34
|
subscribeToExistingCoValue,
|
37
35
|
subscriptionsScopes,
|
38
36
|
} from "../internal.js";
|
37
|
+
import { type Account } from "./account.js";
|
38
|
+
import { type Group } from "./group.js";
|
39
|
+
import { RegisteredSchemas } from "./registeredSchemas.js";
|
39
40
|
|
40
41
|
type CoMapEdit<V> = {
|
41
42
|
value?: V;
|
@@ -178,7 +179,7 @@ export class CoMap extends CoValueBase implements CoValue {
|
|
178
179
|
by:
|
179
180
|
rawEdit.by &&
|
180
181
|
new Ref<Account>(rawEdit.by as ID<Account>, target._loadedAs, {
|
181
|
-
ref: Account,
|
182
|
+
ref: RegisteredSchemas["Account"],
|
182
183
|
optional: false,
|
183
184
|
}).accessFrom(target, "_edits." + key + ".by"),
|
184
185
|
madeAt: rawEdit.at,
|
@@ -742,3 +743,5 @@ const CoMapProxyHandler: ProxyHandler<CoMap> = {
|
|
742
743
|
}
|
743
744
|
},
|
744
745
|
};
|
746
|
+
|
747
|
+
RegisteredSchemas["CoMap"] = CoMap;
|
@@ -1,16 +1,10 @@
|
|
1
1
|
import { SessionID } from "cojson";
|
2
|
-
import {
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
Ref,
|
9
|
-
RefEncoded,
|
10
|
-
UnCo,
|
11
|
-
} from "../internal.js";
|
12
|
-
import { CoKeys, CoMap } from "./coMap.js";
|
13
|
-
import { CoValue, ID } from "./interfaces.js";
|
2
|
+
import { ItemsSym, type Ref, RefEncoded, UnCo } from "../internal.js";
|
3
|
+
import { type Account } from "./account.js";
|
4
|
+
import { type CoFeed, CoFeedEntry } from "./coFeed.js";
|
5
|
+
import { type CoList } from "./coList.js";
|
6
|
+
import { type CoKeys, type CoMap } from "./coMap.js";
|
7
|
+
import { type CoValue, type ID } from "./interfaces.js";
|
14
8
|
|
15
9
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
16
10
|
export function fulfillsDepth(depth: any, value: CoValue): boolean {
|
@@ -1,4 +1,6 @@
|
|
1
|
-
import {
|
1
|
+
import { co, subscriptionsScopes } from "../../internal.js";
|
2
|
+
import { FileStream } from "../coFeed.js";
|
3
|
+
import { CoMap } from "../coMap.js";
|
2
4
|
|
3
5
|
/** @category Media */
|
4
6
|
export class ImageDefinition extends CoMap {
|
package/src/coValues/group.ts
CHANGED
@@ -9,24 +9,19 @@ import type {
|
|
9
9
|
Schema,
|
10
10
|
} from "../internal.js";
|
11
11
|
import {
|
12
|
-
Account,
|
13
|
-
AccountAndGroupProxyHandler,
|
14
|
-
CoMap,
|
15
12
|
CoValueBase,
|
16
13
|
MembersSym,
|
17
14
|
Ref,
|
18
|
-
co,
|
19
15
|
ensureCoValueLoaded,
|
20
|
-
isControlledAccount,
|
21
16
|
loadCoValue,
|
22
17
|
subscribeToCoValue,
|
23
18
|
subscribeToExistingCoValue,
|
24
19
|
} from "../internal.js";
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
}
|
20
|
+
import { AccountAndGroupProxyHandler, isControlledAccount } from "./account.js";
|
21
|
+
import { type Account } from "./account.js";
|
22
|
+
import { type CoMap } from "./coMap.js";
|
23
|
+
import { type Profile } from "./profile.js";
|
24
|
+
import { RegisteredSchemas } from "./registeredSchemas.js";
|
30
25
|
|
31
26
|
/** @category Identity & Permissions */
|
32
27
|
export class Group extends CoValueBase implements CoValue {
|
@@ -51,7 +46,7 @@ export class Group extends CoValueBase implements CoValue {
|
|
51
46
|
profile: "json" satisfies Schema,
|
52
47
|
root: "json" satisfies Schema,
|
53
48
|
[MembersSym]: {
|
54
|
-
ref: () => Account,
|
49
|
+
ref: () => RegisteredSchemas["Account"],
|
55
50
|
optional: false,
|
56
51
|
} satisfies RefEncoded<Account>,
|
57
52
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
@@ -229,3 +224,5 @@ export class Group extends CoValueBase implements CoValue {
|
|
229
224
|
return this._raw.core.waitForSync(options);
|
230
225
|
}
|
231
226
|
}
|
227
|
+
|
228
|
+
RegisteredSchemas["Group"] = Group;
|
@@ -0,0 +1,377 @@
|
|
1
|
+
import {
|
2
|
+
CoID,
|
3
|
+
InviteSecret,
|
4
|
+
RawAccount,
|
5
|
+
RawCoMap,
|
6
|
+
RawControlledAccount,
|
7
|
+
SessionID,
|
8
|
+
} from "cojson";
|
9
|
+
import { CoStreamItem, RawCoStream } from "cojson";
|
10
|
+
import { type Account } from "./account.js";
|
11
|
+
import { CoValue, CoValueClass, ID, loadCoValue } from "./interfaces.js";
|
12
|
+
|
13
|
+
export type InboxInvite = `${CoID<MessagesStream>}/${InviteSecret}`;
|
14
|
+
type TxKey = `${SessionID}/${number}`;
|
15
|
+
|
16
|
+
type MessagesStream = RawCoStream<CoID<InboxMessage<CoValue, any>>>;
|
17
|
+
type FailedMessagesStream = RawCoStream<{
|
18
|
+
errors: string[];
|
19
|
+
value: CoID<InboxMessage<CoValue, any>>;
|
20
|
+
}>;
|
21
|
+
type TxKeyStream = RawCoStream<TxKey>;
|
22
|
+
export type InboxRoot = RawCoMap<{
|
23
|
+
messages: CoID<MessagesStream>;
|
24
|
+
processed: CoID<TxKeyStream>;
|
25
|
+
failed: CoID<FailedMessagesStream>;
|
26
|
+
inviteLink: InboxInvite;
|
27
|
+
}>;
|
28
|
+
|
29
|
+
export function createInboxRoot(account: Account) {
|
30
|
+
if (!account.isMe) {
|
31
|
+
throw new Error("Account is not controlled");
|
32
|
+
}
|
33
|
+
|
34
|
+
const rawAccount = account._raw as RawControlledAccount;
|
35
|
+
|
36
|
+
const group = rawAccount.createGroup();
|
37
|
+
const messagesFeed = group.createStream<MessagesStream>();
|
38
|
+
|
39
|
+
const inboxRoot = rawAccount.createMap<InboxRoot>();
|
40
|
+
const processedFeed = rawAccount.createStream<TxKeyStream>();
|
41
|
+
const failedFeed = rawAccount.createStream<FailedMessagesStream>();
|
42
|
+
|
43
|
+
const inviteLink =
|
44
|
+
`${messagesFeed.id}/${group.createInvite("writeOnly")}` as const;
|
45
|
+
|
46
|
+
inboxRoot.set("messages", messagesFeed.id);
|
47
|
+
inboxRoot.set("processed", processedFeed.id);
|
48
|
+
inboxRoot.set("failed", failedFeed.id);
|
49
|
+
|
50
|
+
return {
|
51
|
+
id: inboxRoot.id,
|
52
|
+
inviteLink,
|
53
|
+
};
|
54
|
+
}
|
55
|
+
|
56
|
+
type InboxMessage<I extends CoValue, O extends CoValue | undefined> = RawCoMap<{
|
57
|
+
payload: ID<I>;
|
58
|
+
result: ID<O> | undefined;
|
59
|
+
processed: boolean;
|
60
|
+
error: string | undefined;
|
61
|
+
}>;
|
62
|
+
|
63
|
+
function createInboxMessage<I extends CoValue, O extends CoValue | undefined>(
|
64
|
+
payload: I,
|
65
|
+
inboxOwner: RawAccount,
|
66
|
+
) {
|
67
|
+
const group = payload._raw.group;
|
68
|
+
|
69
|
+
if (group instanceof RawAccount) {
|
70
|
+
throw new Error("Inbox messages should be owned by a group");
|
71
|
+
}
|
72
|
+
|
73
|
+
group.addMember(inboxOwner, "writer");
|
74
|
+
|
75
|
+
const message = group.createMap<InboxMessage<I, O>>({
|
76
|
+
payload: payload.id,
|
77
|
+
result: undefined,
|
78
|
+
processed: false,
|
79
|
+
error: undefined,
|
80
|
+
});
|
81
|
+
|
82
|
+
return message;
|
83
|
+
}
|
84
|
+
|
85
|
+
export class Inbox {
|
86
|
+
account: Account;
|
87
|
+
messages: MessagesStream;
|
88
|
+
processed: TxKeyStream;
|
89
|
+
failed: FailedMessagesStream;
|
90
|
+
root: InboxRoot;
|
91
|
+
processing = new Set<`${SessionID}/${number}`>();
|
92
|
+
|
93
|
+
private constructor(
|
94
|
+
account: Account,
|
95
|
+
root: InboxRoot,
|
96
|
+
messages: MessagesStream,
|
97
|
+
processed: TxKeyStream,
|
98
|
+
failed: FailedMessagesStream,
|
99
|
+
) {
|
100
|
+
this.account = account;
|
101
|
+
this.root = root;
|
102
|
+
this.messages = messages;
|
103
|
+
this.processed = processed;
|
104
|
+
this.failed = failed;
|
105
|
+
}
|
106
|
+
|
107
|
+
subscribe<I extends CoValue, O extends CoValue | undefined>(
|
108
|
+
Schema: CoValueClass<I>,
|
109
|
+
callback: (
|
110
|
+
message: I,
|
111
|
+
senderAccountID: ID<Account>,
|
112
|
+
) => Promise<O | undefined | void>,
|
113
|
+
options: { retries?: number } = {},
|
114
|
+
) {
|
115
|
+
const processed = new Set<`${SessionID}/${number}`>();
|
116
|
+
const failed = new Map<`${SessionID}/${number}`, string[]>();
|
117
|
+
const node = this.account._raw.core.node;
|
118
|
+
|
119
|
+
this.processed.subscribe((stream) => {
|
120
|
+
for (const items of Object.values(stream.items)) {
|
121
|
+
for (const item of items) {
|
122
|
+
processed.add(item.value as TxKey);
|
123
|
+
}
|
124
|
+
}
|
125
|
+
});
|
126
|
+
|
127
|
+
const { account } = this;
|
128
|
+
const { retries = 3 } = options;
|
129
|
+
|
130
|
+
let failTimer: ReturnType<typeof setTimeout> | number | undefined =
|
131
|
+
undefined;
|
132
|
+
|
133
|
+
const clearFailTimer = () => {
|
134
|
+
clearTimeout(failTimer);
|
135
|
+
failTimer = undefined;
|
136
|
+
};
|
137
|
+
|
138
|
+
const handleNewMessages = (stream: MessagesStream) => {
|
139
|
+
clearFailTimer(); // Stop the failure timers, we're going to process the failed entries anyway
|
140
|
+
|
141
|
+
for (const [sessionID, items] of Object.entries(stream.items) as [
|
142
|
+
SessionID,
|
143
|
+
CoStreamItem<CoID<InboxMessage<I, O>>>[],
|
144
|
+
][]) {
|
145
|
+
const accountID = getAccountIDfromSessionID(sessionID);
|
146
|
+
|
147
|
+
if (!accountID) {
|
148
|
+
console.warn("Received message from unknown account", sessionID);
|
149
|
+
continue;
|
150
|
+
}
|
151
|
+
|
152
|
+
for (const item of items) {
|
153
|
+
const txKey = `${sessionID}/${item.tx.txIndex}` as const;
|
154
|
+
|
155
|
+
if (!processed.has(txKey) && !this.processing.has(txKey)) {
|
156
|
+
this.processing.add(txKey);
|
157
|
+
|
158
|
+
const id = item.value;
|
159
|
+
|
160
|
+
node
|
161
|
+
.load(id)
|
162
|
+
.then((message) => {
|
163
|
+
if (message === "unavailable") {
|
164
|
+
return Promise.reject(
|
165
|
+
new Error("Unable to load inbox message " + id),
|
166
|
+
);
|
167
|
+
}
|
168
|
+
|
169
|
+
return loadCoValue(
|
170
|
+
Schema,
|
171
|
+
message.get("payload") as ID<I>,
|
172
|
+
account,
|
173
|
+
[],
|
174
|
+
);
|
175
|
+
})
|
176
|
+
.then((value) => {
|
177
|
+
if (!value) {
|
178
|
+
return Promise.reject(
|
179
|
+
new Error("Unable to load inbox message " + id),
|
180
|
+
);
|
181
|
+
}
|
182
|
+
|
183
|
+
return callback(value, accountID);
|
184
|
+
})
|
185
|
+
.then((result) => {
|
186
|
+
const inboxMessage = node
|
187
|
+
.expectCoValueLoaded(item.value)
|
188
|
+
.getCurrentContent() as RawCoMap;
|
189
|
+
|
190
|
+
if (result) {
|
191
|
+
inboxMessage.set("result", result.id);
|
192
|
+
}
|
193
|
+
|
194
|
+
inboxMessage.set("processed", true);
|
195
|
+
|
196
|
+
this.processed.push(txKey);
|
197
|
+
this.processing.delete(txKey);
|
198
|
+
})
|
199
|
+
.catch((error) => {
|
200
|
+
console.error("Error processing inbox message", error);
|
201
|
+
this.processing.delete(txKey);
|
202
|
+
const errors = failed.get(txKey) ?? [];
|
203
|
+
|
204
|
+
const stringifiedError = String(error);
|
205
|
+
errors.push(stringifiedError);
|
206
|
+
|
207
|
+
const inboxMessage = node
|
208
|
+
.expectCoValueLoaded(item.value)
|
209
|
+
.getCurrentContent() as RawCoMap;
|
210
|
+
|
211
|
+
inboxMessage.set("error", stringifiedError);
|
212
|
+
|
213
|
+
if (errors.length > retries) {
|
214
|
+
inboxMessage.set("processed", true);
|
215
|
+
this.processed.push(txKey);
|
216
|
+
this.failed.push({ errors, value: item.value });
|
217
|
+
} else {
|
218
|
+
failed.set(txKey, errors);
|
219
|
+
if (!failTimer) {
|
220
|
+
failTimer = setTimeout(
|
221
|
+
() => handleNewMessages(stream),
|
222
|
+
100,
|
223
|
+
);
|
224
|
+
}
|
225
|
+
}
|
226
|
+
});
|
227
|
+
}
|
228
|
+
}
|
229
|
+
}
|
230
|
+
};
|
231
|
+
|
232
|
+
return this.messages.subscribe(handleNewMessages);
|
233
|
+
}
|
234
|
+
|
235
|
+
static async load(account: Account) {
|
236
|
+
const profile = account.profile;
|
237
|
+
|
238
|
+
if (!profile) {
|
239
|
+
throw new Error("Account profile should already be loaded");
|
240
|
+
}
|
241
|
+
|
242
|
+
if (!profile.inbox) {
|
243
|
+
throw new Error("The account has not set up their inbox");
|
244
|
+
}
|
245
|
+
|
246
|
+
const node = account._raw.core.node;
|
247
|
+
|
248
|
+
const root = await node.load(profile.inbox as CoID<InboxRoot>);
|
249
|
+
|
250
|
+
if (root === "unavailable") {
|
251
|
+
throw new Error("Inbox not found");
|
252
|
+
}
|
253
|
+
|
254
|
+
const [messages, processed, failed] = await Promise.all([
|
255
|
+
node.load(root.get("messages")!),
|
256
|
+
node.load(root.get("processed")!),
|
257
|
+
node.load(root.get("failed")!),
|
258
|
+
]);
|
259
|
+
|
260
|
+
if (
|
261
|
+
messages === "unavailable" ||
|
262
|
+
processed === "unavailable" ||
|
263
|
+
failed === "unavailable"
|
264
|
+
) {
|
265
|
+
throw new Error("Inbox not found");
|
266
|
+
}
|
267
|
+
|
268
|
+
return new Inbox(account, root, messages, processed, failed);
|
269
|
+
}
|
270
|
+
}
|
271
|
+
|
272
|
+
export class InboxSender<I extends CoValue, O extends CoValue | undefined> {
|
273
|
+
currentAccount: Account;
|
274
|
+
owner: RawAccount;
|
275
|
+
messages: MessagesStream;
|
276
|
+
|
277
|
+
private constructor(
|
278
|
+
currentAccount: Account,
|
279
|
+
owner: RawAccount,
|
280
|
+
messages: MessagesStream,
|
281
|
+
) {
|
282
|
+
this.currentAccount = currentAccount;
|
283
|
+
this.owner = owner;
|
284
|
+
this.messages = messages;
|
285
|
+
}
|
286
|
+
|
287
|
+
getOwnerAccount() {
|
288
|
+
return this.owner;
|
289
|
+
}
|
290
|
+
|
291
|
+
sendMessage(message: I): Promise<O extends CoValue ? ID<O> : undefined> {
|
292
|
+
const inboxMessage = createInboxMessage<I, O>(message, this.owner);
|
293
|
+
|
294
|
+
this.messages.push(inboxMessage.id);
|
295
|
+
|
296
|
+
return new Promise((resolve, reject) => {
|
297
|
+
inboxMessage.subscribe((message) => {
|
298
|
+
if (message.get("processed")) {
|
299
|
+
const error = message.get("error");
|
300
|
+
if (error) {
|
301
|
+
reject(new Error(error));
|
302
|
+
} else {
|
303
|
+
resolve(
|
304
|
+
message.get("result") as O extends CoValue ? ID<O> : undefined,
|
305
|
+
);
|
306
|
+
}
|
307
|
+
}
|
308
|
+
});
|
309
|
+
});
|
310
|
+
}
|
311
|
+
|
312
|
+
static async load<
|
313
|
+
I extends CoValue,
|
314
|
+
O extends CoValue | undefined = undefined,
|
315
|
+
>(inboxOwnerID: ID<Account>, currentAccount: Account) {
|
316
|
+
const node = currentAccount._raw.core.node;
|
317
|
+
|
318
|
+
const inboxOwnerRaw = await node.load(
|
319
|
+
inboxOwnerID as unknown as CoID<RawAccount>,
|
320
|
+
);
|
321
|
+
|
322
|
+
if (inboxOwnerRaw === "unavailable") {
|
323
|
+
throw new Error("Failed to load the inbox owner");
|
324
|
+
}
|
325
|
+
|
326
|
+
const inboxOwnerProfileRaw = await node.load(inboxOwnerRaw.get("profile")!);
|
327
|
+
|
328
|
+
if (inboxOwnerProfileRaw === "unavailable") {
|
329
|
+
throw new Error("Failed to load the inbox owner profile");
|
330
|
+
}
|
331
|
+
|
332
|
+
const inboxInvite = inboxOwnerProfileRaw.get("inboxInvite");
|
333
|
+
|
334
|
+
if (!inboxInvite) {
|
335
|
+
throw new Error("The user has not set up their inbox");
|
336
|
+
}
|
337
|
+
|
338
|
+
const id = await acceptInvite(inboxInvite as InboxInvite, currentAccount);
|
339
|
+
|
340
|
+
const messages = await node.load(id);
|
341
|
+
|
342
|
+
if (messages === "unavailable") {
|
343
|
+
throw new Error("Inbox not found");
|
344
|
+
}
|
345
|
+
|
346
|
+
return new InboxSender<I, O>(currentAccount, inboxOwnerRaw, messages);
|
347
|
+
}
|
348
|
+
}
|
349
|
+
|
350
|
+
async function acceptInvite(invite: string, account: Account) {
|
351
|
+
const id = invite.slice(0, invite.indexOf("/")) as CoID<MessagesStream>;
|
352
|
+
|
353
|
+
const inviteSecret = invite.slice(invite.indexOf("/") + 1) as InviteSecret;
|
354
|
+
|
355
|
+
if (!id?.startsWith("co_z") || !inviteSecret.startsWith("inviteSecret_")) {
|
356
|
+
throw new Error("Invalid inbox ticket");
|
357
|
+
}
|
358
|
+
|
359
|
+
if (!account.isMe) {
|
360
|
+
throw new Error("Account is not controlled");
|
361
|
+
}
|
362
|
+
|
363
|
+
await (account._raw as RawControlledAccount).acceptInvite(id, inviteSecret);
|
364
|
+
|
365
|
+
return id;
|
366
|
+
}
|
367
|
+
|
368
|
+
function getAccountIDfromSessionID(sessionID: SessionID) {
|
369
|
+
const until = sessionID.indexOf("_session");
|
370
|
+
const accountID = sessionID.slice(0, until);
|
371
|
+
|
372
|
+
if (accountID.startsWith("co_z")) {
|
373
|
+
return accountID as ID<Account>;
|
374
|
+
}
|
375
|
+
|
376
|
+
return;
|
377
|
+
}
|