@graffiti-garden/implementation-decentralized 0.0.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/LICENSE +674 -0
- package/dist/1-services/1-authorization.d.ts +37 -0
- package/dist/1-services/1-authorization.d.ts.map +1 -0
- package/dist/1-services/2-dids-tests.d.ts +2 -0
- package/dist/1-services/2-dids-tests.d.ts.map +1 -0
- package/dist/1-services/2-dids.d.ts +9 -0
- package/dist/1-services/2-dids.d.ts.map +1 -0
- package/dist/1-services/3-storage-buckets-tests.d.ts +2 -0
- package/dist/1-services/3-storage-buckets-tests.d.ts.map +1 -0
- package/dist/1-services/3-storage-buckets.d.ts +11 -0
- package/dist/1-services/3-storage-buckets.d.ts.map +1 -0
- package/dist/1-services/4-inboxes-tests.d.ts +2 -0
- package/dist/1-services/4-inboxes-tests.d.ts.map +1 -0
- package/dist/1-services/4-inboxes.d.ts +87 -0
- package/dist/1-services/4-inboxes.d.ts.map +1 -0
- package/dist/1-services/utilities.d.ts +7 -0
- package/dist/1-services/utilities.d.ts.map +1 -0
- package/dist/2-primitives/1-string-encoding-tests.d.ts +2 -0
- package/dist/2-primitives/1-string-encoding-tests.d.ts.map +1 -0
- package/dist/2-primitives/1-string-encoding.d.ts +6 -0
- package/dist/2-primitives/1-string-encoding.d.ts.map +1 -0
- package/dist/2-primitives/2-content-addresses-tests.d.ts +2 -0
- package/dist/2-primitives/2-content-addresses-tests.d.ts.map +1 -0
- package/dist/2-primitives/2-content-addresses.d.ts +8 -0
- package/dist/2-primitives/2-content-addresses.d.ts.map +1 -0
- package/dist/2-primitives/3-channel-attestations-tests.d.ts +2 -0
- package/dist/2-primitives/3-channel-attestations-tests.d.ts.map +1 -0
- package/dist/2-primitives/3-channel-attestations.d.ts +13 -0
- package/dist/2-primitives/3-channel-attestations.d.ts.map +1 -0
- package/dist/2-primitives/4-allowed-attestations-tests.d.ts +2 -0
- package/dist/2-primitives/4-allowed-attestations-tests.d.ts.map +1 -0
- package/dist/2-primitives/4-allowed-attestations.d.ts +9 -0
- package/dist/2-primitives/4-allowed-attestations.d.ts.map +1 -0
- package/dist/3-protocol/1-sessions.d.ts +81 -0
- package/dist/3-protocol/1-sessions.d.ts.map +1 -0
- package/dist/3-protocol/2-handles-tests.d.ts +2 -0
- package/dist/3-protocol/2-handles-tests.d.ts.map +1 -0
- package/dist/3-protocol/2-handles.d.ts +13 -0
- package/dist/3-protocol/2-handles.d.ts.map +1 -0
- package/dist/3-protocol/3-object-encoding-tests.d.ts +2 -0
- package/dist/3-protocol/3-object-encoding-tests.d.ts.map +1 -0
- package/dist/3-protocol/3-object-encoding.d.ts +43 -0
- package/dist/3-protocol/3-object-encoding.d.ts.map +1 -0
- package/dist/3-protocol/4-graffiti.d.ts +79 -0
- package/dist/3-protocol/4-graffiti.d.ts.map +1 -0
- package/dist/3-protocol/login-dialog.html.d.ts +2 -0
- package/dist/3-protocol/login-dialog.html.d.ts.map +1 -0
- package/dist/browser/ajv-QBSREQSI.js +9 -0
- package/dist/browser/ajv-QBSREQSI.js.map +7 -0
- package/dist/browser/build-BXWPS7VK.js +2 -0
- package/dist/browser/build-BXWPS7VK.js.map +7 -0
- package/dist/browser/chunk-RFBBAUMM.js +2 -0
- package/dist/browser/chunk-RFBBAUMM.js.map +7 -0
- package/dist/browser/graffiti-KV3G3O72-URO7SJIJ.js +2 -0
- package/dist/browser/graffiti-KV3G3O72-URO7SJIJ.js.map +7 -0
- package/dist/browser/index.js +16 -0
- package/dist/browser/index.js.map +7 -0
- package/dist/browser/login-dialog.html-XUWYDNNI.js +44 -0
- package/dist/browser/login-dialog.html-XUWYDNNI.js.map +7 -0
- package/dist/browser/rock-salt-LI7DAH66-KPFEBIBO.js +2 -0
- package/dist/browser/rock-salt-LI7DAH66-KPFEBIBO.js.map +7 -0
- package/dist/browser/style-YUTCEBZV-RWYJV575.js +287 -0
- package/dist/browser/style-YUTCEBZV-RWYJV575.js.map +7 -0
- package/dist/cjs/1-services/1-authorization.js +317 -0
- package/dist/cjs/1-services/1-authorization.js.map +7 -0
- package/dist/cjs/1-services/2-dids-tests.js +44 -0
- package/dist/cjs/1-services/2-dids-tests.js.map +7 -0
- package/dist/cjs/1-services/2-dids.js +47 -0
- package/dist/cjs/1-services/2-dids.js.map +7 -0
- package/dist/cjs/1-services/3-storage-buckets-tests.js +123 -0
- package/dist/cjs/1-services/3-storage-buckets-tests.js.map +7 -0
- package/dist/cjs/1-services/3-storage-buckets.js +148 -0
- package/dist/cjs/1-services/3-storage-buckets.js.map +7 -0
- package/dist/cjs/1-services/4-inboxes-tests.js +145 -0
- package/dist/cjs/1-services/4-inboxes-tests.js.map +7 -0
- package/dist/cjs/1-services/4-inboxes.js +539 -0
- package/dist/cjs/1-services/4-inboxes.js.map +7 -0
- package/dist/cjs/1-services/utilities.js +75 -0
- package/dist/cjs/1-services/utilities.js.map +7 -0
- package/dist/cjs/2-primitives/1-string-encoding-tests.js +50 -0
- package/dist/cjs/2-primitives/1-string-encoding-tests.js.map +7 -0
- package/dist/cjs/2-primitives/1-string-encoding.js +46 -0
- package/dist/cjs/2-primitives/1-string-encoding.js.map +7 -0
- package/dist/cjs/2-primitives/2-content-addresses-tests.js +62 -0
- package/dist/cjs/2-primitives/2-content-addresses-tests.js.map +7 -0
- package/dist/cjs/2-primitives/2-content-addresses.js +53 -0
- package/dist/cjs/2-primitives/2-content-addresses.js.map +7 -0
- package/dist/cjs/2-primitives/3-channel-attestations-tests.js +130 -0
- package/dist/cjs/2-primitives/3-channel-attestations-tests.js.map +7 -0
- package/dist/cjs/2-primitives/3-channel-attestations.js +84 -0
- package/dist/cjs/2-primitives/3-channel-attestations.js.map +7 -0
- package/dist/cjs/2-primitives/4-allowed-attestations-tests.js +96 -0
- package/dist/cjs/2-primitives/4-allowed-attestations-tests.js.map +7 -0
- package/dist/cjs/2-primitives/4-allowed-attestations.js +68 -0
- package/dist/cjs/2-primitives/4-allowed-attestations.js.map +7 -0
- package/dist/cjs/3-protocol/1-sessions.js +473 -0
- package/dist/cjs/3-protocol/1-sessions.js.map +7 -0
- package/dist/cjs/3-protocol/2-handles-tests.js +39 -0
- package/dist/cjs/3-protocol/2-handles-tests.js.map +7 -0
- package/dist/cjs/3-protocol/2-handles.js +65 -0
- package/dist/cjs/3-protocol/2-handles.js.map +7 -0
- package/dist/cjs/3-protocol/3-object-encoding-tests.js +253 -0
- package/dist/cjs/3-protocol/3-object-encoding-tests.js.map +7 -0
- package/dist/cjs/3-protocol/3-object-encoding.js +287 -0
- package/dist/cjs/3-protocol/3-object-encoding.js.map +7 -0
- package/dist/cjs/3-protocol/4-graffiti.js +937 -0
- package/dist/cjs/3-protocol/4-graffiti.js.map +7 -0
- package/dist/cjs/3-protocol/login-dialog.html.js +67 -0
- package/dist/cjs/3-protocol/login-dialog.html.js.map +7 -0
- package/dist/cjs/index.js +32 -0
- package/dist/cjs/index.js.map +7 -0
- package/dist/cjs/index.spec.js +130 -0
- package/dist/cjs/index.spec.js.map +7 -0
- package/dist/esm/1-services/1-authorization.js +304 -0
- package/dist/esm/1-services/1-authorization.js.map +7 -0
- package/dist/esm/1-services/2-dids-tests.js +24 -0
- package/dist/esm/1-services/2-dids-tests.js.map +7 -0
- package/dist/esm/1-services/2-dids.js +27 -0
- package/dist/esm/1-services/2-dids.js.map +7 -0
- package/dist/esm/1-services/3-storage-buckets-tests.js +103 -0
- package/dist/esm/1-services/3-storage-buckets-tests.js.map +7 -0
- package/dist/esm/1-services/3-storage-buckets.js +132 -0
- package/dist/esm/1-services/3-storage-buckets.js.map +7 -0
- package/dist/esm/1-services/4-inboxes-tests.js +125 -0
- package/dist/esm/1-services/4-inboxes-tests.js.map +7 -0
- package/dist/esm/1-services/4-inboxes.js +533 -0
- package/dist/esm/1-services/4-inboxes.js.map +7 -0
- package/dist/esm/1-services/utilities.js +60 -0
- package/dist/esm/1-services/utilities.js.map +7 -0
- package/dist/esm/2-primitives/1-string-encoding-tests.js +33 -0
- package/dist/esm/2-primitives/1-string-encoding-tests.js.map +7 -0
- package/dist/esm/2-primitives/1-string-encoding.js +26 -0
- package/dist/esm/2-primitives/1-string-encoding.js.map +7 -0
- package/dist/esm/2-primitives/2-content-addresses-tests.js +45 -0
- package/dist/esm/2-primitives/2-content-addresses-tests.js.map +7 -0
- package/dist/esm/2-primitives/2-content-addresses.js +33 -0
- package/dist/esm/2-primitives/2-content-addresses.js.map +7 -0
- package/dist/esm/2-primitives/3-channel-attestations-tests.js +116 -0
- package/dist/esm/2-primitives/3-channel-attestations-tests.js.map +7 -0
- package/dist/esm/2-primitives/3-channel-attestations.js +69 -0
- package/dist/esm/2-primitives/3-channel-attestations.js.map +7 -0
- package/dist/esm/2-primitives/4-allowed-attestations-tests.js +82 -0
- package/dist/esm/2-primitives/4-allowed-attestations-tests.js.map +7 -0
- package/dist/esm/2-primitives/4-allowed-attestations.js +51 -0
- package/dist/esm/2-primitives/4-allowed-attestations.js.map +7 -0
- package/dist/esm/3-protocol/1-sessions.js +465 -0
- package/dist/esm/3-protocol/1-sessions.js.map +7 -0
- package/dist/esm/3-protocol/2-handles-tests.js +19 -0
- package/dist/esm/3-protocol/2-handles-tests.js.map +7 -0
- package/dist/esm/3-protocol/2-handles.js +45 -0
- package/dist/esm/3-protocol/2-handles.js.map +7 -0
- package/dist/esm/3-protocol/3-object-encoding-tests.js +248 -0
- package/dist/esm/3-protocol/3-object-encoding-tests.js.map +7 -0
- package/dist/esm/3-protocol/3-object-encoding.js +280 -0
- package/dist/esm/3-protocol/3-object-encoding.js.map +7 -0
- package/dist/esm/3-protocol/4-graffiti.js +957 -0
- package/dist/esm/3-protocol/4-graffiti.js.map +7 -0
- package/dist/esm/3-protocol/login-dialog.html.js +47 -0
- package/dist/esm/3-protocol/login-dialog.html.js.map +7 -0
- package/dist/esm/index.js +14 -0
- package/dist/esm/index.js.map +7 -0
- package/dist/esm/index.spec.js +133 -0
- package/dist/esm/index.spec.js.map +7 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.spec.d.ts +2 -0
- package/dist/index.spec.d.ts.map +1 -0
- package/package.json +62 -0
- package/src/1-services/1-authorization.ts +399 -0
- package/src/1-services/2-dids-tests.ts +24 -0
- package/src/1-services/2-dids.ts +30 -0
- package/src/1-services/3-storage-buckets-tests.ts +121 -0
- package/src/1-services/3-storage-buckets.ts +183 -0
- package/src/1-services/4-inboxes-tests.ts +154 -0
- package/src/1-services/4-inboxes.ts +722 -0
- package/src/1-services/utilities.ts +65 -0
- package/src/2-primitives/1-string-encoding-tests.ts +33 -0
- package/src/2-primitives/1-string-encoding.ts +33 -0
- package/src/2-primitives/2-content-addresses-tests.ts +46 -0
- package/src/2-primitives/2-content-addresses.ts +42 -0
- package/src/2-primitives/3-channel-attestations-tests.ts +125 -0
- package/src/2-primitives/3-channel-attestations.ts +95 -0
- package/src/2-primitives/4-allowed-attestations-tests.ts +86 -0
- package/src/2-primitives/4-allowed-attestations.ts +69 -0
- package/src/3-protocol/1-sessions.ts +601 -0
- package/src/3-protocol/2-handles-tests.ts +17 -0
- package/src/3-protocol/2-handles.ts +60 -0
- package/src/3-protocol/3-object-encoding-tests.ts +269 -0
- package/src/3-protocol/3-object-encoding.ts +396 -0
- package/src/3-protocol/4-graffiti.ts +1265 -0
- package/src/3-protocol/login-dialog.html.ts +43 -0
- package/src/index.spec.ts +158 -0
- package/src/index.ts +16 -0
|
@@ -0,0 +1,601 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
Graffiti,
|
|
3
|
+
GraffitiLoginEvent,
|
|
4
|
+
GraffitiLogoutEvent,
|
|
5
|
+
GraffitiSession,
|
|
6
|
+
GraffitiSessionInitializedEvent,
|
|
7
|
+
} from "@graffiti-garden/api";
|
|
8
|
+
import { DecentralizedIdentifiers } from "../1-services/2-dids";
|
|
9
|
+
import {
|
|
10
|
+
InitializedEventDetailSchema,
|
|
11
|
+
LoginEventDetailSchema,
|
|
12
|
+
LogoutEventDetailSchema,
|
|
13
|
+
type Authorization,
|
|
14
|
+
} from "../1-services/1-authorization";
|
|
15
|
+
import { StorageBuckets } from "../1-services/3-storage-buckets";
|
|
16
|
+
import type { Inboxes } from "../1-services/4-inboxes";
|
|
17
|
+
import type { Service } from "did-resolver";
|
|
18
|
+
import {
|
|
19
|
+
type infer as infer_,
|
|
20
|
+
extend,
|
|
21
|
+
array,
|
|
22
|
+
string,
|
|
23
|
+
object,
|
|
24
|
+
url,
|
|
25
|
+
tuple,
|
|
26
|
+
enum as enum_,
|
|
27
|
+
} from "zod/mini";
|
|
28
|
+
|
|
29
|
+
export const DID_SERVICE_TYPE_GRAFFITI_INBOX = "GraffitiInbox";
|
|
30
|
+
export const DID_SERVICE_TYPE_GRAFFITI_STORAGE_BUCKET = "GraffitiStorageBucket";
|
|
31
|
+
export const DID_SERVICE_ID_GRAFFITI_PERSONAL_INBOX = "#graffitiPersonalInbox";
|
|
32
|
+
export const DID_SERVICE_ID_GRAFFITI_STORAGE_BUCKET = "#graffitiStorageBucket";
|
|
33
|
+
export const DID_SERVICE_ID_GRAFFITI_SHARED_INBOX_PREFIX =
|
|
34
|
+
"#graffitiSharedInbox_";
|
|
35
|
+
|
|
36
|
+
export class Sessions {
|
|
37
|
+
sessionEvents: Graffiti["sessionEvents"] = new EventTarget();
|
|
38
|
+
|
|
39
|
+
constructor(
|
|
40
|
+
protected readonly services: {
|
|
41
|
+
readonly dids: DecentralizedIdentifiers;
|
|
42
|
+
readonly authorization: Authorization;
|
|
43
|
+
readonly storageBuckets: StorageBuckets;
|
|
44
|
+
readonly inboxes: Inboxes;
|
|
45
|
+
},
|
|
46
|
+
) {
|
|
47
|
+
const initializedPromise = new Promise<void>((resolve) => {
|
|
48
|
+
this.services.authorization.eventTarget.addEventListener(
|
|
49
|
+
"initialized",
|
|
50
|
+
(e) => {
|
|
51
|
+
if (!(e instanceof CustomEvent)) return;
|
|
52
|
+
const parsed = InitializedEventDetailSchema.safeParse(e.detail);
|
|
53
|
+
if (!parsed.success) return;
|
|
54
|
+
const error = parsed.data?.error;
|
|
55
|
+
if (error) console.log(error);
|
|
56
|
+
resolve();
|
|
57
|
+
},
|
|
58
|
+
);
|
|
59
|
+
});
|
|
60
|
+
this.services.authorization.eventTarget.addEventListener(
|
|
61
|
+
"login",
|
|
62
|
+
this.onLogin.bind(this),
|
|
63
|
+
);
|
|
64
|
+
this.services.authorization.eventTarget.addEventListener(
|
|
65
|
+
"logout",
|
|
66
|
+
this.onLogout.bind(this),
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
// Handle account registration redirect immediately,
|
|
70
|
+
// to prevent SPA routers from hijacking the URL too soon
|
|
71
|
+
let loginPromise: Promise<void> | undefined;
|
|
72
|
+
if (typeof window !== "undefined") {
|
|
73
|
+
const actorEncoded = new URLSearchParams(window.location.search).get(
|
|
74
|
+
"actor",
|
|
75
|
+
);
|
|
76
|
+
if (actorEncoded) {
|
|
77
|
+
try {
|
|
78
|
+
// Get the actor
|
|
79
|
+
const actor = decodeURIComponent(actorEncoded);
|
|
80
|
+
// Strip it from the URL
|
|
81
|
+
const url = new URL(window.location.toString());
|
|
82
|
+
url.searchParams.delete("actor");
|
|
83
|
+
window.history.replaceState({}, "", url.toString());
|
|
84
|
+
// Complete the login
|
|
85
|
+
loginPromise = this.login(actor);
|
|
86
|
+
} catch (error) {
|
|
87
|
+
console.error("Error decoding actor:", error);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
(async () => {
|
|
93
|
+
// Allow listeners to be added before dispatching events
|
|
94
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
95
|
+
|
|
96
|
+
// Wait for login to complete, if there
|
|
97
|
+
await loginPromise;
|
|
98
|
+
|
|
99
|
+
for (const session of this.loggedInSessions) {
|
|
100
|
+
const loginEvent: GraffitiLoginEvent = new CustomEvent("login", {
|
|
101
|
+
detail: { session: { actor: session.actor } },
|
|
102
|
+
});
|
|
103
|
+
this.sessionEvents.dispatchEvent(loginEvent);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
await initializedPromise;
|
|
107
|
+
|
|
108
|
+
// Send own initialized event
|
|
109
|
+
const initializedEvent: GraffitiSessionInitializedEvent = new CustomEvent(
|
|
110
|
+
"initialized",
|
|
111
|
+
);
|
|
112
|
+
this.sessionEvents.dispatchEvent(initializedEvent);
|
|
113
|
+
})();
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
protected inProgressLogin: infer_<typeof InProgressSchema> | undefined =
|
|
117
|
+
undefined;
|
|
118
|
+
protected inProgressLogout: infer_<typeof InProgressSchema> | undefined =
|
|
119
|
+
undefined;
|
|
120
|
+
|
|
121
|
+
async login(actor: string) {
|
|
122
|
+
try {
|
|
123
|
+
await this.login_(actor);
|
|
124
|
+
} catch (e) {
|
|
125
|
+
const loginEvent: GraffitiLoginEvent = new CustomEvent("login", {
|
|
126
|
+
detail: {
|
|
127
|
+
error: e instanceof Error ? e : new Error(String(e)),
|
|
128
|
+
session: { actor },
|
|
129
|
+
},
|
|
130
|
+
});
|
|
131
|
+
this.sessionEvents.dispatchEvent(loginEvent);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
protected async login_(actor: string) {
|
|
135
|
+
// First look to see if we're already logged in
|
|
136
|
+
const existingSession = this.loggedInSessions.find(
|
|
137
|
+
(session) => session.actor === actor,
|
|
138
|
+
);
|
|
139
|
+
if (existingSession) {
|
|
140
|
+
this.sessionEvents.dispatchEvent(
|
|
141
|
+
new CustomEvent("login", { detail: { session: { actor } } }),
|
|
142
|
+
);
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const actorDocument = await this.services.dids.resolve(actor);
|
|
147
|
+
|
|
148
|
+
const services = actorDocument.service;
|
|
149
|
+
if (!services) {
|
|
150
|
+
throw new Error(`No services found in actor document for ${actor}`);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const storageBucketService = services.find(
|
|
154
|
+
(service) =>
|
|
155
|
+
service.id === DID_SERVICE_ID_GRAFFITI_STORAGE_BUCKET &&
|
|
156
|
+
service.type === DID_SERVICE_TYPE_GRAFFITI_STORAGE_BUCKET,
|
|
157
|
+
);
|
|
158
|
+
const personalInboxService = services.find(
|
|
159
|
+
(service) =>
|
|
160
|
+
service.id === DID_SERVICE_ID_GRAFFITI_PERSONAL_INBOX &&
|
|
161
|
+
service.type === DID_SERVICE_TYPE_GRAFFITI_INBOX,
|
|
162
|
+
);
|
|
163
|
+
const sharedInboxServices = services.filter(
|
|
164
|
+
(service) =>
|
|
165
|
+
service.id.match(
|
|
166
|
+
new RegExp(`^${DID_SERVICE_ID_GRAFFITI_SHARED_INBOX_PREFIX}\\d+$`),
|
|
167
|
+
) && service.type === DID_SERVICE_TYPE_GRAFFITI_INBOX,
|
|
168
|
+
);
|
|
169
|
+
|
|
170
|
+
if (
|
|
171
|
+
!personalInboxService ||
|
|
172
|
+
!storageBucketService ||
|
|
173
|
+
sharedInboxServices.length === 0
|
|
174
|
+
) {
|
|
175
|
+
throw new Error(
|
|
176
|
+
`Required services not found in actor document for ${actor}`,
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Massage the services into a list of endpoints with types
|
|
181
|
+
const storageBucketEndpoint: string =
|
|
182
|
+
serviceToEndpoint(storageBucketService);
|
|
183
|
+
const personalInboxEndpoint: string =
|
|
184
|
+
serviceToEndpoint(personalInboxService);
|
|
185
|
+
const sharedInboxEndpoints: string[] =
|
|
186
|
+
sharedInboxServices.map(serviceToEndpoint);
|
|
187
|
+
const servicesWithTypes = [
|
|
188
|
+
{ endpoint: storageBucketEndpoint, type: "bucket" } as const,
|
|
189
|
+
{ endpoint: personalInboxEndpoint, type: "personal-inbox" } as const,
|
|
190
|
+
...sharedInboxEndpoints.map(
|
|
191
|
+
(endpoint) =>
|
|
192
|
+
({
|
|
193
|
+
endpoint,
|
|
194
|
+
type: "shared-inbox",
|
|
195
|
+
}) as const,
|
|
196
|
+
),
|
|
197
|
+
];
|
|
198
|
+
|
|
199
|
+
// Fetch the authorization endpoints for each service
|
|
200
|
+
const servicesWithAuthorizationEndpoints = await Promise.all(
|
|
201
|
+
servicesWithTypes.map(async ({ endpoint, type }) => {
|
|
202
|
+
const authorizationEndpoint = await (type === "bucket"
|
|
203
|
+
? this.services.storageBuckets.getAuthorizationEndpoint(endpoint)
|
|
204
|
+
: this.services.inboxes.getAuthorizationEndpoint(endpoint));
|
|
205
|
+
return { endpoint, authorizationEndpoint, type };
|
|
206
|
+
}),
|
|
207
|
+
);
|
|
208
|
+
|
|
209
|
+
// Group the endpoints according to their authorization endpoints
|
|
210
|
+
const servicesByAuthorizationMap: Map<
|
|
211
|
+
string,
|
|
212
|
+
{
|
|
213
|
+
endpoint: string;
|
|
214
|
+
type: "bucket" | "personal-inbox" | "shared-inbox";
|
|
215
|
+
}[]
|
|
216
|
+
> = new Map();
|
|
217
|
+
servicesWithAuthorizationEndpoints.forEach(
|
|
218
|
+
({ authorizationEndpoint, endpoint, type }) => {
|
|
219
|
+
if (!servicesByAuthorizationMap.has(authorizationEndpoint)) {
|
|
220
|
+
servicesByAuthorizationMap.set(authorizationEndpoint, []);
|
|
221
|
+
}
|
|
222
|
+
servicesByAuthorizationMap
|
|
223
|
+
.get(authorizationEndpoint)!
|
|
224
|
+
.push({ endpoint, type });
|
|
225
|
+
},
|
|
226
|
+
);
|
|
227
|
+
const servicesByAuthorization = [...servicesByAuthorizationMap.entries()];
|
|
228
|
+
|
|
229
|
+
const session: GraffitiSession = { actor };
|
|
230
|
+
|
|
231
|
+
const inProgressLogin: infer_<typeof InProgressSchema> = {
|
|
232
|
+
...session,
|
|
233
|
+
tokens: [],
|
|
234
|
+
servicesByAuthorization,
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
if (typeof window !== "undefined") {
|
|
238
|
+
// Store the in-progress session in localStorage
|
|
239
|
+
window.localStorage.setItem(
|
|
240
|
+
LOCAL_STORAGE_IN_PROGRESS_LOGIN_KEY,
|
|
241
|
+
JSON.stringify(inProgressLogin),
|
|
242
|
+
);
|
|
243
|
+
} else {
|
|
244
|
+
this.inProgressLogin = inProgressLogin;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// Start the login process with the first endpoint
|
|
248
|
+
const [firstAuthorizationEndpoint, firstServices] =
|
|
249
|
+
servicesByAuthorization[0];
|
|
250
|
+
await this.services.authorization.login(
|
|
251
|
+
firstAuthorizationEndpoint,
|
|
252
|
+
actor,
|
|
253
|
+
firstServices.map((s) => s.endpoint),
|
|
254
|
+
);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
protected async onLogin(event: unknown) {
|
|
258
|
+
if (!(event instanceof CustomEvent)) return;
|
|
259
|
+
const parsed = LoginEventDetailSchema.safeParse(event.detail);
|
|
260
|
+
if (!parsed.success) return;
|
|
261
|
+
|
|
262
|
+
const actor = parsed.data.loginId;
|
|
263
|
+
|
|
264
|
+
try {
|
|
265
|
+
await this.onLogin_(parsed.data);
|
|
266
|
+
} catch (e) {
|
|
267
|
+
const LoginEvent: GraffitiLoginEvent = new CustomEvent("login", {
|
|
268
|
+
detail: {
|
|
269
|
+
error: e instanceof Error ? e : new Error(String(e)),
|
|
270
|
+
session: { actor },
|
|
271
|
+
},
|
|
272
|
+
});
|
|
273
|
+
this.sessionEvents.dispatchEvent(LoginEvent);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
protected async onLogin_(loginDetail: infer_<typeof LoginEventDetailSchema>) {
|
|
277
|
+
if (loginDetail.error) throw loginDetail.error;
|
|
278
|
+
|
|
279
|
+
const token = loginDetail.token;
|
|
280
|
+
const actor = loginDetail.loginId;
|
|
281
|
+
|
|
282
|
+
// Lookup the in-progress session
|
|
283
|
+
let inProgressLogin: infer_<typeof InProgressSchema>;
|
|
284
|
+
if (typeof window !== "undefined") {
|
|
285
|
+
const inProgressLoginString = window.localStorage.getItem(
|
|
286
|
+
LOCAL_STORAGE_IN_PROGRESS_LOGIN_KEY,
|
|
287
|
+
);
|
|
288
|
+
if (!inProgressLoginString) {
|
|
289
|
+
throw new Error("No in-progress login found");
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
const json = JSON.parse(inProgressLoginString);
|
|
293
|
+
inProgressLogin = InProgressSchema.parse(json);
|
|
294
|
+
} else {
|
|
295
|
+
if (!this.inProgressLogin) {
|
|
296
|
+
throw new Error("No in-progress login found");
|
|
297
|
+
}
|
|
298
|
+
inProgressLogin = this.inProgressLogin;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
if (inProgressLogin.actor !== actor) {
|
|
302
|
+
throw new Error("Actor mismatch in login response - concurrent logins?");
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
inProgressLogin.tokens.push(token);
|
|
306
|
+
|
|
307
|
+
if (
|
|
308
|
+
inProgressLogin.tokens.length ===
|
|
309
|
+
inProgressLogin.servicesByAuthorization.length
|
|
310
|
+
) {
|
|
311
|
+
// Login complete!
|
|
312
|
+
if (typeof window === "undefined") {
|
|
313
|
+
this.inProgressLogin = undefined;
|
|
314
|
+
} else {
|
|
315
|
+
window.localStorage.removeItem(LOCAL_STORAGE_IN_PROGRESS_LOGIN_KEY);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// Build the completed session
|
|
319
|
+
const services = inProgressLogin.servicesByAuthorization.flatMap(
|
|
320
|
+
([authorizationEndpoint, services], index) =>
|
|
321
|
+
services.map((service) => ({
|
|
322
|
+
token: inProgressLogin.tokens[index],
|
|
323
|
+
serviceEndpoint: service.endpoint,
|
|
324
|
+
authorizationEndpoint,
|
|
325
|
+
type: service.type,
|
|
326
|
+
})),
|
|
327
|
+
);
|
|
328
|
+
|
|
329
|
+
const session: StoredSession = {
|
|
330
|
+
...inProgressLogin,
|
|
331
|
+
storageBucket: services.find((s) => s.type === "bucket")!,
|
|
332
|
+
personalInbox: services.find((s) => s.type === "personal-inbox")!,
|
|
333
|
+
sharedInboxes: services.filter((s) => s.type === "shared-inbox")!,
|
|
334
|
+
};
|
|
335
|
+
|
|
336
|
+
// Store the completed session
|
|
337
|
+
const sessions = this.loggedInSessions;
|
|
338
|
+
sessions.push(session);
|
|
339
|
+
this.loggedInSessions = sessions;
|
|
340
|
+
|
|
341
|
+
// Return the completed session
|
|
342
|
+
const loginEvent: GraffitiLoginEvent = new CustomEvent("login", {
|
|
343
|
+
detail: { session: { actor } },
|
|
344
|
+
});
|
|
345
|
+
this.sessionEvents.dispatchEvent(loginEvent);
|
|
346
|
+
} else {
|
|
347
|
+
// Store the in progress and continue
|
|
348
|
+
if (typeof window !== "undefined") {
|
|
349
|
+
window.localStorage.setItem(
|
|
350
|
+
LOCAL_STORAGE_IN_PROGRESS_LOGIN_KEY,
|
|
351
|
+
JSON.stringify(inProgressLogin),
|
|
352
|
+
);
|
|
353
|
+
} else {
|
|
354
|
+
this.inProgressLogin = inProgressLogin;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// Continue to the next authorization endpoint
|
|
358
|
+
const [authorizationEndpoint, services] =
|
|
359
|
+
inProgressLogin.servicesByAuthorization[inProgressLogin.tokens.length];
|
|
360
|
+
await this.services.authorization.login(
|
|
361
|
+
authorizationEndpoint,
|
|
362
|
+
actor,
|
|
363
|
+
services.map((s) => s.endpoint),
|
|
364
|
+
);
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
async logout(actor: string) {
|
|
369
|
+
try {
|
|
370
|
+
await this.logout_(actor);
|
|
371
|
+
} catch (e) {
|
|
372
|
+
const logoutEvent: GraffitiLogoutEvent = new CustomEvent("logout", {
|
|
373
|
+
detail: {
|
|
374
|
+
error: e instanceof Error ? e : new Error(String(e)),
|
|
375
|
+
actor,
|
|
376
|
+
},
|
|
377
|
+
});
|
|
378
|
+
this.sessionEvents.dispatchEvent(logoutEvent);
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
protected async logout_(actor: string) {
|
|
382
|
+
const session = this.loggedInSessions.find(
|
|
383
|
+
(session) => session.actor === actor,
|
|
384
|
+
);
|
|
385
|
+
if (!session) {
|
|
386
|
+
throw new Error(`No session found for actor ${actor}`);
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// Remove the session(s)
|
|
390
|
+
this.loggedInSessions = this.loggedInSessions.filter(
|
|
391
|
+
(session) => session.actor !== actor,
|
|
392
|
+
);
|
|
393
|
+
|
|
394
|
+
// Begin the logout
|
|
395
|
+
const token = session.tokens.pop();
|
|
396
|
+
if (!token) {
|
|
397
|
+
throw new Error("No tokens found in session");
|
|
398
|
+
}
|
|
399
|
+
// Store the in progress logout
|
|
400
|
+
if (typeof window !== "undefined") {
|
|
401
|
+
window.localStorage.setItem(
|
|
402
|
+
LOCAL_STORAGE_IN_PROGRESS_LOGOUT_KEY,
|
|
403
|
+
JSON.stringify(session),
|
|
404
|
+
);
|
|
405
|
+
} else {
|
|
406
|
+
this.inProgressLogout = session;
|
|
407
|
+
}
|
|
408
|
+
const [authorizationEndpoint, _] =
|
|
409
|
+
session.servicesByAuthorization[session.tokens.length];
|
|
410
|
+
await this.services.authorization.logout(
|
|
411
|
+
authorizationEndpoint,
|
|
412
|
+
actor,
|
|
413
|
+
token,
|
|
414
|
+
);
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
protected async onLogout(event: unknown) {
|
|
418
|
+
if (!(event instanceof CustomEvent)) return;
|
|
419
|
+
const parsed = LogoutEventDetailSchema.safeParse(event.detail);
|
|
420
|
+
if (!parsed.success) return;
|
|
421
|
+
|
|
422
|
+
const actor = parsed.data.logoutId;
|
|
423
|
+
|
|
424
|
+
try {
|
|
425
|
+
await this.onLogout_(parsed.data);
|
|
426
|
+
} catch (e) {
|
|
427
|
+
const logoutEvent: GraffitiLogoutEvent = new CustomEvent("logout", {
|
|
428
|
+
detail: {
|
|
429
|
+
error: e instanceof Error ? e : new Error(String(e)),
|
|
430
|
+
actor,
|
|
431
|
+
},
|
|
432
|
+
});
|
|
433
|
+
this.sessionEvents.dispatchEvent(logoutEvent);
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
protected async onLogout_(
|
|
437
|
+
logoutDetail: infer_<typeof LogoutEventDetailSchema>,
|
|
438
|
+
) {
|
|
439
|
+
if (logoutDetail.error) throw logoutDetail.error;
|
|
440
|
+
|
|
441
|
+
const actor = logoutDetail.logoutId;
|
|
442
|
+
|
|
443
|
+
// Lookup the in-progress session
|
|
444
|
+
let inProgressLogout: infer_<typeof InProgressSchema>;
|
|
445
|
+
if (typeof window !== "undefined") {
|
|
446
|
+
const inProgressLogoutString = window.localStorage.getItem(
|
|
447
|
+
LOCAL_STORAGE_IN_PROGRESS_LOGOUT_KEY,
|
|
448
|
+
);
|
|
449
|
+
if (!inProgressLogoutString) {
|
|
450
|
+
throw new Error("No in-progress logout found");
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
const json = JSON.parse(inProgressLogoutString);
|
|
454
|
+
inProgressLogout = InProgressSchema.parse(json);
|
|
455
|
+
} else {
|
|
456
|
+
if (!this.inProgressLogout) {
|
|
457
|
+
throw new Error("No in-progress logout found");
|
|
458
|
+
}
|
|
459
|
+
inProgressLogout = this.inProgressLogout;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
if (inProgressLogout.actor !== actor) {
|
|
463
|
+
throw new Error(
|
|
464
|
+
"Actor mismatch in logout response - concurrent logouts?",
|
|
465
|
+
);
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
const token = inProgressLogout.tokens.pop();
|
|
469
|
+
if (!token) {
|
|
470
|
+
// Logout complete
|
|
471
|
+
if (typeof window === "undefined") {
|
|
472
|
+
this.inProgressLogout = undefined;
|
|
473
|
+
} else {
|
|
474
|
+
window.localStorage.removeItem(LOCAL_STORAGE_IN_PROGRESS_LOGOUT_KEY);
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
const logoutEvent: GraffitiLogoutEvent = new CustomEvent("logout", {
|
|
478
|
+
detail: { actor },
|
|
479
|
+
});
|
|
480
|
+
this.sessionEvents.dispatchEvent(logoutEvent);
|
|
481
|
+
} else {
|
|
482
|
+
// Store the in progress and continue
|
|
483
|
+
if (typeof window !== "undefined") {
|
|
484
|
+
window.localStorage.setItem(
|
|
485
|
+
LOCAL_STORAGE_IN_PROGRESS_LOGOUT_KEY,
|
|
486
|
+
JSON.stringify(inProgressLogout),
|
|
487
|
+
);
|
|
488
|
+
} else {
|
|
489
|
+
this.inProgressLogout = inProgressLogout;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
// Continue to the next authorization endpoint
|
|
493
|
+
const [authorizationEndpoint, _] =
|
|
494
|
+
inProgressLogout.servicesByAuthorization[
|
|
495
|
+
inProgressLogout.tokens.length
|
|
496
|
+
];
|
|
497
|
+
await this.services.authorization.logout(
|
|
498
|
+
authorizationEndpoint,
|
|
499
|
+
actor,
|
|
500
|
+
token,
|
|
501
|
+
);
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
protected get loggedInSessions(): StoredSession[] {
|
|
506
|
+
if (typeof window === "undefined") return loggedInSessions_;
|
|
507
|
+
|
|
508
|
+
const data = window.localStorage.getItem(
|
|
509
|
+
LOCAL_STORAGE_LOGGED_IN_SESSIONS_KEY,
|
|
510
|
+
);
|
|
511
|
+
if (!data) return [];
|
|
512
|
+
|
|
513
|
+
let json: unknown;
|
|
514
|
+
try {
|
|
515
|
+
json = JSON.parse(data);
|
|
516
|
+
} catch {
|
|
517
|
+
console.error("Error parsing stored session data");
|
|
518
|
+
window.localStorage.removeItem(LOCAL_STORAGE_LOGGED_IN_SESSIONS_KEY);
|
|
519
|
+
return [];
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
const parsed = array(StoredSessionSchema).safeParse(json);
|
|
523
|
+
if (!parsed.success) {
|
|
524
|
+
console.error("Stored session data is invalid");
|
|
525
|
+
window.localStorage.removeItem(LOCAL_STORAGE_LOGGED_IN_SESSIONS_KEY);
|
|
526
|
+
return [];
|
|
527
|
+
}
|
|
528
|
+
return parsed.data;
|
|
529
|
+
}
|
|
530
|
+
protected set loggedInSessions(sessions: StoredSession[]) {
|
|
531
|
+
if (typeof window === "undefined") {
|
|
532
|
+
loggedInSessions_ = sessions;
|
|
533
|
+
return;
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
window.localStorage.setItem(
|
|
537
|
+
LOCAL_STORAGE_LOGGED_IN_SESSIONS_KEY,
|
|
538
|
+
JSON.stringify(sessions),
|
|
539
|
+
);
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
resolveSession(session: GraffitiSession): StoredSession {
|
|
543
|
+
const resolvedSession = this.loggedInSessions.find(
|
|
544
|
+
(s) => s.actor === session.actor,
|
|
545
|
+
);
|
|
546
|
+
if (!resolvedSession) {
|
|
547
|
+
const logoutEvent: GraffitiLogoutEvent = new CustomEvent("logout", {
|
|
548
|
+
detail: { actor: session.actor },
|
|
549
|
+
});
|
|
550
|
+
this.sessionEvents.dispatchEvent(logoutEvent);
|
|
551
|
+
throw new Error("Not logged in");
|
|
552
|
+
}
|
|
553
|
+
return resolvedSession;
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
let loggedInSessions_: StoredSession[] = [];
|
|
557
|
+
|
|
558
|
+
const LOCAL_STORAGE_IN_PROGRESS_LOGIN_KEY = "graffiti-login-in-progress";
|
|
559
|
+
const LOCAL_STORAGE_IN_PROGRESS_LOGOUT_KEY = "graffiti-logout-in-progress";
|
|
560
|
+
const LOCAL_STORAGE_LOGGED_IN_SESSIONS_KEY = "graffiti-sessions-logged-in";
|
|
561
|
+
|
|
562
|
+
const GraffitiSessionSchema = object({
|
|
563
|
+
actor: url(),
|
|
564
|
+
});
|
|
565
|
+
|
|
566
|
+
const ServiceSessionSchema = object({
|
|
567
|
+
token: string(),
|
|
568
|
+
serviceEndpoint: url(),
|
|
569
|
+
authorizationEndpoint: url(),
|
|
570
|
+
});
|
|
571
|
+
|
|
572
|
+
const ServicesByAuthorizationSchema = array(
|
|
573
|
+
tuple([
|
|
574
|
+
url(), // Authorization endpoint
|
|
575
|
+
array(
|
|
576
|
+
object({
|
|
577
|
+
endpoint: url(), // Service endpoint
|
|
578
|
+
type: enum_(["bucket", "personal-inbox", "shared-inbox"]),
|
|
579
|
+
}),
|
|
580
|
+
),
|
|
581
|
+
]),
|
|
582
|
+
);
|
|
583
|
+
|
|
584
|
+
const InProgressSchema = extend(GraffitiSessionSchema, {
|
|
585
|
+
tokens: array(string()),
|
|
586
|
+
servicesByAuthorization: ServicesByAuthorizationSchema,
|
|
587
|
+
});
|
|
588
|
+
|
|
589
|
+
const StoredSessionSchema = extend(InProgressSchema, {
|
|
590
|
+
storageBucket: ServiceSessionSchema,
|
|
591
|
+
personalInbox: ServiceSessionSchema,
|
|
592
|
+
sharedInboxes: array(ServiceSessionSchema),
|
|
593
|
+
});
|
|
594
|
+
|
|
595
|
+
type StoredSession = infer_<typeof StoredSessionSchema>;
|
|
596
|
+
|
|
597
|
+
function serviceToEndpoint(service: Service): string {
|
|
598
|
+
if (typeof service.serviceEndpoint === "string")
|
|
599
|
+
return service.serviceEndpoint;
|
|
600
|
+
throw new Error(`Service endpoint for ${service.id} is not a string`);
|
|
601
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { describe, expect, test } from "vitest";
|
|
2
|
+
import { Handles } from "./2-handles";
|
|
3
|
+
import { DecentralizedIdentifiers } from "../1-services/2-dids";
|
|
4
|
+
|
|
5
|
+
export function handleTests(handle: string) {
|
|
6
|
+
describe("Handles", async () => {
|
|
7
|
+
const handles = new Handles({
|
|
8
|
+
dids: new DecentralizedIdentifiers(),
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
test("handleToActor and actorToHandle", async () => {
|
|
12
|
+
const actor = await handles.handleToActor(handle);
|
|
13
|
+
const resolvedHandle = await handles.actorToHandle(actor);
|
|
14
|
+
expect(resolvedHandle).toBe(handle);
|
|
15
|
+
});
|
|
16
|
+
});
|
|
17
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type { Graffiti } from "@graffiti-garden/api";
|
|
2
|
+
import { GraffitiErrorNotFound } from "@graffiti-garden/api";
|
|
3
|
+
import { DecentralizedIdentifiers } from "../1-services/2-dids";
|
|
4
|
+
|
|
5
|
+
// Handles used a fixed method
|
|
6
|
+
const HANDLE_DID_PREFIX = "did:web:";
|
|
7
|
+
|
|
8
|
+
export class Handles {
|
|
9
|
+
constructor(
|
|
10
|
+
protected readonly services: { dids: DecentralizedIdentifiers },
|
|
11
|
+
) {}
|
|
12
|
+
|
|
13
|
+
actorToHandle: Graffiti["actorToHandle"] = async (actor) => {
|
|
14
|
+
const actorDocument = await this.services.dids.resolve(actor);
|
|
15
|
+
|
|
16
|
+
const handleDid = actorDocument.alsoKnownAs?.at(0);
|
|
17
|
+
if (!handleDid) {
|
|
18
|
+
throw new GraffitiErrorNotFound(
|
|
19
|
+
`Handle for actor DID ${actor} not found`,
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
if (!handleDid.startsWith(HANDLE_DID_PREFIX)) {
|
|
23
|
+
throw new Error(`Handle DID ${handleDid} is not a valid handle`);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const handle = handleDid.slice(HANDLE_DID_PREFIX.length);
|
|
27
|
+
|
|
28
|
+
const handleDocument = await this.services.dids.resolve(handleDid);
|
|
29
|
+
if (
|
|
30
|
+
!handleDocument.alsoKnownAs ||
|
|
31
|
+
!handleDocument.alsoKnownAs.includes(actor)
|
|
32
|
+
) {
|
|
33
|
+
throw new Error(`Handle ${handle} does not match actor ${actor}`);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return handle;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
handleToActor: Graffiti["handleToActor"] = async (handle) => {
|
|
40
|
+
const handleDid = `${HANDLE_DID_PREFIX}${handle}`;
|
|
41
|
+
const handleDocument = await this.services.dids.resolve(handleDid);
|
|
42
|
+
|
|
43
|
+
const actor = handleDocument.alsoKnownAs?.at(0);
|
|
44
|
+
if (!actor) {
|
|
45
|
+
throw new GraffitiErrorNotFound(
|
|
46
|
+
`Actor for handle DID ${handleDid} not found`,
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const actorDocument = await this.services.dids.resolve(actor);
|
|
51
|
+
if (
|
|
52
|
+
!actorDocument.alsoKnownAs ||
|
|
53
|
+
!actorDocument.alsoKnownAs.includes(handleDid)
|
|
54
|
+
) {
|
|
55
|
+
throw new Error(`Actor ${actor} does not match handle ${handle}`);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return actor;
|
|
59
|
+
};
|
|
60
|
+
}
|