@agent-network-protocol/dsh-anp-identity 0.1.0-dsh-test.20260831.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/lib/index.js ADDED
@@ -0,0 +1,1096 @@
1
+ /** Host-only ANP Identity service for DeepSeek Harness. */
2
+ import { randomUUID } from 'node:crypto';
3
+ import { homedir } from 'node:os';
4
+ import { isAbsolute, join } from 'node:path';
5
+ import { Service } from '@deepseek-ai/cordis';
6
+ import z from '@deepseek-ai/schemastery';
7
+ import { CatalogStore, CATALOG_SCHEMA, CREATE_INTENT_SCHEMA, emptyCatalog, entryReference, findEntry, normalizeHandle, } from './catalog.js';
8
+ import { pluginError, AnpIdentityPluginError } from './errors.js';
9
+ import { createAuthenticatedHttp } from './http-auth.js';
10
+ import { ANP_IDENTITY_NATIVE_PROVIDER_PROTOCOL, ANP_IDENTITY_PROVIDER_PROTOCOL, ANP_IDENTITY_SERVICE_PROTOCOL, } from './provider-api.js';
11
+ export { AnpIdentityPluginError } from './errors.js';
12
+ export { ANP_IDENTITY_HTTP_MAX_BODY_BYTES, } from './http-auth.js';
13
+ export { ANP_IDENTITY_PROVIDER_PROTOCOL, ANP_IDENTITY_SERVICE_PROTOCOL, } from './provider-api.js';
14
+ export const Config = z.object({
15
+ stateRoot: z.string(),
16
+ allowConsumers: z.array(z.string()).default([]),
17
+ allowProviderConsumers: z.array(z.string()).default([]),
18
+ httpAllowedOrigins: z.dict(z.array(z.string())).default({}),
19
+ recoveryOnOpen: z.boolean().default(true),
20
+ });
21
+ const CLIENT_CAPABILITIES = new Set([
22
+ 'identity:read',
23
+ 'identity:create',
24
+ 'identity:sign',
25
+ 'identity:document-update',
26
+ 'identity:http-auth',
27
+ 'identity:delete',
28
+ 'identity:recover',
29
+ 'identity:handle',
30
+ ]);
31
+ const ALL_PROVIDER_CAPABILITIES = [
32
+ 'IDENTITY_READ',
33
+ 'IDENTITY_CREATE',
34
+ 'IDENTITY_IMPORT',
35
+ 'IDENTITY_SIGN',
36
+ 'IDENTITY_ECDH_SEALED',
37
+ 'IDENTITY_DOCUMENT_UPDATE',
38
+ 'IDENTITY_KEY_LIFECYCLE',
39
+ 'IDENTITY_DELETE',
40
+ 'IDENTITY_HTTP_SIGNATURE',
41
+ 'AWIKI_LEGACY_ROOT_TRANSFER_V1',
42
+ ];
43
+ const HOST_LEASE_RENEWAL_SKEW_MS = 60_000;
44
+ /** One multi-DID ANP Identity service with a replaceable native Provider. */
45
+ export class AnpIdentityService extends Service {
46
+ static Config = Config;
47
+ resolvedConfig;
48
+ catalogStore;
49
+ providerSlot;
50
+ constructor(ctx, config) {
51
+ super(ctx, 'anpIdentity');
52
+ this.resolvedConfig = resolveConfig(config);
53
+ this.catalogStore = new CatalogStore(this.resolvedConfig.stateRoot);
54
+ ctx.effect(() => async () => {
55
+ const slot = this.providerSlot;
56
+ if (slot !== undefined)
57
+ await this.disposeSlot(slot);
58
+ }, 'anp-identity: dispose native provider');
59
+ }
60
+ async health() {
61
+ const slot = this.providerSlot;
62
+ if (slot === undefined || slot.disposed) {
63
+ return {
64
+ status: 'unavailable',
65
+ protocol: ANP_IDENTITY_SERVICE_PROTOCOL,
66
+ catalog: 'unavailable',
67
+ };
68
+ }
69
+ try {
70
+ await slot.ready;
71
+ }
72
+ catch {
73
+ return {
74
+ status: 'unavailable',
75
+ protocol: ANP_IDENTITY_SERVICE_PROTOCOL,
76
+ catalog: slot.catalog,
77
+ };
78
+ }
79
+ return {
80
+ status: slot.catalog === 'ready' ? 'ready' : 'degraded',
81
+ protocol: ANP_IDENTITY_SERVICE_PROTOCOL,
82
+ providerProtocol: ANP_IDENTITY_PROVIDER_PROTOCOL,
83
+ catalog: slot.catalog,
84
+ };
85
+ }
86
+ registerProvider(registration) {
87
+ if (this.providerSlot !== undefined)
88
+ throw pluginError('provider_unavailable');
89
+ const slot = {
90
+ id: Symbol('anp-identity-provider'),
91
+ leases: new Set(),
92
+ disposed: false,
93
+ initialized: false,
94
+ catalog: 'unavailable',
95
+ ready: Promise.resolve(registration).then(async (value) => {
96
+ if (slot.disposed)
97
+ throw pluginError('provider_disposed');
98
+ const provider = validateRegistration(value);
99
+ slot.provider = provider;
100
+ await this.initializeSlot(slot, provider);
101
+ if (slot.disposed)
102
+ throw pluginError('provider_disposed');
103
+ slot.initialized = true;
104
+ return provider;
105
+ }),
106
+ };
107
+ this.providerSlot = slot;
108
+ void slot.ready.catch(() => { });
109
+ return () => this.disposeSlot(slot);
110
+ }
111
+ async acquireClient(input) {
112
+ const request = validateClientRequest(input, this.resolvedConfig);
113
+ const slot = this.requireSlot();
114
+ const provider = await this.awaitProvider(slot);
115
+ const capabilities = nativeClientCapabilities(request.capabilities);
116
+ let native;
117
+ try {
118
+ native = provider.acquireLease({
119
+ consumer: request.consumer,
120
+ capabilities,
121
+ ttlSeconds: request.ttlSeconds,
122
+ });
123
+ }
124
+ catch (error) {
125
+ throw mapNativeError(error);
126
+ }
127
+ const lease = new ClientLease(this, slot, native, request.consumer, request.capabilities, request.httpOrigins, Date.now() + request.ttlSeconds * 1_000);
128
+ slot.leases.add(lease);
129
+ return lease;
130
+ }
131
+ acquireProvider(input) {
132
+ const request = validateProviderRequest(input, this.resolvedConfig);
133
+ const slot = this.requireSlot();
134
+ const provider = slot.provider;
135
+ if (provider === undefined || !slot.initialized)
136
+ throw pluginError('provider_unavailable');
137
+ let native;
138
+ try {
139
+ native = provider.acquireLease({
140
+ consumer: request.consumer,
141
+ capabilities: [...request.capabilities],
142
+ ttlSeconds: request.ttlSeconds,
143
+ });
144
+ }
145
+ catch (error) {
146
+ throw mapNativeError(error);
147
+ }
148
+ const lease = new HostLease(this, slot, provider, native, request.consumer, request.capabilities, request.ttlSeconds);
149
+ slot.leases.add(lease);
150
+ return lease;
151
+ }
152
+ async listForClient(lease, input) {
153
+ lease.assertCapability('identity:read');
154
+ const catalog = await this.loadCatalog(lease.slot);
155
+ const native = await nativeCall(() => lease.native.list());
156
+ const byId = new Map(native.map(value => [value.reference.identityId, value]));
157
+ const output = [];
158
+ for (const entry of catalog.entries) {
159
+ if (entry.state !== 'active' || !entry.grantedConsumers.includes(lease.consumer))
160
+ continue;
161
+ const descriptor = byId.get(entry.identityId);
162
+ if (descriptor === undefined || (input?.state !== undefined && descriptor.state !== input.state))
163
+ continue;
164
+ output.push({
165
+ ...descriptor,
166
+ ...(entry.label === undefined ? {} : { label: entry.label }),
167
+ ...(entry.handle === undefined ? {} : { handle: entry.handle }),
168
+ catalogState: entry.state,
169
+ });
170
+ }
171
+ return output.sort((left, right) => left.reference.did.localeCompare(right.reference.did));
172
+ }
173
+ async createForClient(lease, input) {
174
+ lease.assertCapability('identity:create');
175
+ validateCreateInput(input);
176
+ const requestId = input.requestId ?? randomUUID();
177
+ const createdAt = new Date().toISOString();
178
+ await this.catalogStore.reserveIntent({
179
+ schema: CREATE_INTENT_SCHEMA,
180
+ requestId,
181
+ consumer: lease.consumer,
182
+ ...(input.label === undefined ? {} : { label: input.label }),
183
+ ...(input.handle === undefined ? {} : { handle: normalizeHandle(input.handle) }),
184
+ createdAt,
185
+ baselineIdentityIds: [],
186
+ });
187
+ let identity;
188
+ try {
189
+ identity = await this.catalogStore.withNativeCreateLock(async () => {
190
+ lease.assertCapability('identity:create');
191
+ const baseline = await nativeCall(() => lease.native.list());
192
+ await this.catalogStore.updateIntent(requestId, current => ({
193
+ ...current,
194
+ baselineIdentityIds: baseline.map(value => value.reference.identityId).sort(),
195
+ }));
196
+ const created = await this.createNativeWithRecovery(lease.native, input.identity, requestId, baseline.map(value => value.reference.identityId));
197
+ await this.catalogStore.updateIntent(requestId, current => ({
198
+ ...current,
199
+ reference: created.reference,
200
+ }));
201
+ return created;
202
+ });
203
+ await this.catalogStore.commitIntent(requestId);
204
+ await this.catalogStore.deleteIntent(requestId);
205
+ }
206
+ catch (error) {
207
+ throw mapOperationError(error);
208
+ }
209
+ return new ManagedClient(this, lease, identity.reference, identity);
210
+ }
211
+ async getForClient(lease, reference) {
212
+ lease.assertCapability('identity:read');
213
+ const resolved = await this.resolveAuthorized(lease, reference);
214
+ return new ManagedClient(this, lease, resolved);
215
+ }
216
+ async deleteForClient(lease, reference, _input) {
217
+ lease.assertCapability('identity:delete');
218
+ const resolved = await this.resolveAuthorized(lease, reference);
219
+ await this.catalogStore.mutate((catalog) => {
220
+ const entry = findEntry(catalog, resolved);
221
+ if (entry === undefined)
222
+ throw pluginError('identity_not_found');
223
+ if (entry.state !== 'active')
224
+ throw pluginError('identity_deleting');
225
+ if (entry.grantedConsumers.some(consumer => consumer !== lease.consumer)) {
226
+ throw pluginError('identity_in_use');
227
+ }
228
+ return replaceEntry(catalog, entry, { ...entry, state: 'deleting' });
229
+ });
230
+ await nativeCall(() => lease.native.delete(resolved));
231
+ await this.catalogStore.mutate(catalog => ({
232
+ ...catalog,
233
+ entries: catalog.entries.filter(entry => entry.identityId !== resolved.identityId),
234
+ }));
235
+ }
236
+ async recoverForClient(lease) {
237
+ lease.assertCapability('identity:recover');
238
+ const native = await nativeCall(() => lease.native.recover());
239
+ const report = await this.reconcile(lease.slot, lease.native, true, false);
240
+ return { ...native, ...report };
241
+ }
242
+ async setHandleForClient(lease, reference, handle) {
243
+ lease.assertCapability('identity:handle');
244
+ const resolved = await this.resolveAuthorized(lease, reference);
245
+ const normalized = handle === null ? undefined : normalizeHandle(handle);
246
+ await this.catalogStore.mutate((catalog) => {
247
+ const entry = findEntry(catalog, resolved);
248
+ assertGrantedEntry(entry, lease.consumer);
249
+ if (normalized !== undefined
250
+ && catalog.entries.some(candidate => candidate.identityId !== entry.identityId
251
+ && candidate.handle === normalized)) {
252
+ throw pluginError('handle_conflict');
253
+ }
254
+ const { handle: _currentHandle, ...withoutHandle } = entry;
255
+ return replaceEntry(catalog, entry, {
256
+ ...withoutHandle,
257
+ ...(normalized === undefined ? {} : { handle: normalized }),
258
+ });
259
+ });
260
+ }
261
+ async assertManagedAccess(lease, reference) {
262
+ lease.assertActive();
263
+ const catalog = await this.loadCatalog(lease.slot);
264
+ assertGrantedEntry(findEntry(catalog, reference), lease.consumer);
265
+ }
266
+ async assertHostTransitionAccess(slot, consumer, expectedCurrentDid, successorDid) {
267
+ const catalog = await this.loadCatalog(slot);
268
+ assertGrantedEntry(catalog.entries.find(entry => entry.did === expectedCurrentDid), consumer);
269
+ assertGrantedEntry(catalog.entries.find(entry => entry.did === successorDid), consumer);
270
+ }
271
+ async grantConsumer(slot, reference, consumer) {
272
+ this.assertAllowedConsumer(consumer);
273
+ this.assertSlot(slot);
274
+ await this.catalogStore.mutate((catalog) => {
275
+ const entry = findEntry(catalog, reference);
276
+ if (entry === undefined)
277
+ throw pluginError('identity_not_found');
278
+ if (entry.state === 'deleting')
279
+ throw pluginError('identity_deleting');
280
+ return replaceEntry(catalog, entry, {
281
+ ...entry,
282
+ state: 'active',
283
+ grantedConsumers: [...new Set([...entry.grantedConsumers, consumer])].sort(),
284
+ });
285
+ });
286
+ }
287
+ async revokeConsumer(slot, reference, consumer) {
288
+ this.assertSlot(slot);
289
+ await this.catalogStore.mutate((catalog) => {
290
+ const entry = findEntry(catalog, reference);
291
+ if (entry === undefined)
292
+ throw pluginError('identity_not_found');
293
+ if (entry.state !== 'active')
294
+ throw pluginError('identity_deleting');
295
+ const grantedConsumers = entry.grantedConsumers.filter(value => value !== consumer);
296
+ return replaceEntry(catalog, entry, {
297
+ ...entry,
298
+ grantedConsumers,
299
+ state: grantedConsumers.length === 0 ? 'unclaimed' : 'active',
300
+ });
301
+ });
302
+ }
303
+ async createForHost(slot, native, consumer, request) {
304
+ this.assertSlot(slot);
305
+ const requestId = randomUUID();
306
+ const createdAt = new Date().toISOString();
307
+ await this.catalogStore.reserveIntent({
308
+ schema: CREATE_INTENT_SCHEMA,
309
+ requestId,
310
+ consumer,
311
+ createdAt,
312
+ baselineIdentityIds: [],
313
+ });
314
+ try {
315
+ const identity = await this.catalogStore.withNativeCreateLock(async () => {
316
+ this.assertSlot(slot);
317
+ const baseline = await nativeCall(() => native.list());
318
+ await this.catalogStore.updateIntent(requestId, current => ({
319
+ ...current,
320
+ baselineIdentityIds: baseline.map(value => value.reference.identityId).sort(),
321
+ }));
322
+ const created = await this.createNativeWithRecovery(native, request, requestId, baseline.map(value => value.reference.identityId));
323
+ await this.catalogStore.updateIntent(requestId, current => ({
324
+ ...current,
325
+ reference: created.reference,
326
+ }));
327
+ return created;
328
+ });
329
+ await this.catalogStore.commitIntent(requestId);
330
+ await this.catalogStore.deleteIntent(requestId);
331
+ return identity;
332
+ }
333
+ catch (error) {
334
+ throw mapOperationError(error);
335
+ }
336
+ }
337
+ async deleteForHost(slot, native, consumer, reference) {
338
+ this.assertSlot(slot);
339
+ let catalogEntry;
340
+ try {
341
+ const catalog = await this.loadCatalog(slot);
342
+ catalogEntry = findEntry(catalog, reference);
343
+ }
344
+ catch (error) {
345
+ if (!isPluginCode(error, 'catalog_corrupt'))
346
+ throw error;
347
+ throw pluginError('catalog_corrupt');
348
+ }
349
+ if (catalogEntry !== undefined) {
350
+ if (catalogEntry.state === 'deleting')
351
+ throw pluginError('identity_deleting');
352
+ if (catalogEntry.grantedConsumers.some(value => value !== consumer)) {
353
+ throw pluginError('identity_in_use');
354
+ }
355
+ await this.catalogStore.mutate(catalog => {
356
+ const entry = findEntry(catalog, reference);
357
+ if (entry === undefined)
358
+ throw pluginError('identity_not_found');
359
+ return replaceEntry(catalog, entry, { ...entry, state: 'deleting' });
360
+ });
361
+ }
362
+ await nativeCall(() => native.delete(reference));
363
+ if (catalogEntry !== undefined) {
364
+ await this.catalogStore.mutate(catalog => ({
365
+ ...catalog,
366
+ entries: catalog.entries.filter(entry => entry.identityId !== reference.identityId),
367
+ }));
368
+ }
369
+ }
370
+ async createNativeWithRecovery(native, request, requestId, baselineIdentityIds) {
371
+ try {
372
+ return await nativeCall(() => native.create(request));
373
+ }
374
+ catch (createError) {
375
+ try {
376
+ const baseline = new Set(baselineIdentityIds);
377
+ const candidates = (await nativeCall(() => native.list()))
378
+ .filter(value => !baseline.has(value.reference.identityId));
379
+ if (candidates.length === 0) {
380
+ await this.catalogStore.deleteIntent(requestId);
381
+ }
382
+ else if (candidates.length === 1) {
383
+ return await nativeCall(() => native.publicIdentity(candidates[0].reference));
384
+ }
385
+ }
386
+ catch {
387
+ // Keep the intent when the native outcome cannot be determined safely.
388
+ }
389
+ throw createError;
390
+ }
391
+ }
392
+ removeLease(slot, lease) {
393
+ slot.leases.delete(lease);
394
+ }
395
+ async initializeSlot(slot, provider) {
396
+ try {
397
+ await this.catalogStore.initialize();
398
+ slot.catalog = 'ready';
399
+ }
400
+ catch (error) {
401
+ if (isPluginCode(error, 'catalog_corrupt'))
402
+ slot.catalog = 'corrupt';
403
+ else
404
+ throw error;
405
+ }
406
+ const native = provider.acquireLease({
407
+ consumer: '@agent-network-protocol/dsh-anp-identity',
408
+ capabilities: ALL_PROVIDER_CAPABILITIES,
409
+ ttlSeconds: 3_600,
410
+ });
411
+ try {
412
+ if (this.resolvedConfig.recoveryOnOpen)
413
+ await nativeCall(() => native.recover());
414
+ await this.reconcile(slot, native, false, false);
415
+ }
416
+ finally {
417
+ native.dispose();
418
+ }
419
+ }
420
+ async reconcile(slot, native, rebuildCorrupt, runNativeRecovery) {
421
+ if (runNativeRecovery)
422
+ await nativeCall(() => native.recover());
423
+ const descriptors = await nativeCall(() => native.list());
424
+ const storeById = new Map(descriptors.map(value => [value.reference.identityId, value.reference]));
425
+ let catalog;
426
+ try {
427
+ catalog = await this.catalogStore.load();
428
+ slot.catalog = 'ready';
429
+ }
430
+ catch (error) {
431
+ if (!isPluginCode(error, 'catalog_corrupt'))
432
+ throw error;
433
+ slot.catalog = 'corrupt';
434
+ if (!rebuildCorrupt) {
435
+ return { catalogRebuilt: false, unclaimedIdentityCount: descriptors.length };
436
+ }
437
+ catalog = await this.catalogStore.replace({
438
+ ...emptyCatalog(),
439
+ entries: descriptors.map(value => unclaimedEntry(value.reference)),
440
+ });
441
+ slot.catalog = 'ready';
442
+ return { catalogRebuilt: true, unclaimedIdentityCount: catalog.entries.length };
443
+ }
444
+ for (const intent of await this.catalogStore.listIntents()) {
445
+ let reference = intent.reference;
446
+ if (reference === undefined) {
447
+ const baseline = new Set(intent.baselineIdentityIds);
448
+ const known = new Set(catalog.entries.map(value => value.identityId));
449
+ const candidates = descriptors
450
+ .map(value => value.reference)
451
+ .filter(value => !baseline.has(value.identityId) && !known.has(value.identityId));
452
+ if (candidates.length === 1) {
453
+ const recovered = candidates[0];
454
+ reference = recovered;
455
+ await this.catalogStore.updateIntent(intent.requestId, current => ({
456
+ ...current,
457
+ reference: recovered,
458
+ }));
459
+ }
460
+ else if (candidates.length === 0) {
461
+ await this.catalogStore.deleteIntent(intent.requestId);
462
+ continue;
463
+ }
464
+ else {
465
+ for (const candidate of candidates) {
466
+ catalog = await addUnclaimed(this.catalogStore, catalog, candidate);
467
+ }
468
+ await this.catalogStore.deleteIntent(intent.requestId);
469
+ continue;
470
+ }
471
+ }
472
+ const storedReference = storeById.get(reference.identityId);
473
+ if (storedReference === undefined || !sameReference(storedReference, reference)) {
474
+ await this.catalogStore.deleteIntent(intent.requestId);
475
+ continue;
476
+ }
477
+ await this.catalogStore.commitIntent(intent.requestId);
478
+ await this.catalogStore.deleteIntent(intent.requestId);
479
+ catalog = await this.catalogStore.load();
480
+ }
481
+ catalog = await this.catalogStore.load();
482
+ for (const entry of catalog.entries.filter(value => value.state === 'deleting')) {
483
+ const reference = storeById.get(entry.identityId);
484
+ if (reference !== undefined) {
485
+ await nativeCall(() => native.delete(reference));
486
+ storeById.delete(entry.identityId);
487
+ }
488
+ }
489
+ catalog = await this.catalogStore.mutate(current => ({
490
+ ...current,
491
+ entries: current.entries.filter(entry => entry.state !== 'deleting'
492
+ && sameReference(storeById.get(entry.identityId), entryReference(entry))),
493
+ }));
494
+ const known = new Set(catalog.entries.map(value => value.identityId));
495
+ for (const reference of storeById.values()) {
496
+ if (!known.has(reference.identityId)) {
497
+ catalog = await addUnclaimed(this.catalogStore, catalog, reference);
498
+ known.add(reference.identityId);
499
+ }
500
+ }
501
+ return {
502
+ catalogRebuilt: false,
503
+ unclaimedIdentityCount: catalog.entries.filter(value => value.state === 'unclaimed').length,
504
+ };
505
+ }
506
+ async resolveAuthorized(lease, reference) {
507
+ lease.assertActive();
508
+ const catalog = await this.loadCatalog(lease.slot);
509
+ const entry = 'handle' in reference
510
+ ? catalog.entries.find(value => value.handle === normalizeHandle(reference.handle))
511
+ : findEntry(catalog, reference);
512
+ assertGrantedEntry(entry, lease.consumer);
513
+ return entryReference(entry);
514
+ }
515
+ async loadCatalog(slot) {
516
+ this.assertSlot(slot);
517
+ if (slot.catalog === 'corrupt')
518
+ throw pluginError('catalog_corrupt');
519
+ try {
520
+ return await this.catalogStore.load();
521
+ }
522
+ catch (error) {
523
+ if (isPluginCode(error, 'catalog_corrupt'))
524
+ slot.catalog = 'corrupt';
525
+ throw error;
526
+ }
527
+ }
528
+ assertAllowedConsumer(consumer) {
529
+ validateConsumer(consumer);
530
+ if (!this.resolvedConfig.allowConsumers.has(consumer))
531
+ throw pluginError('consumer_forbidden');
532
+ }
533
+ requireSlot() {
534
+ const slot = this.providerSlot;
535
+ if (slot === undefined || slot.disposed)
536
+ throw pluginError('provider_unavailable');
537
+ return slot;
538
+ }
539
+ assertSlot(slot) {
540
+ if (slot.disposed || this.providerSlot?.id !== slot.id)
541
+ throw pluginError('provider_disposed');
542
+ }
543
+ async awaitProvider(slot) {
544
+ this.assertSlot(slot);
545
+ try {
546
+ const provider = await slot.ready;
547
+ this.assertSlot(slot);
548
+ return provider;
549
+ }
550
+ catch (error) {
551
+ if (isPluginCode(error, 'provider_disposed'))
552
+ throw error;
553
+ throw pluginError('provider_unavailable');
554
+ }
555
+ }
556
+ async disposeSlot(slot) {
557
+ if (slot.disposed)
558
+ return;
559
+ slot.disposed = true;
560
+ if (this.providerSlot?.id === slot.id)
561
+ this.providerSlot = undefined;
562
+ for (const lease of [...slot.leases])
563
+ await lease.dispose();
564
+ slot.leases.clear();
565
+ }
566
+ }
567
+ class ClientLease {
568
+ service;
569
+ slot;
570
+ native;
571
+ consumer;
572
+ expiresAt;
573
+ capabilities;
574
+ httpOrigins;
575
+ #disposed = false;
576
+ constructor(service, slot, native, consumer, capabilities, httpOrigins, expiresAt) {
577
+ this.service = service;
578
+ this.slot = slot;
579
+ this.native = native;
580
+ this.consumer = consumer;
581
+ this.expiresAt = expiresAt;
582
+ this.capabilities = Object.freeze([...capabilities]);
583
+ this.httpOrigins = httpOrigins;
584
+ }
585
+ list(input) {
586
+ return this.service.listForClient(this, input);
587
+ }
588
+ create(input) {
589
+ return this.service.createForClient(this, input);
590
+ }
591
+ get(reference) {
592
+ return this.service.getForClient(this, reference);
593
+ }
594
+ delete(reference, input) {
595
+ return this.service.deleteForClient(this, reference, input);
596
+ }
597
+ recover() {
598
+ return this.service.recoverForClient(this);
599
+ }
600
+ setHandle(reference, handle) {
601
+ return this.service.setHandleForClient(this, reference, handle);
602
+ }
603
+ async dispose() {
604
+ if (this.#disposed)
605
+ return;
606
+ this.#disposed = true;
607
+ this.native.dispose();
608
+ this.service.removeLease(this.slot, this);
609
+ }
610
+ assertActive() {
611
+ if (this.#disposed || this.slot.disposed || Date.now() >= this.expiresAt) {
612
+ throw pluginError('provider_disposed');
613
+ }
614
+ }
615
+ assertCapability(capability) {
616
+ this.assertActive();
617
+ if (!this.capabilities.includes(capability))
618
+ throw pluginError('capability_forbidden');
619
+ }
620
+ }
621
+ class ManagedClient {
622
+ service;
623
+ lease;
624
+ reference;
625
+ authenticatedHttp;
626
+ cachedIdentity;
627
+ constructor(service, lease, reference, identity) {
628
+ this.service = service;
629
+ this.lease = lease;
630
+ this.reference = reference;
631
+ this.cachedIdentity = identity;
632
+ this.authenticatedHttp = createAuthenticatedHttp(reference, lease.native, lease.httpOrigins, async () => {
633
+ lease.assertCapability('identity:http-auth');
634
+ await service.assertManagedAccess(lease, reference);
635
+ }, () => this.requestSigningKid());
636
+ }
637
+ async publicIdentity() {
638
+ this.lease.assertCapability('identity:read');
639
+ await this.service.assertManagedAccess(this.lease, this.reference);
640
+ const identity = await nativeCall(() => this.lease.native.publicIdentity(this.reference));
641
+ this.cachedIdentity = identity;
642
+ return identity;
643
+ }
644
+ async sign(request) {
645
+ this.lease.assertCapability('identity:sign');
646
+ await this.service.assertManagedAccess(this.lease, this.reference);
647
+ return nativeCall(() => this.lease.native.sign(this.reference, request));
648
+ }
649
+ async signOriginProof(request) {
650
+ this.lease.assertCapability('identity:sign');
651
+ await this.service.assertManagedAccess(this.lease, this.reference);
652
+ return nativeCall(() => this.lease.native.signOriginProof(this.reference, request));
653
+ }
654
+ async verify(request) {
655
+ this.lease.assertCapability('identity:read');
656
+ await this.service.assertManagedAccess(this.lease, this.reference);
657
+ return nativeCall(() => this.lease.native.verify(this.reference, request));
658
+ }
659
+ async prepareDocumentChange(request) {
660
+ this.lease.assertCapability('identity:document-update');
661
+ await this.service.assertManagedAccess(this.lease, this.reference);
662
+ const session = await nativeCall(() => this.lease.native.prepareDocumentChange(this.reference, request));
663
+ return new DocumentChangeClient(this.service, this.lease, this.reference, session, identity => { this.cachedIdentity = identity; });
664
+ }
665
+ async resumeDocumentChange() {
666
+ this.lease.assertCapability('identity:document-update');
667
+ await this.service.assertManagedAccess(this.lease, this.reference);
668
+ const session = await nativeCall(() => this.lease.native.resumeDocumentChange(this.reference));
669
+ return session === undefined
670
+ ? undefined
671
+ : new DocumentChangeClient(this.service, this.lease, this.reference, session, identity => { this.cachedIdentity = identity; });
672
+ }
673
+ async requestSigningKid() {
674
+ const identity = this.cachedIdentity ?? await this.publicIdentity();
675
+ const candidates = identity.activeKeys.filter(key => key.algorithm === 'ed25519'
676
+ && key.purposes.includes('authentication')
677
+ && key.purposes.includes('application_assertion'));
678
+ if (candidates.length !== 1)
679
+ throw pluginError('provider_incompatible');
680
+ return candidates[0].kid;
681
+ }
682
+ }
683
+ class DocumentChangeClient {
684
+ service;
685
+ lease;
686
+ reference;
687
+ native;
688
+ updateIdentity;
689
+ constructor(service, lease, reference, native, updateIdentity) {
690
+ this.service = service;
691
+ this.lease = lease;
692
+ this.reference = reference;
693
+ this.native = native;
694
+ this.updateIdentity = updateIdentity;
695
+ }
696
+ async candidate() {
697
+ await this.assertActive();
698
+ return nativeCall(() => this.native.candidate());
699
+ }
700
+ async beginPublication() {
701
+ await this.assertActive();
702
+ return nativeCall(() => this.native.beginPublication());
703
+ }
704
+ async complete(attempt, result) {
705
+ await this.assertActive();
706
+ const outcome = await nativeCall(() => this.native.complete(attempt, result));
707
+ if (outcome.outcome === 'committed')
708
+ this.updateIdentity(outcome.identity);
709
+ return outcome;
710
+ }
711
+ async reconcile(observation) {
712
+ await this.assertActive();
713
+ const outcome = await nativeCall(() => this.native.reconcile(observation));
714
+ if (outcome.outcome === 'committed')
715
+ this.updateIdentity(outcome.identity);
716
+ return outcome;
717
+ }
718
+ async assertActive() {
719
+ this.lease.assertCapability('identity:document-update');
720
+ await this.service.assertManagedAccess(this.lease, this.reference);
721
+ }
722
+ }
723
+ class HostLease {
724
+ service;
725
+ slot;
726
+ provider;
727
+ consumer;
728
+ ttlSeconds;
729
+ protocol = ANP_IDENTITY_PROVIDER_PROTOCOL;
730
+ capabilities;
731
+ #disposed = false;
732
+ #native;
733
+ #expiresAt;
734
+ #renewAt;
735
+ #retired = new Map();
736
+ constructor(service, slot, provider, native, consumer, capabilities, ttlSeconds) {
737
+ this.service = service;
738
+ this.slot = slot;
739
+ this.provider = provider;
740
+ this.consumer = consumer;
741
+ this.ttlSeconds = ttlSeconds;
742
+ this.capabilities = Object.freeze([...capabilities]);
743
+ this.#native = native;
744
+ const now = Date.now();
745
+ this.#expiresAt = now + ttlSeconds * 1_000;
746
+ this.#renewAt = renewalAt(now, ttlSeconds);
747
+ }
748
+ dispose() {
749
+ if (this.#disposed)
750
+ return;
751
+ this.#disposed = true;
752
+ this.#native.dispose();
753
+ for (const [native, timer] of this.#retired) {
754
+ clearTimeout(timer);
755
+ native.dispose();
756
+ }
757
+ this.#retired.clear();
758
+ this.service.removeLease(this.slot, this);
759
+ }
760
+ info = () => this.call('IDENTITY_READ', () => this.native.info());
761
+ recover = () => this.call('IDENTITY_READ', () => this.native.recover());
762
+ list = () => this.call('IDENTITY_READ', () => this.native.list());
763
+ publicIdentity = (reference) => this.call('IDENTITY_READ', () => this.native.publicIdentity(reference));
764
+ hostStatus = (reference) => this.call('IDENTITY_READ', () => this.native.hostStatus(reference));
765
+ recoverIdentity = (reference) => this.call('IDENTITY_READ', () => this.native.recoverIdentity(reference));
766
+ create = (request) => this.call('IDENTITY_CREATE', () => this.service.createForHost(this.slot, this.native, this.consumer, request));
767
+ delete = (reference) => this.call('IDENTITY_DELETE', () => this.service.deleteForHost(this.slot, this.native, this.consumer, reference));
768
+ sign = (reference, request) => this.call('IDENTITY_SIGN', () => this.native.sign(reference, request));
769
+ verify = (reference, request) => this.call('IDENTITY_READ', () => this.native.verify(reference, request));
770
+ signOriginProof = (reference, request) => this.call('IDENTITY_SIGN', () => this.native.signOriginProof(reference, request));
771
+ signObjectProof = (reference, request) => this.call('IDENTITY_SIGN', () => this.native.signObjectProof(reference, request));
772
+ signDocumentProof = (reference, request) => this.call('IDENTITY_SIGN', () => this.native.signDocumentProof(reference, request));
773
+ prepareHttpSignature = (request) => this.call('IDENTITY_HTTP_SIGNATURE', () => this.native.prepareHttpSignature(request));
774
+ prepareLegacyDidWba = (reference, request) => this.call('IDENTITY_HTTP_SIGNATURE', () => this.native.prepareLegacyDidWba(reference, request));
775
+ prepareDocumentChange = (reference, request) => this.call('IDENTITY_DOCUMENT_UPDATE', () => this.native.prepareDocumentChange(reference, request));
776
+ resumeDocumentChange = (reference) => this.call('IDENTITY_DOCUMENT_UPDATE', () => this.native.resumeDocumentChange(reference));
777
+ prepareIdentityTransition = (request) => this.call('IDENTITY_DOCUMENT_UPDATE', async () => {
778
+ await this.service.assertHostTransitionAccess(this.slot, this.consumer, request.expectedCurrentDid, request.successor.did);
779
+ const native = await this.native.prepareIdentityTransition(request);
780
+ return new HostIdentityTransitionSession(native, operation => this.transitionCall(request.expectedCurrentDid, request.successor.did, operation));
781
+ });
782
+ resumeIdentityTransition = (expectedCurrentDid) => this.call('IDENTITY_DOCUMENT_UPDATE', async () => {
783
+ const native = await this.native.resumeIdentityTransition(expectedCurrentDid);
784
+ if (native === undefined)
785
+ return undefined;
786
+ const candidate = await native.candidate();
787
+ await this.service.assertHostTransitionAccess(this.slot, this.consumer, expectedCurrentDid, candidate.successorDid);
788
+ return new HostIdentityTransitionSession(native, operation => this.transitionCall(expectedCurrentDid, candidate.successorDid, operation));
789
+ });
790
+ adoptVerifiedDocument = (reference, remote) => this.call('IDENTITY_DOCUMENT_UPDATE', () => this.native.adoptVerifiedDocument(reference, remote));
791
+ beginDeviceEnrollment = (request) => this.call('IDENTITY_CREATE', () => this.native.beginDeviceEnrollment(request));
792
+ beginRequestSigningEnrollment = (request) => this.call('IDENTITY_CREATE', () => this.native.beginRequestSigningEnrollment(request));
793
+ resumeEnrollment = (reference) => this.call('IDENTITY_READ', () => this.native.resumeEnrollment(reference));
794
+ importWrappedRoot = (reference, envelope) => this.call('AWIKI_LEGACY_ROOT_TRANSFER_V1', () => this.native.importWrappedRoot(reference, envelope));
795
+ confirmRootPromotion = (reference, request) => this.call('AWIKI_LEGACY_ROOT_TRANSFER_V1', () => this.native.confirmRootPromotion(reference, request));
796
+ signPendingRootObjectProof = (reference, request) => this.call('AWIKI_LEGACY_ROOT_TRANSFER_V1', () => this.native.signPendingRootObjectProof(reference, request));
797
+ ecdhSealed = (request) => this.call('IDENTITY_ECDH_SEALED', () => this.native.ecdhSealed(request));
798
+ exportRootKeySealed = (request) => this.call('AWIKI_LEGACY_ROOT_TRANSFER_V1', () => this.native.exportRootKeySealed(request));
799
+ prepareLegacyRootImport = (request) => this.call('IDENTITY_IMPORT', () => this.native.prepareLegacyRootImport(request));
800
+ prepareIdentityMaterialImport = (request) => this.call('IDENTITY_IMPORT', () => this.native.prepareIdentityMaterialImport(request));
801
+ grantConsumer = (reference, consumer) => this.call('IDENTITY_READ', () => this.service.grantConsumer(this.slot, reference, consumer));
802
+ revokeConsumer = (reference, consumer) => this.call('IDENTITY_READ', () => this.service.revokeConsumer(this.slot, reference, consumer));
803
+ call(capability, operation) {
804
+ if (this.#disposed || this.slot.disposed)
805
+ return Promise.reject(pluginError('provider_disposed'));
806
+ if (!this.capabilities.includes(capability))
807
+ return Promise.reject(pluginError('capability_forbidden'));
808
+ try {
809
+ this.renewIfNeeded();
810
+ }
811
+ catch (error) {
812
+ return Promise.reject(mapNativeError(error));
813
+ }
814
+ return nativeCall(operation);
815
+ }
816
+ transitionCall(expectedCurrentDid, successorDid, operation) {
817
+ return this.call('IDENTITY_DOCUMENT_UPDATE', async () => {
818
+ await this.service.assertHostTransitionAccess(this.slot, this.consumer, expectedCurrentDid, successorDid);
819
+ return operation();
820
+ });
821
+ }
822
+ get native() {
823
+ return this.#native;
824
+ }
825
+ renewIfNeeded() {
826
+ const now = Date.now();
827
+ if (now < this.#renewAt)
828
+ return;
829
+ let next;
830
+ try {
831
+ next = this.provider.acquireLease({
832
+ consumer: this.consumer,
833
+ capabilities: [...this.capabilities],
834
+ ttlSeconds: this.ttlSeconds,
835
+ });
836
+ }
837
+ catch (error) {
838
+ if (now >= this.#expiresAt)
839
+ throw error;
840
+ return;
841
+ }
842
+ const retired = this.#native;
843
+ const retiredExpiresAt = this.#expiresAt;
844
+ this.#native = next;
845
+ this.#expiresAt = now + this.ttlSeconds * 1_000;
846
+ this.#renewAt = renewalAt(now, this.ttlSeconds);
847
+ this.retire(retired, retiredExpiresAt);
848
+ }
849
+ retire(native, expiresAt) {
850
+ const timer = setTimeout(() => {
851
+ this.#retired.delete(native);
852
+ native.dispose();
853
+ }, Math.max(1, expiresAt - Date.now()));
854
+ timer.unref?.();
855
+ this.#retired.set(native, timer);
856
+ }
857
+ }
858
+ class HostIdentityTransitionSession {
859
+ native;
860
+ call;
861
+ constructor(native, call) {
862
+ this.native = native;
863
+ this.call = call;
864
+ }
865
+ candidate = () => this.call(() => this.native.candidate());
866
+ beginPublication = () => this.call(() => this.native.beginPublication());
867
+ complete = (attempt, result) => this.call(() => this.native.complete(attempt, result));
868
+ reconcile = (observation) => this.call(() => this.native.reconcile(observation));
869
+ }
870
+ function renewalAt(issuedAt, ttlSeconds) {
871
+ const ttlMs = ttlSeconds * 1_000;
872
+ return issuedAt + ttlMs - Math.min(HOST_LEASE_RENEWAL_SKEW_MS, Math.floor(ttlMs / 5));
873
+ }
874
+ export default AnpIdentityService;
875
+ function resolveConfig(config) {
876
+ const configured = config.stateRoot?.trim();
877
+ const dshHome = process.env.DSH_HOME?.trim() || join(homedir(), '.dsh');
878
+ const stateRoot = configured === undefined || configured.length === 0
879
+ ? join(dshHome, 'anp-identity')
880
+ : configured;
881
+ if (!isAbsolute(stateRoot))
882
+ throw new TypeError('anp-identity: stateRoot must be absolute');
883
+ const allowConsumers = new Set((config.allowConsumers ?? []).map(validateConsumer));
884
+ const allowProviderConsumers = new Set((config.allowProviderConsumers ?? []).map(validateConsumer));
885
+ for (const consumer of allowProviderConsumers) {
886
+ if (!allowConsumers.has(consumer)) {
887
+ throw new TypeError('anp-identity: Host Provider consumers must also be allowed clients');
888
+ }
889
+ }
890
+ const httpAllowedOrigins = new Map();
891
+ for (const [consumer, origins] of Object.entries(config.httpAllowedOrigins ?? {})) {
892
+ validateConsumer(consumer);
893
+ if (!allowConsumers.has(consumer))
894
+ throw new TypeError('anp-identity: HTTP consumer must be allowed');
895
+ const normalized = origins.map(normalizeHttpsOrigin);
896
+ if (new Set(normalized).size !== normalized.length) {
897
+ throw new TypeError('anp-identity: HTTP origins must be unique');
898
+ }
899
+ httpAllowedOrigins.set(consumer, new Set(normalized));
900
+ }
901
+ return {
902
+ stateRoot,
903
+ allowConsumers,
904
+ allowProviderConsumers,
905
+ httpAllowedOrigins,
906
+ recoveryOnOpen: config.recoveryOnOpen ?? true,
907
+ };
908
+ }
909
+ function validateRegistration(value) {
910
+ if (value?.protocol !== ANP_IDENTITY_NATIVE_PROVIDER_PROTOCOL
911
+ || typeof value.provider?.acquireLease !== 'function') {
912
+ throw pluginError('provider_incompatible');
913
+ }
914
+ return value.provider;
915
+ }
916
+ function validateClientRequest(input, config) {
917
+ const consumer = validateConsumer(input.consumer);
918
+ if (!config.allowConsumers.has(consumer))
919
+ throw pluginError('consumer_forbidden');
920
+ if (!Array.isArray(input.capabilities)
921
+ || input.capabilities.length === 0
922
+ || input.capabilities.some(value => !CLIENT_CAPABILITIES.has(value))
923
+ || new Set(input.capabilities).size !== input.capabilities.length) {
924
+ throw pluginError('invalid_request');
925
+ }
926
+ const ttlSeconds = validateTtl(input.ttlSeconds);
927
+ const configured = config.httpAllowedOrigins.get(consumer) ?? new Set();
928
+ const requested = input.httpOrigins?.map(normalizeHttpsOrigin) ?? [];
929
+ if (requested.some(origin => !configured.has(origin)))
930
+ throw pluginError('http_origin_forbidden');
931
+ return {
932
+ consumer,
933
+ capabilities: [...input.capabilities],
934
+ ttlSeconds,
935
+ httpOrigins: new Set(requested),
936
+ };
937
+ }
938
+ function validateProviderRequest(input, config) {
939
+ const consumer = validateConsumer(input.consumer);
940
+ if (!config.allowProviderConsumers.has(consumer))
941
+ throw pluginError('consumer_forbidden');
942
+ if (!Array.isArray(input.capabilities)
943
+ || input.capabilities.length === 0
944
+ || input.capabilities.some(value => !ALL_PROVIDER_CAPABILITIES.includes(value))
945
+ || new Set(input.capabilities).size !== input.capabilities.length) {
946
+ throw pluginError('invalid_request');
947
+ }
948
+ return { consumer, capabilities: [...input.capabilities], ttlSeconds: validateTtl(input.ttlSeconds) };
949
+ }
950
+ function validateTtl(value) {
951
+ const ttl = value ?? 300;
952
+ if (!Number.isSafeInteger(ttl) || ttl < 1 || ttl > 3_600)
953
+ throw pluginError('invalid_request');
954
+ return ttl;
955
+ }
956
+ function validateConsumer(value) {
957
+ if (typeof value !== 'string'
958
+ || value !== value.trim()
959
+ || !/^[A-Za-z0-9@][A-Za-z0-9@/._-]{0,127}$/u.test(value)) {
960
+ throw new TypeError('anp-identity: consumer must be an exact plugin identifier');
961
+ }
962
+ return value;
963
+ }
964
+ function normalizeHttpsOrigin(value) {
965
+ let url;
966
+ try {
967
+ url = new URL(value);
968
+ }
969
+ catch {
970
+ throw new TypeError('anp-identity: HTTP origins must be absolute HTTPS origins');
971
+ }
972
+ if (url.protocol !== 'https:' || url.origin !== value || url.pathname !== '/'
973
+ || url.search !== '' || url.hash !== '' || url.username !== '' || url.password !== '') {
974
+ throw new TypeError('anp-identity: HTTP origins must be exact HTTPS origins');
975
+ }
976
+ return url.origin;
977
+ }
978
+ function nativeClientCapabilities(capabilities) {
979
+ const output = new Set();
980
+ for (const capability of capabilities) {
981
+ switch (capability) {
982
+ case 'identity:read':
983
+ case 'identity:recover':
984
+ case 'identity:handle':
985
+ output.add('IDENTITY_READ');
986
+ break;
987
+ case 'identity:create':
988
+ output.add('IDENTITY_CREATE');
989
+ output.add('IDENTITY_READ');
990
+ break;
991
+ case 'identity:sign':
992
+ output.add('IDENTITY_SIGN');
993
+ break;
994
+ case 'identity:document-update':
995
+ output.add('IDENTITY_DOCUMENT_UPDATE');
996
+ break;
997
+ case 'identity:http-auth':
998
+ output.add('IDENTITY_HTTP_SIGNATURE');
999
+ break;
1000
+ case 'identity:delete':
1001
+ output.add('IDENTITY_DELETE');
1002
+ break;
1003
+ }
1004
+ }
1005
+ return [...output];
1006
+ }
1007
+ function validateCreateInput(input) {
1008
+ if (typeof input !== 'object' || input === null || typeof input.identity !== 'object') {
1009
+ throw pluginError('invalid_request');
1010
+ }
1011
+ if (input.requestId !== undefined && !/^[A-Za-z0-9_-]{1,128}$/u.test(input.requestId)) {
1012
+ throw pluginError('invalid_request');
1013
+ }
1014
+ if (input.label !== undefined
1015
+ && (input.label !== input.label.trim() || input.label.length < 1 || input.label.length > 256)) {
1016
+ throw pluginError('invalid_request');
1017
+ }
1018
+ if (input.handle !== undefined)
1019
+ normalizeHandle(input.handle);
1020
+ }
1021
+ function assertGrantedEntry(entry, consumer) {
1022
+ if (entry === undefined)
1023
+ throw pluginError('identity_not_found');
1024
+ if (entry.state === 'deleting')
1025
+ throw pluginError('identity_deleting');
1026
+ if (entry.state === 'unclaimed' || !entry.grantedConsumers.includes(consumer)) {
1027
+ throw pluginError('identity_unclaimed');
1028
+ }
1029
+ }
1030
+ function replaceEntry(catalog, previous, next) {
1031
+ return {
1032
+ schema: CATALOG_SCHEMA,
1033
+ catalogGeneration: catalog.catalogGeneration,
1034
+ entries: catalog.entries.map(entry => entry.identityId === previous.identityId ? next : entry),
1035
+ };
1036
+ }
1037
+ function unclaimedEntry(reference) {
1038
+ const now = new Date().toISOString();
1039
+ return {
1040
+ identityId: reference.identityId,
1041
+ did: reference.did,
1042
+ storeId: reference.storeId,
1043
+ grantedConsumers: [],
1044
+ createdAt: now,
1045
+ lastUsedAt: now,
1046
+ state: 'unclaimed',
1047
+ };
1048
+ }
1049
+ function sameReference(left, right) {
1050
+ return left !== undefined
1051
+ && left.storeId === right.storeId
1052
+ && left.identityId === right.identityId
1053
+ && left.did === right.did;
1054
+ }
1055
+ async function addUnclaimed(store, catalog, reference) {
1056
+ return store.mutate(current => current.entries.some(entry => entry.identityId === reference.identityId)
1057
+ ? current
1058
+ : { ...current, entries: [...current.entries, unclaimedEntry(reference)] });
1059
+ }
1060
+ async function nativeCall(operation) {
1061
+ try {
1062
+ return await operation();
1063
+ }
1064
+ catch (error) {
1065
+ throw mapNativeError(error);
1066
+ }
1067
+ }
1068
+ function mapOperationError(error) {
1069
+ return error instanceof AnpIdentityPluginError ? error : mapNativeError(error);
1070
+ }
1071
+ function mapNativeError(error) {
1072
+ if (error instanceof AnpIdentityPluginError)
1073
+ return error;
1074
+ const code = readErrorCode(error);
1075
+ if (code === 'identity_not_found' || code === 'key_not_found')
1076
+ return pluginError('identity_not_found');
1077
+ if (code === 'invalid_request' || code === 'invalid_identity' || code === 'unsupported_profile') {
1078
+ return pluginError('invalid_request');
1079
+ }
1080
+ if (code === 'conflict')
1081
+ return pluginError('catalog_conflict');
1082
+ if (code === 'provider_disposed')
1083
+ return pluginError('provider_disposed');
1084
+ if (code === 'provider_unavailable' || code === 'store_not_found')
1085
+ return pluginError('provider_unavailable');
1086
+ return pluginError('provider_unavailable');
1087
+ }
1088
+ function readErrorCode(error) {
1089
+ if (typeof error !== 'object' || error === null || !('code' in error))
1090
+ return undefined;
1091
+ return typeof error.code === 'string' ? error.code : undefined;
1092
+ }
1093
+ function isPluginCode(error, code) {
1094
+ return error instanceof AnpIdentityPluginError && error.code === code;
1095
+ }
1096
+ //# sourceMappingURL=index.js.map