@graffiti-garden/implementation-decentralized 0.0.7 → 0.0.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/3-protocol/1-sessions.d.ts.map +1 -1
- package/dist/3-protocol/4-graffiti.d.ts +5 -0
- package/dist/3-protocol/4-graffiti.d.ts.map +1 -1
- package/dist/browser/{ajv-QBSREQSI.js → ajv-U35FOJ27.js} +5 -5
- package/dist/browser/ajv-U35FOJ27.js.map +7 -0
- package/dist/browser/index.js +7 -7
- package/dist/browser/index.js.map +3 -3
- package/dist/cjs/3-protocol/1-sessions.js +5 -1
- package/dist/cjs/3-protocol/1-sessions.js.map +2 -2
- package/dist/cjs/3-protocol/4-graffiti.js +110 -7
- package/dist/cjs/3-protocol/4-graffiti.js.map +3 -3
- package/dist/esm/3-protocol/1-sessions.js +5 -1
- package/dist/esm/3-protocol/1-sessions.js.map +2 -2
- package/dist/esm/3-protocol/4-graffiti.js +110 -7
- package/dist/esm/3-protocol/4-graffiti.js.map +3 -3
- package/package.json +2 -2
- package/src/3-protocol/1-sessions.ts +8 -1
- package/src/3-protocol/4-graffiti.ts +160 -5
- package/dist/browser/ajv-QBSREQSI.js.map +0 -7
|
@@ -37,6 +37,7 @@ import {
|
|
|
37
37
|
type LabeledMessage,
|
|
38
38
|
type MessageStream,
|
|
39
39
|
} from "../1-services/4-inboxes";
|
|
40
|
+
import { GraffitiErrorUnauthorized } from "../1-services/utilities";
|
|
40
41
|
|
|
41
42
|
import {
|
|
42
43
|
StringEncoder,
|
|
@@ -133,6 +134,7 @@ export interface GraffitiDecentralizedOptions {
|
|
|
133
134
|
}
|
|
134
135
|
|
|
135
136
|
const CONCURRENCY = 16;
|
|
137
|
+
const LOCAL_STORAGE_REMEMBERED_HANDLE_KEY = "graffiti-login-remembered-handle";
|
|
136
138
|
|
|
137
139
|
export class GraffitiDecentralized implements Graffiti {
|
|
138
140
|
protected readonly dids = new DecentralizedIdentifiers();
|
|
@@ -188,10 +190,11 @@ export class GraffitiDecentralized implements Graffiti {
|
|
|
188
190
|
this.sessionEvents.addEventListener("login", async (event) => {
|
|
189
191
|
if (!(event instanceof CustomEvent)) return;
|
|
190
192
|
const detail = event.detail as GraffitiLoginEvent["detail"];
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
193
|
+
|
|
194
|
+
if (detail.error !== undefined) {
|
|
195
|
+
// If "manual", the window was closed by the user, nothing to do
|
|
196
|
+
if ("manual" in detail && detail.manual) return;
|
|
197
|
+
|
|
195
198
|
alert("Login failed: " + detail.error.message);
|
|
196
199
|
const actor = detail.session?.actor;
|
|
197
200
|
let handle: string | undefined;
|
|
@@ -202,9 +205,46 @@ export class GraffitiDecentralized implements Graffiti {
|
|
|
202
205
|
console.error("Failed to handle actor:", error);
|
|
203
206
|
}
|
|
204
207
|
}
|
|
208
|
+
// Open the login window again with the failed actor
|
|
205
209
|
this.login_(handle);
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const actor = detail.session.actor;
|
|
214
|
+
|
|
215
|
+
let handle: string | undefined;
|
|
216
|
+
try {
|
|
217
|
+
handle = await this.actorToHandle(actor);
|
|
218
|
+
} catch (error) {
|
|
219
|
+
console.error("Failed to load handle from actor:", error);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
if (handle) {
|
|
223
|
+
await this.rememberHandleInPasswordManager(handle, actor);
|
|
206
224
|
}
|
|
207
225
|
});
|
|
226
|
+
|
|
227
|
+
// If any methods throw an unauthorized error,
|
|
228
|
+
// log out the used session
|
|
229
|
+
for (const method of [
|
|
230
|
+
"post",
|
|
231
|
+
"get",
|
|
232
|
+
"delete",
|
|
233
|
+
"postMedia",
|
|
234
|
+
"getMedia",
|
|
235
|
+
"deleteMedia",
|
|
236
|
+
]) {
|
|
237
|
+
const fn = (this as any)[method];
|
|
238
|
+
(this as any)[method] = async (...args: unknown[]) => {
|
|
239
|
+
try {
|
|
240
|
+
return await fn(...args);
|
|
241
|
+
} catch (e) {
|
|
242
|
+
const session = args[args.length - 1] as any;
|
|
243
|
+
await this.tryLogoutIfUnauthorized(e, session);
|
|
244
|
+
throw e;
|
|
245
|
+
}
|
|
246
|
+
};
|
|
247
|
+
}
|
|
208
248
|
}
|
|
209
249
|
|
|
210
250
|
readonly actorToHandle: Graffiti["actorToHandle"] =
|
|
@@ -241,7 +281,10 @@ export class GraffitiDecentralized implements Graffiti {
|
|
|
241
281
|
const input = template?.querySelector(
|
|
242
282
|
"#username",
|
|
243
283
|
) as HTMLInputElement | null;
|
|
244
|
-
|
|
284
|
+
const rememberedHandle =
|
|
285
|
+
proposedHandle ||
|
|
286
|
+
(await this.getRememberedHandleFromPasswordManager());
|
|
287
|
+
if (input) input.value = rememberedHandle ?? "";
|
|
245
288
|
input?.addEventListener("focus", () => input?.select());
|
|
246
289
|
setTimeout(() => input?.focus(), 0);
|
|
247
290
|
|
|
@@ -353,6 +396,105 @@ export class GraffitiDecentralized implements Graffiti {
|
|
|
353
396
|
}
|
|
354
397
|
}
|
|
355
398
|
|
|
399
|
+
protected async rememberHandleInPasswordManager(
|
|
400
|
+
handle: string,
|
|
401
|
+
actor: string,
|
|
402
|
+
) {
|
|
403
|
+
if (typeof window === "undefined") return;
|
|
404
|
+
|
|
405
|
+
const credentials = window.navigator.credentials;
|
|
406
|
+
|
|
407
|
+
const PasswordCredentialConstructor = (
|
|
408
|
+
window as unknown as {
|
|
409
|
+
PasswordCredential?: new (data: {
|
|
410
|
+
id: string;
|
|
411
|
+
password: string;
|
|
412
|
+
name?: string;
|
|
413
|
+
}) => Credential;
|
|
414
|
+
}
|
|
415
|
+
).PasswordCredential;
|
|
416
|
+
|
|
417
|
+
if (!credentials?.store || !PasswordCredentialConstructor) {
|
|
418
|
+
this.rememberHandleInBrowserStorage(handle);
|
|
419
|
+
return;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
try {
|
|
423
|
+
const credential = new PasswordCredentialConstructor({
|
|
424
|
+
id: handle,
|
|
425
|
+
// Graffiti logins use external authorization; this stable placeholder
|
|
426
|
+
// allows browsers/password managers to persist the handle.
|
|
427
|
+
password: "no-password",
|
|
428
|
+
name: handle,
|
|
429
|
+
});
|
|
430
|
+
await credentials.store(credential);
|
|
431
|
+
} catch (error) {
|
|
432
|
+
console.error("Failed to remember handle in password manager:", error);
|
|
433
|
+
this.rememberHandleInBrowserStorage(handle);
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
protected async getRememberedHandleFromPasswordManager() {
|
|
438
|
+
if (typeof window === "undefined") return undefined;
|
|
439
|
+
|
|
440
|
+
const credentials = window.navigator.credentials;
|
|
441
|
+
const PasswordCredentialConstructor = (
|
|
442
|
+
window as unknown as {
|
|
443
|
+
PasswordCredential?: new (data: {
|
|
444
|
+
id: string;
|
|
445
|
+
password: string;
|
|
446
|
+
name?: string;
|
|
447
|
+
}) => Credential;
|
|
448
|
+
}
|
|
449
|
+
).PasswordCredential;
|
|
450
|
+
if (!credentials?.get || !PasswordCredentialConstructor) {
|
|
451
|
+
return this.getRememberedHandleFromBrowserStorage();
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
try {
|
|
455
|
+
const credential = await credentials.get({
|
|
456
|
+
password: true,
|
|
457
|
+
mediation: "optional",
|
|
458
|
+
} as any);
|
|
459
|
+
if (
|
|
460
|
+
credential &&
|
|
461
|
+
"id" in credential &&
|
|
462
|
+
typeof credential.id === "string"
|
|
463
|
+
) {
|
|
464
|
+
return credential.id;
|
|
465
|
+
}
|
|
466
|
+
} catch (error) {
|
|
467
|
+
console.error("Failed to load handle from password manager:", error);
|
|
468
|
+
return this.getRememberedHandleFromBrowserStorage();
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
return undefined;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
protected rememberHandleInBrowserStorage(handle: string) {
|
|
475
|
+
if (typeof window === "undefined") return;
|
|
476
|
+
|
|
477
|
+
try {
|
|
478
|
+
window.localStorage.setItem(LOCAL_STORAGE_REMEMBERED_HANDLE_KEY, handle);
|
|
479
|
+
} catch (error) {
|
|
480
|
+
console.error("Failed to remember handle in browser storage:", error);
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
protected getRememberedHandleFromBrowserStorage() {
|
|
485
|
+
if (typeof window === "undefined") return undefined;
|
|
486
|
+
|
|
487
|
+
try {
|
|
488
|
+
return (
|
|
489
|
+
window.localStorage.getItem(LOCAL_STORAGE_REMEMBERED_HANDLE_KEY) ??
|
|
490
|
+
undefined
|
|
491
|
+
);
|
|
492
|
+
} catch (error) {
|
|
493
|
+
console.error("Failed to load handle from browser storage:", error);
|
|
494
|
+
return undefined;
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
|
|
356
498
|
logout: Graffiti["logout"] = async (session) => {
|
|
357
499
|
await this.sessions.logout(session.actor);
|
|
358
500
|
};
|
|
@@ -720,6 +862,7 @@ export class GraffitiDecentralized implements Graffiti {
|
|
|
720
862
|
error: next.error,
|
|
721
863
|
origin: allInboxes[next.index].serviceEndpoint,
|
|
722
864
|
};
|
|
865
|
+
this.tryLogoutIfUnauthorized(next.error, session);
|
|
723
866
|
} else if (next.result.done) {
|
|
724
867
|
// Store the cursor for future use
|
|
725
868
|
const inbox = allInboxes[next.index];
|
|
@@ -783,6 +926,9 @@ export class GraffitiDecentralized implements Graffiti {
|
|
|
783
926
|
}
|
|
784
927
|
}
|
|
785
928
|
}
|
|
929
|
+
} catch (e) {
|
|
930
|
+
this.tryLogoutIfUnauthorized(e, session);
|
|
931
|
+
throw e;
|
|
786
932
|
} finally {
|
|
787
933
|
await Promise.all(
|
|
788
934
|
iterators.map<Promise<void>>(async (it) => {
|
|
@@ -823,6 +969,15 @@ export class GraffitiDecentralized implements Graffiti {
|
|
|
823
969
|
return this.discoverMeta<{}>(channels, {}, cursors, session);
|
|
824
970
|
};
|
|
825
971
|
|
|
972
|
+
protected async tryLogoutIfUnauthorized(
|
|
973
|
+
e: unknown,
|
|
974
|
+
session?: GraffitiSession | null,
|
|
975
|
+
) {
|
|
976
|
+
if (e instanceof GraffitiErrorUnauthorized && session) {
|
|
977
|
+
await this.sessions.logout(session.actor);
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
|
|
826
981
|
async announceObject(
|
|
827
982
|
object: GraffitiObjectBase,
|
|
828
983
|
tags: Uint8Array[],
|