@le-space/p2pass 0.2.0 → 0.3.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/dist/index.d.ts +6 -5
- package/dist/index.js +9 -4
- package/dist/registry/device-registry.d.ts +34 -2
- package/dist/registry/device-registry.js +70 -2
- package/dist/registry/index.d.ts +1 -1
- package/dist/registry/index.js +3 -0
- package/dist/registry/manager.d.ts +5 -0
- package/dist/registry/manager.js +13 -0
- package/dist/registry/pairing-protocol.d.ts +1 -1
- package/dist/registry/pairing-protocol.js +8 -4
- package/dist/ucan/storacha-auth.d.ts +14 -1
- package/dist/ucan/storacha-auth.js +120 -2
- package/dist/ui/StorachaFab.svelte +30 -4
- package/dist/ui/StorachaIntegration.svelte +387 -201
- package/package.json +25 -2
package/dist/index.d.ts
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
export const VERSION: "0.1
|
|
2
|
-
export { default as StorachaIntegration } from "./ui/StorachaIntegration.svelte";
|
|
3
|
-
export { default as StorachaFab } from "./ui/StorachaFab.svelte";
|
|
1
|
+
export const VERSION: "0.3.1";
|
|
4
2
|
export { MultiDeviceManager } from "./registry/manager.js";
|
|
3
|
+
import StorachaIntegration from './ui/StorachaIntegration.svelte';
|
|
4
|
+
import StorachaFab from './ui/StorachaFab.svelte';
|
|
5
|
+
export { StorachaIntegration, StorachaFab };
|
|
5
6
|
export { IdentityService, hasLocalPasskeyHint } from "./identity/identity-service.js";
|
|
6
7
|
export { detectSigningMode, getStoredSigningMode } from "./identity/mode-detector.js";
|
|
7
8
|
export { SIGNING_PREFERENCE_STORAGE_KEY, SIGNING_PREFERENCE_LIST, isSigningPreference, readSigningPreferenceFromStorage, writeSigningPreferenceToStorage, resolveSigningPreference } from "./identity/signing-preference.js";
|
|
8
|
-
export { createStorachaClient, parseDelegation, storeDelegation, loadStoredDelegation, clearStoredDelegation } from "./ucan/storacha-auth.js";
|
|
9
|
-
export { openDeviceRegistry, registerDevice, listDevices, getDeviceByCredentialId, getDeviceByDID, grantDeviceWriteAccess, revokeDeviceAccess, hashCredentialId, coseToJwk, storeDelegationEntry, listDelegations, getDelegation, removeDelegation, storeArchiveEntry, getArchiveEntry, storeKeypairEntry, getKeypairEntry, listKeypairs } from "./registry/device-registry.js";
|
|
9
|
+
export { createStorachaClient, parseDelegation, storeDelegation, loadStoredDelegation, clearStoredDelegation, formatDelegationsTooltipSummary } from "./ucan/storacha-auth.js";
|
|
10
|
+
export { openDeviceRegistry, registerDevice, listDevices, getDeviceByCredentialId, getDeviceByDID, grantDeviceWriteAccess, revokeDeviceAccess, removeDeviceEntry, delegationCountForDevice, delegationsEntriesForDevice, hashCredentialId, coseToJwk, storeDelegationEntry, listDelegations, getDelegation, removeDelegation, storeArchiveEntry, getArchiveEntry, storeKeypairEntry, getKeypairEntry, listKeypairs } from "./registry/device-registry.js";
|
|
10
11
|
export { LINK_DEVICE_PROTOCOL, registerLinkDeviceHandler, unregisterLinkDeviceHandler, sendPairingRequest, detectDeviceLabel, sortPairingMultiaddrs, filterPairingDialMultiaddrs, pairingFlow, PAIRING_HINT_ADDR_CAP } from "./registry/pairing-protocol.js";
|
|
11
12
|
export { setupP2PStack, createLibp2pInstance, createHeliaInstance, cleanupP2PStack } from "./p2p/setup.js";
|
|
12
13
|
export { listSpaces, getSpaceUsage, listStorachaFiles } from "./ui/storacha-backup.js";
|
package/dist/index.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
// @le-space/p2pass — public API
|
|
2
|
-
export const VERSION = '0.1
|
|
2
|
+
export const VERSION = '0.3.1';
|
|
3
3
|
|
|
4
|
-
//
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
// Explicit default bindings (not `export { default as X } from`) — avoids star-export / default resolution errors in Vite.
|
|
5
|
+
import StorachaIntegration from './ui/StorachaIntegration.svelte';
|
|
6
|
+
import StorachaFab from './ui/StorachaFab.svelte';
|
|
7
|
+
export { StorachaIntegration, StorachaFab };
|
|
7
8
|
|
|
8
9
|
// Identity
|
|
9
10
|
export { IdentityService, hasLocalPasskeyHint } from './identity/identity-service.js';
|
|
@@ -24,6 +25,7 @@ export {
|
|
|
24
25
|
storeDelegation,
|
|
25
26
|
loadStoredDelegation,
|
|
26
27
|
clearStoredDelegation,
|
|
28
|
+
formatDelegationsTooltipSummary,
|
|
27
29
|
} from './ucan/storacha-auth.js';
|
|
28
30
|
|
|
29
31
|
// Registry (multi-device + credential storage)
|
|
@@ -36,6 +38,9 @@ export {
|
|
|
36
38
|
getDeviceByDID,
|
|
37
39
|
grantDeviceWriteAccess,
|
|
38
40
|
revokeDeviceAccess,
|
|
41
|
+
removeDeviceEntry,
|
|
42
|
+
delegationCountForDevice,
|
|
43
|
+
delegationsEntriesForDevice,
|
|
39
44
|
hashCredentialId,
|
|
40
45
|
coseToJwk,
|
|
41
46
|
storeDelegationEntry,
|
|
@@ -23,7 +23,7 @@ export function openDeviceRegistry(orbitdb: Object, ownerIdentityId: string, add
|
|
|
23
23
|
/**
|
|
24
24
|
* Register a device entry in the registry.
|
|
25
25
|
* @param {Object} db - OrbitDB KV database
|
|
26
|
-
* @param {Object} entry - { credential_id, public_key, device_label, created_at, status, ed25519_did }
|
|
26
|
+
* @param {Object} entry - { credential_id, public_key, device_label, created_at, status, ed25519_did, passkey_kind? }
|
|
27
27
|
*/
|
|
28
28
|
export function registerDevice(db: Object, entry: Object): Promise<void>;
|
|
29
29
|
/**
|
|
@@ -58,14 +58,46 @@ export function grantDeviceWriteAccess(db: Object, did: string): Promise<void>;
|
|
|
58
58
|
* @param {string} did - Ed25519 DID to revoke
|
|
59
59
|
*/
|
|
60
60
|
export function revokeDeviceAccess(db: Object, did: string): Promise<void>;
|
|
61
|
+
/**
|
|
62
|
+
* Revoke a device's write access and remove its registry row (OrbitDB KV key).
|
|
63
|
+
*
|
|
64
|
+
* @param {Object} db - OrbitDB KV database
|
|
65
|
+
* @param {string} credentialId - device entry credential_id (same string used at registration)
|
|
66
|
+
* @returns {Promise<boolean>} true if an entry was removed
|
|
67
|
+
*/
|
|
68
|
+
export function removeDeviceEntry(db: Object, credentialId: string): Promise<boolean>;
|
|
69
|
+
/**
|
|
70
|
+
* UCAN delegations attributed to a device (stored_by_did). Entries without stored_by_did count toward ownerDidForLegacy only.
|
|
71
|
+
*
|
|
72
|
+
* @param {Array<{ stored_by_did?: string }>} delegations
|
|
73
|
+
* @param {string} deviceDid
|
|
74
|
+
* @param {string} [ownerDidForLegacy]
|
|
75
|
+
* @returns {number}
|
|
76
|
+
*/
|
|
77
|
+
export function delegationCountForDevice(delegations: Array<{
|
|
78
|
+
stored_by_did?: string;
|
|
79
|
+
}>, deviceDid: string, ownerDidForLegacy?: string): number;
|
|
80
|
+
/**
|
|
81
|
+
* Registry delegation rows attributed to a device (same rules as {@link delegationCountForDevice}).
|
|
82
|
+
*
|
|
83
|
+
* @param {Array<{ stored_by_did?: string, delegation?: string }>} delegations
|
|
84
|
+
* @param {string} deviceDid
|
|
85
|
+
* @param {string} [ownerDidForLegacy]
|
|
86
|
+
* @returns {Array<Record<string, unknown>>}
|
|
87
|
+
*/
|
|
88
|
+
export function delegationsEntriesForDevice(delegations: Array<{
|
|
89
|
+
stored_by_did?: string;
|
|
90
|
+
delegation?: string;
|
|
91
|
+
}>, deviceDid: string, ownerDidForLegacy?: string): Array<Record<string, unknown>>;
|
|
61
92
|
/**
|
|
62
93
|
* Store a UCAN delegation in the registry.
|
|
63
94
|
* @param {Object} db - OrbitDB KV database
|
|
64
95
|
* @param {string} delegationBase64 - raw delegation string
|
|
65
96
|
* @param {string} [spaceDid] - Storacha space DID
|
|
66
97
|
* @param {string} [label] - human-readable label
|
|
98
|
+
* @param {string} [storedByDid] - Ed25519 DID of the device that stored this delegation (for per-device UI)
|
|
67
99
|
*/
|
|
68
|
-
export function storeDelegationEntry(db: Object, delegationBase64: string, spaceDid?: string, label?: string): Promise<void>;
|
|
100
|
+
export function storeDelegationEntry(db: Object, delegationBase64: string, spaceDid?: string, label?: string, storedByDid?: string): Promise<void>;
|
|
69
101
|
/**
|
|
70
102
|
* List all stored UCAN delegations.
|
|
71
103
|
* @param {Object} db - OrbitDB KV database
|
|
@@ -81,7 +81,7 @@ export async function openDeviceRegistry(orbitdb, ownerIdentityId, address = nul
|
|
|
81
81
|
/**
|
|
82
82
|
* Register a device entry in the registry.
|
|
83
83
|
* @param {Object} db - OrbitDB KV database
|
|
84
|
-
* @param {Object} entry - { credential_id, public_key, device_label, created_at, status, ed25519_did }
|
|
84
|
+
* @param {Object} entry - { credential_id, public_key, device_label, created_at, status, ed25519_did, passkey_kind? }
|
|
85
85
|
*/
|
|
86
86
|
export async function registerDevice(db, entry) {
|
|
87
87
|
const key = await hashCredentialId(entry.credential_id);
|
|
@@ -153,6 +153,72 @@ export async function revokeDeviceAccess(db, did) {
|
|
|
153
153
|
}
|
|
154
154
|
}
|
|
155
155
|
|
|
156
|
+
/**
|
|
157
|
+
* Revoke a device's write access and remove its registry row (OrbitDB KV key).
|
|
158
|
+
*
|
|
159
|
+
* @param {Object} db - OrbitDB KV database
|
|
160
|
+
* @param {string} credentialId - device entry credential_id (same string used at registration)
|
|
161
|
+
* @returns {Promise<boolean>} true if an entry was removed
|
|
162
|
+
*/
|
|
163
|
+
export async function removeDeviceEntry(db, credentialId) {
|
|
164
|
+
const key = await hashCredentialId(credentialId);
|
|
165
|
+
const entry = await db.get(key);
|
|
166
|
+
if (!entry) return false;
|
|
167
|
+
const did = entry.ed25519_did;
|
|
168
|
+
try {
|
|
169
|
+
if (did) await db.access.revoke('write', did);
|
|
170
|
+
} catch (err) {
|
|
171
|
+
console.warn('[device-registry] revoke write before del:', err?.message);
|
|
172
|
+
}
|
|
173
|
+
await db.del(key);
|
|
174
|
+
return true;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* UCAN delegations attributed to a device (stored_by_did). Entries without stored_by_did count toward ownerDidForLegacy only.
|
|
179
|
+
*
|
|
180
|
+
* @param {Array<{ stored_by_did?: string }>} delegations
|
|
181
|
+
* @param {string} deviceDid
|
|
182
|
+
* @param {string} [ownerDidForLegacy]
|
|
183
|
+
* @returns {number}
|
|
184
|
+
*/
|
|
185
|
+
export function delegationCountForDevice(delegations, deviceDid, ownerDidForLegacy) {
|
|
186
|
+
if (!deviceDid) return 0;
|
|
187
|
+
let n = 0;
|
|
188
|
+
for (const d of delegations) {
|
|
189
|
+
if (d.stored_by_did === deviceDid) n++;
|
|
190
|
+
}
|
|
191
|
+
if (ownerDidForLegacy && deviceDid === ownerDidForLegacy) {
|
|
192
|
+
for (const d of delegations) {
|
|
193
|
+
if (!d.stored_by_did) n++;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
return n;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Registry delegation rows attributed to a device (same rules as {@link delegationCountForDevice}).
|
|
201
|
+
*
|
|
202
|
+
* @param {Array<{ stored_by_did?: string, delegation?: string }>} delegations
|
|
203
|
+
* @param {string} deviceDid
|
|
204
|
+
* @param {string} [ownerDidForLegacy]
|
|
205
|
+
* @returns {Array<Record<string, unknown>>}
|
|
206
|
+
*/
|
|
207
|
+
export function delegationsEntriesForDevice(delegations, deviceDid, ownerDidForLegacy) {
|
|
208
|
+
if (!deviceDid) return [];
|
|
209
|
+
/** @type {Array<Record<string, unknown>>} */
|
|
210
|
+
const out = [];
|
|
211
|
+
for (const d of delegations) {
|
|
212
|
+
if (d.stored_by_did === deviceDid) out.push(d);
|
|
213
|
+
}
|
|
214
|
+
if (ownerDidForLegacy && deviceDid === ownerDidForLegacy) {
|
|
215
|
+
for (const d of delegations) {
|
|
216
|
+
if (!d.stored_by_did) out.push(d);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
return out;
|
|
220
|
+
}
|
|
221
|
+
|
|
156
222
|
// ---------------------------------------------------------------------------
|
|
157
223
|
// UCAN delegation entries
|
|
158
224
|
// ---------------------------------------------------------------------------
|
|
@@ -163,8 +229,9 @@ export async function revokeDeviceAccess(db, did) {
|
|
|
163
229
|
* @param {string} delegationBase64 - raw delegation string
|
|
164
230
|
* @param {string} [spaceDid] - Storacha space DID
|
|
165
231
|
* @param {string} [label] - human-readable label
|
|
232
|
+
* @param {string} [storedByDid] - Ed25519 DID of the device that stored this delegation (for per-device UI)
|
|
166
233
|
*/
|
|
167
|
-
export async function storeDelegationEntry(db, delegationBase64, spaceDid, label) {
|
|
234
|
+
export async function storeDelegationEntry(db, delegationBase64, spaceDid, label, storedByDid) {
|
|
168
235
|
const hash = await hashCredentialId(delegationBase64);
|
|
169
236
|
const key = `delegation:${hash}`;
|
|
170
237
|
await db.put(key, {
|
|
@@ -172,6 +239,7 @@ export async function storeDelegationEntry(db, delegationBase64, spaceDid, label
|
|
|
172
239
|
space_did: spaceDid || '',
|
|
173
240
|
label: label || 'default',
|
|
174
241
|
created_at: Date.now(),
|
|
242
|
+
stored_by_did: storedByDid || '',
|
|
175
243
|
});
|
|
176
244
|
}
|
|
177
245
|
|
package/dist/registry/index.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
export { MultiDeviceManager } from "./manager.js";
|
|
2
|
-
export { openDeviceRegistry, registerDevice, listDevices, getDeviceByCredentialId, getDeviceByDID, grantDeviceWriteAccess, revokeDeviceAccess, hashCredentialId, coseToJwk, storeDelegationEntry, listDelegations, getDelegation, removeDelegation, storeArchiveEntry, getArchiveEntry, storeKeypairEntry, getKeypairEntry, listKeypairs } from "./device-registry.js";
|
|
2
|
+
export { openDeviceRegistry, registerDevice, listDevices, getDeviceByCredentialId, getDeviceByDID, grantDeviceWriteAccess, revokeDeviceAccess, removeDeviceEntry, delegationCountForDevice, delegationsEntriesForDevice, hashCredentialId, coseToJwk, storeDelegationEntry, listDelegations, getDelegation, removeDelegation, storeArchiveEntry, getArchiveEntry, storeKeypairEntry, getKeypairEntry, listKeypairs } from "./device-registry.js";
|
|
3
3
|
export { LINK_DEVICE_PROTOCOL, registerLinkDeviceHandler, unregisterLinkDeviceHandler, sendPairingRequest, detectDeviceLabel, sortPairingMultiaddrs, filterPairingDialMultiaddrs, pairingFlow, PAIRING_HINT_ADDR_CAP } from "./pairing-protocol.js";
|
package/dist/registry/index.js
CHANGED
|
@@ -59,6 +59,11 @@ export class MultiDeviceManager {
|
|
|
59
59
|
listDevices(): Promise<any[]>;
|
|
60
60
|
syncDevices(): Promise<void>;
|
|
61
61
|
revokeDevice(did: any): Promise<void>;
|
|
62
|
+
/**
|
|
63
|
+
* Remove a device row from the registry and revoke its OrbitDB write access.
|
|
64
|
+
* @param {string} credentialId - value stored as device entry `credential_id`
|
|
65
|
+
*/
|
|
66
|
+
removeLinkedDevice(credentialId: string): Promise<boolean>;
|
|
62
67
|
processIncomingPairingRequest(requestMsg: any): Promise<{
|
|
63
68
|
type: string;
|
|
64
69
|
orbitdbAddress: any;
|
package/dist/registry/manager.js
CHANGED
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
getDeviceByDID,
|
|
14
14
|
grantDeviceWriteAccess,
|
|
15
15
|
revokeDeviceAccess,
|
|
16
|
+
removeDeviceEntry,
|
|
16
17
|
coseToJwk,
|
|
17
18
|
} from './device-registry.js';
|
|
18
19
|
|
|
@@ -140,6 +141,7 @@ export class MultiDeviceManager {
|
|
|
140
141
|
created_at: Date.now(),
|
|
141
142
|
status: 'active',
|
|
142
143
|
ed25519_did: this._identity.id,
|
|
144
|
+
passkey_kind: this._identity.passkeyKind || null,
|
|
143
145
|
});
|
|
144
146
|
|
|
145
147
|
await this.syncDevices();
|
|
@@ -247,6 +249,7 @@ export class MultiDeviceManager {
|
|
|
247
249
|
this._credential?.credentialId || this._credential?.id || this._libp2p.peerId.toString(),
|
|
248
250
|
publicKey: null,
|
|
249
251
|
deviceLabel: detectDeviceLabel(),
|
|
252
|
+
passkeyKind: this._identity.passkeyKind || null,
|
|
250
253
|
},
|
|
251
254
|
multiaddrs
|
|
252
255
|
);
|
|
@@ -325,6 +328,15 @@ export class MultiDeviceManager {
|
|
|
325
328
|
await revokeDeviceAccess(this._devicesDb, did);
|
|
326
329
|
}
|
|
327
330
|
|
|
331
|
+
/**
|
|
332
|
+
* Remove a device row from the registry and revoke its OrbitDB write access.
|
|
333
|
+
* @param {string} credentialId - value stored as device entry `credential_id`
|
|
334
|
+
*/
|
|
335
|
+
async removeLinkedDevice(credentialId) {
|
|
336
|
+
if (!this._devicesDb) throw new Error('Device registry not initialized');
|
|
337
|
+
return removeDeviceEntry(this._devicesDb, credentialId);
|
|
338
|
+
}
|
|
339
|
+
|
|
328
340
|
async processIncomingPairingRequest(requestMsg) {
|
|
329
341
|
if (!this._devicesDb) throw new Error('Device registry not initialized');
|
|
330
342
|
const { identity } = requestMsg;
|
|
@@ -348,6 +360,7 @@ export class MultiDeviceManager {
|
|
|
348
360
|
created_at: Date.now(),
|
|
349
361
|
status: 'active',
|
|
350
362
|
ed25519_did: identity.id,
|
|
363
|
+
passkey_kind: identity.passkeyKind || null,
|
|
351
364
|
});
|
|
352
365
|
return { type: 'granted', orbitdbAddress: this._dbAddress };
|
|
353
366
|
}
|
|
@@ -42,7 +42,7 @@ export function detectDeviceLabel(): string;
|
|
|
42
42
|
*
|
|
43
43
|
* @param {Object} libp2p - libp2p instance (Device B)
|
|
44
44
|
* @param {string|Object} deviceAPeerId - peerId string or PeerId object of Device A
|
|
45
|
-
* @param {Object} identity - { id, credentialId, publicKey?, deviceLabel? }
|
|
45
|
+
* @param {Object} identity - { id, credentialId, publicKey?, deviceLabel?, passkeyKind? }
|
|
46
46
|
* @param {string[]} [hintMultiaddrs] - Known multiaddrs for Device A (from QR payload)
|
|
47
47
|
* @returns {Promise<{type: 'granted', orbitdbAddress: string}|{type: 'rejected', reason: string}>}
|
|
48
48
|
*/
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Protocol: /orbitdb/link-device/1.0.0
|
|
5
5
|
*
|
|
6
6
|
* Message flow:
|
|
7
|
-
* Device B → Device A: { type: 'request', identity: { id, credentialId, deviceLabel } }
|
|
7
|
+
* Device B → Device A: { type: 'request', identity: { id, credentialId, deviceLabel, passkeyKind?, ... } }
|
|
8
8
|
* Device A → Device B: { type: 'granted', orbitdbAddress } | { type: 'rejected', reason }
|
|
9
9
|
*
|
|
10
10
|
* Copied from orbitdb-identity-provider-webauthn-did/src/multi-device/pairing-protocol.js
|
|
@@ -330,12 +330,14 @@ export async function registerLinkDeviceHandler(libp2p, db, onRequest, onDeviceL
|
|
|
330
330
|
result = { type: 'granted', orbitdbAddress: db.address };
|
|
331
331
|
if (isKnown && onDeviceLinked) {
|
|
332
332
|
onDeviceLinked({
|
|
333
|
+
...isKnown,
|
|
333
334
|
credential_id: identity.credentialId,
|
|
334
|
-
public_key: identity.publicKey
|
|
335
|
-
device_label: identity.deviceLabel || 'Linked Device',
|
|
335
|
+
public_key: identity.publicKey ?? isKnown.public_key ?? null,
|
|
336
|
+
device_label: identity.deviceLabel || isKnown.device_label || 'Linked Device',
|
|
336
337
|
created_at: isKnown.created_at || Date.now(),
|
|
337
338
|
status: 'active',
|
|
338
339
|
ed25519_did: identity.id,
|
|
340
|
+
passkey_kind: identity.passkeyKind || isKnown.passkey_kind || null,
|
|
339
341
|
});
|
|
340
342
|
}
|
|
341
343
|
} else {
|
|
@@ -365,6 +367,7 @@ export async function registerLinkDeviceHandler(libp2p, db, onRequest, onDeviceL
|
|
|
365
367
|
created_at: Date.now(),
|
|
366
368
|
status: 'active',
|
|
367
369
|
ed25519_did: identity.id,
|
|
370
|
+
passkey_kind: identity.passkeyKind || null,
|
|
368
371
|
};
|
|
369
372
|
try {
|
|
370
373
|
await registerDevice(db, deviceEntry);
|
|
@@ -528,7 +531,7 @@ export function detectDeviceLabel() {
|
|
|
528
531
|
*
|
|
529
532
|
* @param {Object} libp2p - libp2p instance (Device B)
|
|
530
533
|
* @param {string|Object} deviceAPeerId - peerId string or PeerId object of Device A
|
|
531
|
-
* @param {Object} identity - { id, credentialId, publicKey?, deviceLabel? }
|
|
534
|
+
* @param {Object} identity - { id, credentialId, publicKey?, deviceLabel?, passkeyKind? }
|
|
532
535
|
* @param {string[]} [hintMultiaddrs] - Known multiaddrs for Device A (from QR payload)
|
|
533
536
|
* @returns {Promise<{type: 'granted', orbitdbAddress: string}|{type: 'rejected', reason: string}>}
|
|
534
537
|
*/
|
|
@@ -628,6 +631,7 @@ export async function sendPairingRequest(libp2p, deviceAPeerId, identity, hintMu
|
|
|
628
631
|
credentialId: identity.credentialId,
|
|
629
632
|
publicKey: identity.publicKey || null,
|
|
630
633
|
deviceLabel: identity.deviceLabel || detectDeviceLabel(),
|
|
634
|
+
passkeyKind: identity.passkeyKind || null,
|
|
631
635
|
},
|
|
632
636
|
};
|
|
633
637
|
pairingFlow('BOB', 'sending link-device REQUEST (length-prefixed JSON) to Alice', {
|
|
@@ -24,8 +24,9 @@ export function parseDelegation(proofString: string): Promise<any>;
|
|
|
24
24
|
* @param {string} delegationBase64
|
|
25
25
|
* @param {Object} [registryDb] - OrbitDB registry database
|
|
26
26
|
* @param {string} [spaceDid] - Storacha space DID (for registry metadata)
|
|
27
|
+
* @param {string} [storedByDid] - device DID that imported the delegation (per-device counts in UI)
|
|
27
28
|
*/
|
|
28
|
-
export function storeDelegation(delegationBase64: string, registryDb?: Object, spaceDid?: string): Promise<void>;
|
|
29
|
+
export function storeDelegation(delegationBase64: string, registryDb?: Object, spaceDid?: string, storedByDid?: string): Promise<void>;
|
|
29
30
|
/**
|
|
30
31
|
* Load a stored delegation string.
|
|
31
32
|
* Reads from registry DB if provided, otherwise localStorage.
|
|
@@ -43,3 +44,15 @@ export function loadStoredDelegation(registryDb?: Object): Promise<string | null
|
|
|
43
44
|
* @param {string} [delegationBase64] - specific delegation to remove (if omitted, removes all)
|
|
44
45
|
*/
|
|
45
46
|
export function clearStoredDelegation(registryDb?: Object, delegationBase64?: string): Promise<void>;
|
|
47
|
+
/**
|
|
48
|
+
* Multi-line text suitable for a native HTML `title` on the linked-device UCAN badge.
|
|
49
|
+
* Parses each stored delegation via {@link parseDelegation}.
|
|
50
|
+
*
|
|
51
|
+
* @param {Array<{ delegation?: string, space_did?: string, label?: string }>} entries
|
|
52
|
+
* @returns {Promise<string>}
|
|
53
|
+
*/
|
|
54
|
+
export function formatDelegationsTooltipSummary(entries: Array<{
|
|
55
|
+
delegation?: string;
|
|
56
|
+
space_did?: string;
|
|
57
|
+
label?: string;
|
|
58
|
+
}>): Promise<string>;
|
|
@@ -98,11 +98,12 @@ export async function parseDelegation(proofString) {
|
|
|
98
98
|
* @param {string} delegationBase64
|
|
99
99
|
* @param {Object} [registryDb] - OrbitDB registry database
|
|
100
100
|
* @param {string} [spaceDid] - Storacha space DID (for registry metadata)
|
|
101
|
+
* @param {string} [storedByDid] - device DID that imported the delegation (per-device counts in UI)
|
|
101
102
|
*/
|
|
102
|
-
export async function storeDelegation(delegationBase64, registryDb, spaceDid) {
|
|
103
|
+
export async function storeDelegation(delegationBase64, registryDb, spaceDid, storedByDid) {
|
|
103
104
|
if (registryDb) {
|
|
104
105
|
try {
|
|
105
|
-
await storeDelegationEntry(registryDb, delegationBase64, spaceDid);
|
|
106
|
+
await storeDelegationEntry(registryDb, delegationBase64, spaceDid, undefined, storedByDid);
|
|
106
107
|
console.log('[storacha] Delegation stored in registry DB');
|
|
107
108
|
return;
|
|
108
109
|
} catch (err) {
|
|
@@ -162,3 +163,120 @@ export async function clearStoredDelegation(registryDb, delegationBase64) {
|
|
|
162
163
|
localStorage.removeItem(STORAGE_KEY_DELEGATION);
|
|
163
164
|
}
|
|
164
165
|
}
|
|
166
|
+
|
|
167
|
+
/** Max characters for native `title` tooltips (browser-dependent display). */
|
|
168
|
+
const DELEGATION_TOOLTIP_MAX = 1200;
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* @param {string} s
|
|
172
|
+
* @param {number} max
|
|
173
|
+
*/
|
|
174
|
+
function truncateMiddle(s, max) {
|
|
175
|
+
if (s.length <= max) return s;
|
|
176
|
+
const half = Math.floor((max - 1) / 2);
|
|
177
|
+
return `${s.slice(0, half)}…${s.slice(s.length - half)}`;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* @param {unknown} p - UCANTO principal or string
|
|
182
|
+
*/
|
|
183
|
+
function principalDid(p) {
|
|
184
|
+
if (p == null) return '—';
|
|
185
|
+
if (typeof p === 'string') return p;
|
|
186
|
+
if (typeof p === 'object' && p !== null && 'did' in p && typeof p.did === 'function') {
|
|
187
|
+
try {
|
|
188
|
+
return /** @type {{ did: () => string }} */ (p).did();
|
|
189
|
+
} catch {
|
|
190
|
+
return String(p);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
return String(p);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* @param {unknown} exp - UCAN expiration (seconds or ms since epoch)
|
|
198
|
+
*/
|
|
199
|
+
function formatUcanExpiration(exp) {
|
|
200
|
+
if (exp == null) return null;
|
|
201
|
+
const n = typeof exp === 'bigint' ? Number(exp) : Number(exp);
|
|
202
|
+
if (!Number.isFinite(n) || n <= 0) return null;
|
|
203
|
+
const ms = n > 1e12 ? n : n * 1000;
|
|
204
|
+
try {
|
|
205
|
+
return new Date(ms).toUTCString();
|
|
206
|
+
} catch {
|
|
207
|
+
return String(exp);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* @param {any} d - Parsed delegation from {@link parseDelegation}
|
|
213
|
+
* @returns {string}
|
|
214
|
+
*/
|
|
215
|
+
function summarizeParsedDelegationForTooltip(d) {
|
|
216
|
+
const lines = [];
|
|
217
|
+
lines.push(`Issuer: ${truncateMiddle(principalDid(d?.issuer), 64)}`);
|
|
218
|
+
lines.push(`Audience: ${truncateMiddle(principalDid(d?.audience), 64)}`);
|
|
219
|
+
const expStr = formatUcanExpiration(d?.expiration);
|
|
220
|
+
if (expStr) lines.push(`Expires: ${expStr}`);
|
|
221
|
+
const nbStr = formatUcanExpiration(d?.notBefore);
|
|
222
|
+
if (nbStr) lines.push(`Not before: ${nbStr}`);
|
|
223
|
+
const caps = d?.capabilities;
|
|
224
|
+
if (Array.isArray(caps) && caps.length > 0) {
|
|
225
|
+
const shown = caps.slice(0, 6).map((c) => {
|
|
226
|
+
const can = c?.can ?? '?';
|
|
227
|
+
const w = c?.with != null ? String(c.with) : '—';
|
|
228
|
+
return `${can} → ${truncateMiddle(w, 48)}`;
|
|
229
|
+
});
|
|
230
|
+
let capBlock = shown.join('\n');
|
|
231
|
+
if (caps.length > 6) capBlock += `\n… +${caps.length - 6} more`;
|
|
232
|
+
lines.push(`Capabilities:\n${capBlock}`);
|
|
233
|
+
}
|
|
234
|
+
const facts = d?.facts;
|
|
235
|
+
if (Array.isArray(facts) && facts.length > 0) {
|
|
236
|
+
lines.push(`Facts: ${facts.length} attached`);
|
|
237
|
+
}
|
|
238
|
+
try {
|
|
239
|
+
if (d?.cid != null) lines.push(`Root CID: ${truncateMiddle(String(d.cid), 72)}`);
|
|
240
|
+
} catch {
|
|
241
|
+
/* ignore */
|
|
242
|
+
}
|
|
243
|
+
return lines.join('\n');
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Multi-line text suitable for a native HTML `title` on the linked-device UCAN badge.
|
|
248
|
+
* Parses each stored delegation via {@link parseDelegation}.
|
|
249
|
+
*
|
|
250
|
+
* @param {Array<{ delegation?: string, space_did?: string, label?: string }>} entries
|
|
251
|
+
* @returns {Promise<string>}
|
|
252
|
+
*/
|
|
253
|
+
export async function formatDelegationsTooltipSummary(entries) {
|
|
254
|
+
if (!Array.isArray(entries) || entries.length === 0) {
|
|
255
|
+
return 'No UCAN delegations';
|
|
256
|
+
}
|
|
257
|
+
const blocks = [];
|
|
258
|
+
for (let i = 0; i < entries.length; i++) {
|
|
259
|
+
const e = entries[i];
|
|
260
|
+
const raw = e?.delegation;
|
|
261
|
+
if (typeof raw !== 'string' || !raw.trim()) continue;
|
|
262
|
+
let head =
|
|
263
|
+
entries.length > 1 ? `Delegation ${i + 1} of ${entries.length}` : 'UCAN delegation';
|
|
264
|
+
if (e.space_did) head += ` · Space ${truncateMiddle(e.space_did, 40)}`;
|
|
265
|
+
if (e.label && e.label !== 'default') head += ` · ${e.label}`;
|
|
266
|
+
try {
|
|
267
|
+
const parsed = await parseDelegation(raw);
|
|
268
|
+
blocks.push(`${head}\n${summarizeParsedDelegationForTooltip(parsed)}`);
|
|
269
|
+
} catch (err) {
|
|
270
|
+
const msg =
|
|
271
|
+
err && typeof err === 'object' && 'message' in err
|
|
272
|
+
? String(/** @type {{ message: string }} */ (err).message)
|
|
273
|
+
: String(err);
|
|
274
|
+
blocks.push(`${head}\n(parse failed: ${truncateMiddle(msg, 100)})`);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
if (blocks.length === 0) return 'No valid delegation payloads';
|
|
278
|
+
const joined = blocks.join('\n\n────────\n\n');
|
|
279
|
+
return joined.length > DELEGATION_TOOLTIP_MAX
|
|
280
|
+
? truncateMiddle(joined, DELEGATION_TOOLTIP_MAX)
|
|
281
|
+
: joined;
|
|
282
|
+
}
|
|
@@ -13,16 +13,41 @@
|
|
|
13
13
|
libp2p = null,
|
|
14
14
|
preferWorkerMode = false,
|
|
15
15
|
signingPreference = null,
|
|
16
|
+
/** When false, hide the floating FAB (e.g. host app opens the panel from the footer). */
|
|
17
|
+
fabVisible = true,
|
|
18
|
+
/**
|
|
19
|
+
* When set, panel open state is synced with this store so the host can toggle from a footer control.
|
|
20
|
+
* @type {import('svelte/store').Writable<boolean> | null}
|
|
21
|
+
*/
|
|
22
|
+
panelOpenStore = null,
|
|
16
23
|
} = $props();
|
|
17
24
|
|
|
18
25
|
let showPanel = $state(false);
|
|
19
26
|
let isHovered = $state(false);
|
|
27
|
+
|
|
28
|
+
function setPanelOpen(/** @type {boolean} */ v) {
|
|
29
|
+
showPanel = v;
|
|
30
|
+
panelOpenStore?.set(v);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function togglePanel() {
|
|
34
|
+
setPanelOpen(!showPanel);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
$effect(() => {
|
|
38
|
+
if (!panelOpenStore) return;
|
|
39
|
+
const unsub = panelOpenStore.subscribe((v) => {
|
|
40
|
+
showPanel = v;
|
|
41
|
+
});
|
|
42
|
+
return unsub;
|
|
43
|
+
});
|
|
20
44
|
</script>
|
|
21
45
|
|
|
22
46
|
<!-- Floating Storacha FAB Button -->
|
|
47
|
+
{#if fabVisible}
|
|
23
48
|
<button
|
|
24
49
|
data-testid="storacha-fab-toggle"
|
|
25
|
-
onclick={() => (
|
|
50
|
+
onclick={() => togglePanel()}
|
|
26
51
|
onmouseenter={() => (isHovered = true)}
|
|
27
52
|
onmouseleave={() => (isHovered = false)}
|
|
28
53
|
title={showPanel ? 'Hide P2Pass panel' : 'Open P2Pass — passkey & UCAN, peer-to-peer'}
|
|
@@ -84,13 +109,14 @@
|
|
|
84
109
|
/>
|
|
85
110
|
</svg>
|
|
86
111
|
</button>
|
|
112
|
+
{/if}
|
|
87
113
|
|
|
88
114
|
<!-- Backdrop overlay -->
|
|
89
115
|
{#if showPanel}
|
|
90
116
|
<!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
|
|
91
117
|
<div
|
|
92
|
-
onclick={() => (
|
|
93
|
-
onkeydown={(e) => e.key === 'Escape' && (
|
|
118
|
+
onclick={() => setPanelOpen(false)}
|
|
119
|
+
onkeydown={(e) => e.key === 'Escape' && setPanelOpen(false)}
|
|
94
120
|
role="presentation"
|
|
95
121
|
style="
|
|
96
122
|
position: fixed;
|
|
@@ -125,7 +151,7 @@
|
|
|
125
151
|
{onBackup}
|
|
126
152
|
{onAuthenticate}
|
|
127
153
|
onPairingPromptOpen={() => {
|
|
128
|
-
|
|
154
|
+
setPanelOpen(true);
|
|
129
155
|
}}
|
|
130
156
|
{libp2p}
|
|
131
157
|
{preferWorkerMode}
|