@1shotapi/ows-signer 0.2.0

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.
@@ -0,0 +1,691 @@
1
+ import {
2
+ API_VERSION,
3
+ METHODS,
4
+ PRF_LABEL_ED25519,
5
+ SIGN_SCHEMES,
6
+ SIGNER_VERSION,
7
+ } from "./constants.js";
8
+ import {
9
+ decryptAes256Batch,
10
+ encryptAes256Batch,
11
+ } from "./crypto/aes256.js";
12
+ import {
13
+ deriveEd25519SeedFromPrf,
14
+ deriveKeysFromCredential,
15
+ hkdfExpand,
16
+ normalizePrfOutputToArrayBuffer,
17
+ } from "./crypto/prf.js";
18
+ import { getPublicKeyAsync as edGetPublicKeyAsync } from "./crypto/vendor/noble-ed25519.js";
19
+ import { getPublicKey as secpGetPublicKey } from "./crypto/vendor/noble-secp256k1.js";
20
+ import { debugLog, describePrfExtensionResults } from "./debug.js";
21
+ import { decryptPrivateKey, encryptPrivateKey } from "./crypto/recovery.js";
22
+ import { signWithScheme, validateSignPayload } from "./crypto/sign.js";
23
+ import { parse0xHex, to0xHex } from "./hex.js";
24
+ import { emitEvent } from "./rpc.js";
25
+ import {
26
+ clearRecoveryPrivateKey,
27
+ getRecoveryPrivateKey,
28
+ hasRecoverySession,
29
+ setRecoveryPrivateKey,
30
+ withCeremony,
31
+ zeroize,
32
+ } from "./state.js";
33
+ import { clearUi, promptPassphrase, showPrivateKey } from "./ui.js";
34
+ import {
35
+ createPasskeyCredential,
36
+ getAssertionSignatureBase64Url,
37
+ getCredentialId,
38
+ getPasskeyAssertion,
39
+ getPasskeyPublicKeyBase64Url,
40
+ } from "./webauthn.js";
41
+
42
+ /**
43
+ * @param {PublicKeyCredential} credential
44
+ * @returns {Promise<Uint8Array>}
45
+ */
46
+ async function ed25519SeedFromCredential(credential) {
47
+ const prfRaw = /** @type {{ prf?: { results?: { first?: unknown } } }} */ (
48
+ credential.getClientExtensionResults()
49
+ ).prf?.results?.first;
50
+ const prfBuf = normalizePrfOutputToArrayBuffer(prfRaw);
51
+ if (!prfBuf) throw new Error("noPrf");
52
+ return deriveEd25519SeedFromPrf(prfBuf);
53
+ }
54
+
55
+ /**
56
+ * @param {Uint8Array} secp256k1Scalar
57
+ * @returns {Promise<Uint8Array>}
58
+ */
59
+ async function ed25519SeedFromSecp256k1Scalar(secp256k1Scalar) {
60
+ return hkdfExpand(
61
+ secp256k1Scalar.buffer.slice(
62
+ secp256k1Scalar.byteOffset,
63
+ secp256k1Scalar.byteOffset + secp256k1Scalar.byteLength,
64
+ ),
65
+ PRF_LABEL_ED25519,
66
+ 32,
67
+ );
68
+ }
69
+
70
+ /**
71
+ * @param {string} targetOrigin
72
+ * @param {string | undefined} correlationId
73
+ * @param {Uint8Array} secp256k1PublicKey
74
+ * @param {Uint8Array} ed25519PublicKey
75
+ */
76
+ function emitKeyDerived(
77
+ targetOrigin,
78
+ correlationId,
79
+ secp256k1PublicKey,
80
+ ed25519PublicKey,
81
+ ) {
82
+ emitEvent(window.parent, targetOrigin, "KeyDerived", correlationId, {
83
+ secp256k1PublicKey: to0xHex(secp256k1PublicKey),
84
+ ed25519PublicKey: to0xHex(ed25519PublicKey),
85
+ });
86
+ }
87
+
88
+ /**
89
+ * @param {string} method
90
+ * @param {Record<string, unknown>} params
91
+ * @param {string | undefined} correlationId
92
+ * @param {string} targetOrigin
93
+ */
94
+ export async function handleRequest(
95
+ method,
96
+ params,
97
+ correlationId,
98
+ targetOrigin,
99
+ ) {
100
+ if (!METHODS.includes(method)) {
101
+ emitEvent(window.parent, targetOrigin, "InvalidRequest", correlationId, {
102
+ reason: "unknownMethod",
103
+ });
104
+ return;
105
+ }
106
+
107
+ try {
108
+ switch (method) {
109
+ case "getVersion":
110
+ emitEvent(window.parent, targetOrigin, "Version", correlationId, {
111
+ apiVersion: API_VERSION,
112
+ signerVersion: SIGNER_VERSION,
113
+ recoverySessionActive: hasRecoverySession(),
114
+ });
115
+ return;
116
+
117
+ case "createCredential":
118
+ await handleCreateCredential(params, correlationId, targetOrigin);
119
+ return;
120
+
121
+ case "signDigest":
122
+ await handleSignDigest(params, correlationId, targetOrigin);
123
+ return;
124
+
125
+ case "revealPrivateKey":
126
+ await handleRevealPrivateKey(params, correlationId, targetOrigin);
127
+ return;
128
+
129
+ case "createRecoveryData":
130
+ await handleCreateRecoveryData(params, correlationId, targetOrigin);
131
+ return;
132
+
133
+ case "recoverKey":
134
+ await handleRecoverKey(params, correlationId, targetOrigin);
135
+ return;
136
+
137
+ case "getPublicKey":
138
+ await handleGetPublicKey(params, correlationId, targetOrigin);
139
+ return;
140
+
141
+ case "clearRecoverySession":
142
+ clearRecoveryPrivateKey();
143
+ clearUi();
144
+ emitEvent(
145
+ window.parent,
146
+ targetOrigin,
147
+ "RecoverySessionCleared",
148
+ correlationId,
149
+ {},
150
+ );
151
+ return;
152
+
153
+ case "encryptAES256":
154
+ await handleEncryptAES256(params, correlationId, targetOrigin);
155
+ return;
156
+
157
+ case "decryptAES256":
158
+ await handleDecryptAES256(params, correlationId, targetOrigin);
159
+ return;
160
+ }
161
+ } catch (error) {
162
+ handleError(error, correlationId, targetOrigin);
163
+ }
164
+ }
165
+
166
+ /**
167
+ * PRF bytes are returned on assertion (`prf.eval`), not registration (`prf.enable`).
168
+ * Some platforms may return results on create; otherwise run a follow-up get.
169
+ *
170
+ * @param {PublicKeyCredential} credential
171
+ * @param {string} [credentialId]
172
+ * @returns {Promise<PublicKeyCredential>}
173
+ */
174
+ async function credentialForKeyDerivation(credential, credentialId) {
175
+ debugLog("ceremony extension results", describePrfExtensionResults(credential));
176
+
177
+ const rawPrf = credential.getClientExtensionResults()?.prf?.results?.first;
178
+ if (normalizePrfOutputToArrayBuffer(rawPrf)) {
179
+ debugLog("using PRF output from current ceremony");
180
+ return credential;
181
+ }
182
+
183
+ const id = credentialId ?? getCredentialId(credential);
184
+ debugLog("no PRF results on registration; running assertion with prf.eval", {
185
+ credentialId: id,
186
+ });
187
+ const assertion = await getPasskeyAssertion(undefined, id);
188
+ debugLog(
189
+ "assertion extension results",
190
+ describePrfExtensionResults(assertion),
191
+ );
192
+ return assertion;
193
+ }
194
+
195
+ /**
196
+ * @param {Record<string, unknown>} params
197
+ * @param {string | undefined} correlationId
198
+ * @param {string} targetOrigin
199
+ */
200
+ async function handleCreateCredential(params, correlationId, targetOrigin) {
201
+ const name = params.name;
202
+ if (typeof name !== "string" || !name) {
203
+ emitInvalid(correlationId, targetOrigin, "invalidName");
204
+ return;
205
+ }
206
+ const options =
207
+ params.options && typeof params.options === "object"
208
+ ? /** @type {{ rpName?: string, userDisplayName?: string, userId?: string }} */ (
209
+ params.options
210
+ )
211
+ : {};
212
+
213
+ await withCeremony(async () => {
214
+ const credential = await createPasskeyCredential(name, options);
215
+ const credentialId = getCredentialId(credential);
216
+ const prfCredential = await credentialForKeyDerivation(
217
+ credential,
218
+ credentialId,
219
+ );
220
+ const keys = await deriveKeysFromCredential(prfCredential);
221
+ emitKeyDerived(
222
+ targetOrigin,
223
+ correlationId,
224
+ keys.secp256k1PublicKey,
225
+ keys.ed25519PublicKey,
226
+ );
227
+
228
+ emitEvent(window.parent, targetOrigin, "CredentialCreated", correlationId, {
229
+ credentialId,
230
+ passkeyPublicKey: getPasskeyPublicKeyBase64Url(credential),
231
+ secp256k1PublicKey: to0xHex(keys.secp256k1PublicKey),
232
+ });
233
+ zeroize(keys.secp256k1PrivateKey);
234
+ });
235
+ }
236
+
237
+ /**
238
+ * @param {Record<string, unknown>} params
239
+ * @param {string | undefined} correlationId
240
+ * @param {string} targetOrigin
241
+ */
242
+ async function handleSignDigest(params, correlationId, targetOrigin) {
243
+ const digestData = params.digestData;
244
+ const scheme = params.scheme;
245
+ const credentialId =
246
+ typeof params.credentialId === "string" ? params.credentialId : undefined;
247
+
248
+ if (typeof digestData !== "string" || typeof scheme !== "string") {
249
+ emitInvalid(correlationId, targetOrigin, "invalidParams");
250
+ return;
251
+ }
252
+ if (
253
+ !SIGN_SCHEMES.includes(
254
+ /** @type {import('./constants.js').SignScheme} */ (scheme),
255
+ )
256
+ ) {
257
+ emitInvalid(correlationId, targetOrigin, "unknownScheme");
258
+ return;
259
+ }
260
+
261
+ const digest = parse0xHex(digestData);
262
+ validateSignPayload(
263
+ /** @type {import('./constants.js').SignScheme} */ (scheme),
264
+ digest,
265
+ );
266
+
267
+ if (hasRecoverySession()) {
268
+ const cached = getRecoveryPrivateKey();
269
+ if (!cached) throw new Error("recoverySessionEmpty");
270
+ const ed25519Seed = await ed25519SeedFromSecp256k1Scalar(cached);
271
+ const signature = await signWithScheme(
272
+ /** @type {import('./constants.js').SignScheme} */ (scheme),
273
+ digest,
274
+ cached,
275
+ ed25519Seed,
276
+ );
277
+ zeroize(ed25519Seed);
278
+ emitEvent(window.parent, targetOrigin, "DigestSigned", correlationId, {
279
+ digest: digestData,
280
+ scheme,
281
+ signature,
282
+ credentialId: credentialId ?? null,
283
+ });
284
+ return;
285
+ }
286
+
287
+ await withCeremony(async () => {
288
+ const credential = await getPasskeyAssertion(undefined, credentialId);
289
+ const keys = await deriveKeysFromCredential(credential);
290
+ const ed25519Seed = await ed25519SeedFromCredential(credential);
291
+ emitKeyDerived(
292
+ targetOrigin,
293
+ correlationId,
294
+ keys.secp256k1PublicKey,
295
+ keys.ed25519PublicKey,
296
+ );
297
+
298
+ const signature = await signWithScheme(
299
+ /** @type {import('./constants.js').SignScheme} */ (scheme),
300
+ digest,
301
+ keys.secp256k1PrivateKey,
302
+ ed25519Seed,
303
+ );
304
+ zeroize(keys.secp256k1PrivateKey);
305
+ zeroize(ed25519Seed);
306
+
307
+ emitEvent(window.parent, targetOrigin, "DigestSigned", correlationId, {
308
+ digest: digestData,
309
+ scheme,
310
+ signature,
311
+ credentialId: credentialId ?? getCredentialId(credential),
312
+ });
313
+ });
314
+ }
315
+
316
+ /**
317
+ * @param {Record<string, unknown>} params
318
+ * @param {string | undefined} correlationId
319
+ * @param {string} targetOrigin
320
+ */
321
+ async function handleRevealPrivateKey(params, correlationId, targetOrigin) {
322
+ const credentialId =
323
+ typeof params.credentialId === "string" ? params.credentialId : undefined;
324
+
325
+ await withCeremony(async () => {
326
+ const credential = await getPasskeyAssertion(undefined, credentialId);
327
+ const keys = await deriveKeysFromCredential(credential);
328
+ emitKeyDerived(
329
+ targetOrigin,
330
+ correlationId,
331
+ keys.secp256k1PublicKey,
332
+ keys.ed25519PublicKey,
333
+ );
334
+ showPrivateKey(keys.secp256k1PrivateKey);
335
+ zeroize(keys.secp256k1PrivateKey);
336
+ });
337
+ }
338
+
339
+ /**
340
+ * @param {Record<string, unknown>} params
341
+ * @param {string | undefined} correlationId
342
+ * @param {string} targetOrigin
343
+ */
344
+ async function handleCreateRecoveryData(params, correlationId, targetOrigin) {
345
+ const passwordText = params.passwordText;
346
+ const buttonText = params.buttonText;
347
+ const minPasswordLength = params.minPasswordLength;
348
+ const credentialId =
349
+ typeof params.credentialId === "string" ? params.credentialId : undefined;
350
+
351
+ if (
352
+ typeof passwordText !== "string" ||
353
+ typeof buttonText !== "string" ||
354
+ typeof minPasswordLength !== "number"
355
+ ) {
356
+ emitInvalid(correlationId, targetOrigin, "invalidParams");
357
+ return;
358
+ }
359
+
360
+ const passphrase = await promptPassphrase(
361
+ passwordText,
362
+ buttonText,
363
+ minPasswordLength,
364
+ );
365
+
366
+ // Recovery session already holds the secp256k1 scalar — no WebAuthn needed.
367
+ if (hasRecoverySession()) {
368
+ const cached = getRecoveryPrivateKey();
369
+ if (!cached) throw new Error("recoverySessionEmpty");
370
+ const secp256k1PublicKey = secpGetPublicKey(cached, false);
371
+ const ed25519Seed = await ed25519SeedFromSecp256k1Scalar(cached);
372
+ const ed25519PublicKey = await edGetPublicKeyAsync(ed25519Seed);
373
+ emitKeyDerived(
374
+ targetOrigin,
375
+ correlationId,
376
+ secp256k1PublicKey,
377
+ ed25519PublicKey,
378
+ );
379
+ const encryptedPrivateKey = await encryptPrivateKey(cached, passphrase);
380
+ zeroize(ed25519Seed);
381
+ clearUi();
382
+ emitEvent(
383
+ window.parent,
384
+ targetOrigin,
385
+ "RecoveryDataCreated",
386
+ correlationId,
387
+ { encryptedPrivateKey },
388
+ );
389
+ return;
390
+ }
391
+
392
+ await withCeremony(async () => {
393
+ const credential = await getPasskeyAssertion(undefined, credentialId);
394
+ const keys = await deriveKeysFromCredential(credential);
395
+ emitKeyDerived(
396
+ targetOrigin,
397
+ correlationId,
398
+ keys.secp256k1PublicKey,
399
+ keys.ed25519PublicKey,
400
+ );
401
+ const encryptedPrivateKey = await encryptPrivateKey(
402
+ keys.secp256k1PrivateKey,
403
+ passphrase,
404
+ );
405
+ zeroize(keys.secp256k1PrivateKey);
406
+ emitEvent(
407
+ window.parent,
408
+ targetOrigin,
409
+ "RecoveryDataCreated",
410
+ correlationId,
411
+ { encryptedPrivateKey },
412
+ );
413
+ });
414
+ }
415
+
416
+ /**
417
+ * @param {Record<string, unknown>} params
418
+ * @param {string | undefined} correlationId
419
+ * @param {string} targetOrigin
420
+ */
421
+ async function handleRecoverKey(params, correlationId, targetOrigin) {
422
+ const envelope = params.aes256EncryptedPrivateKey;
423
+ const passwordText = params.passwordText;
424
+ const buttonText = params.buttonText;
425
+ const credentialId =
426
+ typeof params.credentialId === "string" ? params.credentialId : undefined;
427
+
428
+ if (
429
+ typeof envelope !== "string" ||
430
+ typeof passwordText !== "string" ||
431
+ typeof buttonText !== "string"
432
+ ) {
433
+ emitInvalid(correlationId, targetOrigin, "invalidParams");
434
+ return;
435
+ }
436
+
437
+ const passphrase = await promptPassphrase(passwordText, buttonText, 1);
438
+ const privateKey = await decryptPrivateKey(envelope, passphrase);
439
+ setRecoveryPrivateKey(privateKey);
440
+
441
+ // Emit public keys so OWSSigner can cache addresses (uncompressed secp256k1 —
442
+ // viem publicKeyToAddress requires 0x04 ‖ X ‖ Y).
443
+ const secp256k1PublicKey = secpGetPublicKey(privateKey, false);
444
+ const ed25519Seed = await ed25519SeedFromSecp256k1Scalar(privateKey);
445
+ const ed25519PublicKey = await edGetPublicKeyAsync(ed25519Seed);
446
+ emitKeyDerived(
447
+ targetOrigin,
448
+ correlationId,
449
+ secp256k1PublicKey,
450
+ ed25519PublicKey,
451
+ );
452
+ zeroize(ed25519Seed);
453
+
454
+ showPrivateKey(privateKey);
455
+
456
+ if (credentialId) {
457
+ await withCeremony(async () => {
458
+ await getPasskeyAssertion(undefined, credentialId);
459
+ clearRecoveryPrivateKey();
460
+ });
461
+ emitEvent(
462
+ window.parent,
463
+ targetOrigin,
464
+ "RecoverySessionCleared",
465
+ correlationId,
466
+ { rebound: true },
467
+ );
468
+ return;
469
+ }
470
+
471
+ emitEvent(
472
+ window.parent,
473
+ targetOrigin,
474
+ "RecoverySessionStarted",
475
+ correlationId,
476
+ { recoverySessionActive: true },
477
+ );
478
+ }
479
+
480
+ /**
481
+ * @param {unknown} value
482
+ * @returns {value is string[]}
483
+ */
484
+ function isStringArray(value) {
485
+ return (
486
+ Array.isArray(value) && value.every((item) => typeof item === "string")
487
+ );
488
+ }
489
+
490
+ /**
491
+ * Batch AES-256-GCM seal using the secp256k1 scalar (same as `signDigest`).
492
+ *
493
+ * @param {Record<string, unknown>} params
494
+ * @param {string | undefined} correlationId
495
+ * @param {string} targetOrigin
496
+ */
497
+ async function handleEncryptAES256(params, correlationId, targetOrigin) {
498
+ const plaintexts = params.plaintexts;
499
+ const credentialId =
500
+ typeof params.credentialId === "string" ? params.credentialId : undefined;
501
+
502
+ if (!isStringArray(plaintexts)) {
503
+ emitInvalid(correlationId, targetOrigin, "invalidParams");
504
+ return;
505
+ }
506
+
507
+ if (plaintexts.length === 0) {
508
+ emitEvent(window.parent, targetOrigin, "AES256Encrypted", correlationId, {
509
+ ciphertexts: [],
510
+ });
511
+ return;
512
+ }
513
+
514
+ if (hasRecoverySession()) {
515
+ const cached = getRecoveryPrivateKey();
516
+ if (!cached) throw new Error("recoverySessionEmpty");
517
+ const ciphertexts = await encryptAes256Batch(plaintexts, cached);
518
+ emitEvent(window.parent, targetOrigin, "AES256Encrypted", correlationId, {
519
+ ciphertexts,
520
+ });
521
+ return;
522
+ }
523
+
524
+ await withCeremony(async () => {
525
+ const credential = await getPasskeyAssertion(undefined, credentialId);
526
+ const keys = await deriveKeysFromCredential(credential);
527
+ emitKeyDerived(
528
+ targetOrigin,
529
+ correlationId,
530
+ keys.secp256k1PublicKey,
531
+ keys.ed25519PublicKey,
532
+ );
533
+ try {
534
+ const ciphertexts = await encryptAes256Batch(
535
+ plaintexts,
536
+ keys.secp256k1PrivateKey,
537
+ );
538
+ emitEvent(window.parent, targetOrigin, "AES256Encrypted", correlationId, {
539
+ ciphertexts,
540
+ });
541
+ } finally {
542
+ zeroize(keys.secp256k1PrivateKey);
543
+ }
544
+ });
545
+ }
546
+
547
+ /**
548
+ * Batch AES-256-GCM unseal using the secp256k1 scalar (same as `signDigest`).
549
+ *
550
+ * @param {Record<string, unknown>} params
551
+ * @param {string | undefined} correlationId
552
+ * @param {string} targetOrigin
553
+ */
554
+ async function handleDecryptAES256(params, correlationId, targetOrigin) {
555
+ const ciphertexts = params.ciphertexts;
556
+ const credentialId =
557
+ typeof params.credentialId === "string" ? params.credentialId : undefined;
558
+
559
+ if (!isStringArray(ciphertexts)) {
560
+ emitInvalid(correlationId, targetOrigin, "invalidParams");
561
+ return;
562
+ }
563
+
564
+ if (ciphertexts.length === 0) {
565
+ emitEvent(window.parent, targetOrigin, "AES256Decrypted", correlationId, {
566
+ plaintexts: [],
567
+ });
568
+ return;
569
+ }
570
+
571
+ if (hasRecoverySession()) {
572
+ const cached = getRecoveryPrivateKey();
573
+ if (!cached) throw new Error("recoverySessionEmpty");
574
+ const plaintexts = await decryptAes256Batch(ciphertexts, cached);
575
+ emitEvent(window.parent, targetOrigin, "AES256Decrypted", correlationId, {
576
+ plaintexts,
577
+ });
578
+ return;
579
+ }
580
+
581
+ await withCeremony(async () => {
582
+ const credential = await getPasskeyAssertion(undefined, credentialId);
583
+ const keys = await deriveKeysFromCredential(credential);
584
+ emitKeyDerived(
585
+ targetOrigin,
586
+ correlationId,
587
+ keys.secp256k1PublicKey,
588
+ keys.ed25519PublicKey,
589
+ );
590
+ try {
591
+ const plaintexts = await decryptAes256Batch(
592
+ ciphertexts,
593
+ keys.secp256k1PrivateKey,
594
+ );
595
+ emitEvent(window.parent, targetOrigin, "AES256Decrypted", correlationId, {
596
+ plaintexts,
597
+ });
598
+ } finally {
599
+ zeroize(keys.secp256k1PrivateKey);
600
+ }
601
+ });
602
+ }
603
+
604
+ /**
605
+ * @param {Record<string, unknown>} params
606
+ * @param {string | undefined} correlationId
607
+ * @param {string} targetOrigin
608
+ */
609
+ async function handleGetPublicKey(params, correlationId, targetOrigin) {
610
+ const credentialId =
611
+ typeof params.credentialId === "string" ? params.credentialId : undefined;
612
+ const challenge =
613
+ typeof params.challenge === "string"
614
+ ? parse0xHex(params.challenge)
615
+ : undefined;
616
+
617
+ await withCeremony(async () => {
618
+ const credential = await getPasskeyAssertion(challenge, credentialId);
619
+ const keys = await deriveKeysFromCredential(credential);
620
+ emitKeyDerived(
621
+ targetOrigin,
622
+ correlationId,
623
+ keys.secp256k1PublicKey,
624
+ keys.ed25519PublicKey,
625
+ );
626
+
627
+ emitEvent(window.parent, targetOrigin, "PublicKey", correlationId, {
628
+ credentialId: getCredentialId(credential),
629
+ passkeyPublicKey: getPasskeyPublicKeyBase64Url(credential),
630
+ secp256k1PublicKey: to0xHex(keys.secp256k1PublicKey),
631
+ ed25519PublicKey: to0xHex(keys.ed25519PublicKey),
632
+ });
633
+
634
+ if (challenge) {
635
+ const signature = getAssertionSignatureBase64Url(credential);
636
+ if (signature) {
637
+ emitEvent(
638
+ window.parent,
639
+ targetOrigin,
640
+ "ChallengeSigned",
641
+ correlationId,
642
+ {
643
+ challenge: to0xHex(challenge),
644
+ signature,
645
+ },
646
+ );
647
+ }
648
+ }
649
+
650
+ zeroize(keys.secp256k1PrivateKey);
651
+ });
652
+ }
653
+
654
+ /**
655
+ * @param {unknown} error
656
+ * @param {string | undefined} correlationId
657
+ * @param {string} targetOrigin
658
+ */
659
+ function handleError(error, correlationId, targetOrigin) {
660
+ const name = error instanceof Error ? error.name : "";
661
+ const message = error instanceof Error ? error.message : String(error);
662
+
663
+ if (name === "NotAllowedError" || message.includes("NotAllowed")) {
664
+ emitEvent(window.parent, targetOrigin, "NotAllowed", correlationId, {
665
+ reason: message || "notAllowed",
666
+ });
667
+ return;
668
+ }
669
+
670
+ if (message === "ceremonyInProgress") {
671
+ emitEvent(window.parent, targetOrigin, "InvalidRequest", correlationId, {
672
+ reason: "ceremonyInProgress",
673
+ });
674
+ return;
675
+ }
676
+
677
+ emitEvent(window.parent, targetOrigin, "InvalidRequest", correlationId, {
678
+ reason: message || "error",
679
+ });
680
+ }
681
+
682
+ /**
683
+ * @param {string | undefined} correlationId
684
+ * @param {string} targetOrigin
685
+ * @param {string} reason
686
+ */
687
+ function emitInvalid(correlationId, targetOrigin, reason) {
688
+ emitEvent(window.parent, targetOrigin, "InvalidRequest", correlationId, {
689
+ reason,
690
+ });
691
+ }