@embarkai/ui-kit 0.1.1 → 0.1.3
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/README.md +98 -100
- package/dist/iframe/index.html +1 -1
- package/dist/iframe/kyc/sumsub.html +1 -1
- package/dist/iframe/main.js +479 -483
- package/dist/iframe/main.js.map +1 -1
- package/dist/iframe/oauth/telegram.html +1 -1
- package/dist/iframe/oauth/x.html +1 -1
- package/dist/index.cjs +1388 -1535
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +78 -102
- package/dist/index.d.ts +78 -102
- package/dist/index.js +1223 -1368
- package/dist/index.js.map +1 -1
- package/dist/styles.css +1 -1
- package/package.json +4 -5
package/dist/iframe/main.js
CHANGED
|
@@ -373,6 +373,77 @@ var init_crypto_utils = __esm({
|
|
|
373
373
|
}
|
|
374
374
|
});
|
|
375
375
|
|
|
376
|
+
// src/iframe/lib/backup/local-backup.ts
|
|
377
|
+
var local_backup_exports = {};
|
|
378
|
+
__export(local_backup_exports, {
|
|
379
|
+
backupToLocalFile: () => backupToLocalFile,
|
|
380
|
+
restoreFromLocalFile: () => restoreFromLocalFile
|
|
381
|
+
});
|
|
382
|
+
async function backupToLocalFile(data, password) {
|
|
383
|
+
let encryptionPassword = password;
|
|
384
|
+
let credentialId;
|
|
385
|
+
if (!encryptionPassword) {
|
|
386
|
+
const result = await deriveBackupPasswordFromPasskey(data.userId);
|
|
387
|
+
encryptionPassword = result.password;
|
|
388
|
+
credentialId = result.credentialId;
|
|
389
|
+
}
|
|
390
|
+
const encrypted = await encryptKeyshare(
|
|
391
|
+
data,
|
|
392
|
+
encryptionPassword,
|
|
393
|
+
password ? "password" : "passkey",
|
|
394
|
+
credentialId
|
|
395
|
+
);
|
|
396
|
+
const backupData = {
|
|
397
|
+
...encrypted,
|
|
398
|
+
userId: data.userId,
|
|
399
|
+
createdAt: data.createdAt
|
|
400
|
+
};
|
|
401
|
+
const blob = new Blob([JSON.stringify(backupData, null, 2)], {
|
|
402
|
+
type: "application/json"
|
|
403
|
+
});
|
|
404
|
+
const url = URL.createObjectURL(blob);
|
|
405
|
+
const a = document.createElement("a");
|
|
406
|
+
a.href = url;
|
|
407
|
+
a.download = `lumia-passport-backup-${data.userId}-${Date.now()}.json`;
|
|
408
|
+
document.body.appendChild(a);
|
|
409
|
+
a.click();
|
|
410
|
+
document.body.removeChild(a);
|
|
411
|
+
URL.revokeObjectURL(url);
|
|
412
|
+
}
|
|
413
|
+
async function restoreFromLocalFile(fileContent, userId, password) {
|
|
414
|
+
console.log("[iframe][restoreFromLocalFile] Starting restore from local file");
|
|
415
|
+
const encryptedBackup = JSON.parse(fileContent);
|
|
416
|
+
let decryptionPassword;
|
|
417
|
+
let credentialId;
|
|
418
|
+
if (password) {
|
|
419
|
+
decryptionPassword = password;
|
|
420
|
+
} else {
|
|
421
|
+
const credentialIdFromBackup = encryptedBackup.encryptionMethod === "passkey" ? encryptedBackup.credentialId : void 0;
|
|
422
|
+
const result = await deriveBackupPasswordFromPasskey(
|
|
423
|
+
userId,
|
|
424
|
+
credentialIdFromBackup
|
|
425
|
+
).catch(() => {
|
|
426
|
+
throw new Error(
|
|
427
|
+
"Restore requires either password or passkey authentication"
|
|
428
|
+
);
|
|
429
|
+
});
|
|
430
|
+
decryptionPassword = result.password;
|
|
431
|
+
credentialId = result.credentialId;
|
|
432
|
+
}
|
|
433
|
+
const { decryptKeyshare: decryptKeyshare2 } = await Promise.resolve().then(() => (init_crypto_utils(), crypto_utils_exports));
|
|
434
|
+
const backupData = await decryptKeyshare2(encryptedBackup, decryptionPassword);
|
|
435
|
+
if (backupData.userId !== userId) {
|
|
436
|
+
throw new Error("Backup file does not match current user");
|
|
437
|
+
}
|
|
438
|
+
console.log("[iframe][restoreFromLocalFile] Restore successful");
|
|
439
|
+
return backupData;
|
|
440
|
+
}
|
|
441
|
+
var init_local_backup = __esm({
|
|
442
|
+
"src/iframe/lib/backup/local-backup.ts"() {
|
|
443
|
+
init_crypto_utils();
|
|
444
|
+
}
|
|
445
|
+
});
|
|
446
|
+
|
|
376
447
|
// src/iframe/lib/backup/server-backup.ts
|
|
377
448
|
var server_backup_exports = {};
|
|
378
449
|
__export(server_backup_exports, {
|
|
@@ -477,77 +548,6 @@ var init_server_backup = __esm({
|
|
|
477
548
|
}
|
|
478
549
|
});
|
|
479
550
|
|
|
480
|
-
// src/iframe/lib/backup/local-backup.ts
|
|
481
|
-
var local_backup_exports = {};
|
|
482
|
-
__export(local_backup_exports, {
|
|
483
|
-
backupToLocalFile: () => backupToLocalFile,
|
|
484
|
-
restoreFromLocalFile: () => restoreFromLocalFile
|
|
485
|
-
});
|
|
486
|
-
async function backupToLocalFile(data, password) {
|
|
487
|
-
let encryptionPassword = password;
|
|
488
|
-
let credentialId;
|
|
489
|
-
if (!encryptionPassword) {
|
|
490
|
-
const result = await deriveBackupPasswordFromPasskey(data.userId);
|
|
491
|
-
encryptionPassword = result.password;
|
|
492
|
-
credentialId = result.credentialId;
|
|
493
|
-
}
|
|
494
|
-
const encrypted = await encryptKeyshare(
|
|
495
|
-
data,
|
|
496
|
-
encryptionPassword,
|
|
497
|
-
password ? "password" : "passkey",
|
|
498
|
-
credentialId
|
|
499
|
-
);
|
|
500
|
-
const backupData = {
|
|
501
|
-
...encrypted,
|
|
502
|
-
userId: data.userId,
|
|
503
|
-
createdAt: data.createdAt
|
|
504
|
-
};
|
|
505
|
-
const blob = new Blob([JSON.stringify(backupData, null, 2)], {
|
|
506
|
-
type: "application/json"
|
|
507
|
-
});
|
|
508
|
-
const url = URL.createObjectURL(blob);
|
|
509
|
-
const a = document.createElement("a");
|
|
510
|
-
a.href = url;
|
|
511
|
-
a.download = `lumia-passport-backup-${data.userId}-${Date.now()}.json`;
|
|
512
|
-
document.body.appendChild(a);
|
|
513
|
-
a.click();
|
|
514
|
-
document.body.removeChild(a);
|
|
515
|
-
URL.revokeObjectURL(url);
|
|
516
|
-
}
|
|
517
|
-
async function restoreFromLocalFile(fileContent, userId, password) {
|
|
518
|
-
console.log("[iframe][restoreFromLocalFile] Starting restore from local file");
|
|
519
|
-
const encryptedBackup = JSON.parse(fileContent);
|
|
520
|
-
let decryptionPassword;
|
|
521
|
-
let credentialId;
|
|
522
|
-
if (password) {
|
|
523
|
-
decryptionPassword = password;
|
|
524
|
-
} else {
|
|
525
|
-
const credentialIdFromBackup = encryptedBackup.encryptionMethod === "passkey" ? encryptedBackup.credentialId : void 0;
|
|
526
|
-
const result = await deriveBackupPasswordFromPasskey(
|
|
527
|
-
userId,
|
|
528
|
-
credentialIdFromBackup
|
|
529
|
-
).catch(() => {
|
|
530
|
-
throw new Error(
|
|
531
|
-
"Restore requires either password or passkey authentication"
|
|
532
|
-
);
|
|
533
|
-
});
|
|
534
|
-
decryptionPassword = result.password;
|
|
535
|
-
credentialId = result.credentialId;
|
|
536
|
-
}
|
|
537
|
-
const { decryptKeyshare: decryptKeyshare2 } = await Promise.resolve().then(() => (init_crypto_utils(), crypto_utils_exports));
|
|
538
|
-
const backupData = await decryptKeyshare2(encryptedBackup, decryptionPassword);
|
|
539
|
-
if (backupData.userId !== userId) {
|
|
540
|
-
throw new Error("Backup file does not match current user");
|
|
541
|
-
}
|
|
542
|
-
console.log("[iframe][restoreFromLocalFile] Restore successful");
|
|
543
|
-
return backupData;
|
|
544
|
-
}
|
|
545
|
-
var init_local_backup = __esm({
|
|
546
|
-
"src/iframe/lib/backup/local-backup.ts"() {
|
|
547
|
-
init_crypto_utils();
|
|
548
|
-
}
|
|
549
|
-
});
|
|
550
|
-
|
|
551
551
|
// ../../node_modules/.pnpm/viem@2.38.0_bufferutil@4.0.9_typescript@5.9.3_utf-8-validate@5.0.10_zod@3.25.76/node_modules/viem/_esm/utils/data/isHex.js
|
|
552
552
|
function isHex(value, { strict = true } = {}) {
|
|
553
553
|
if (!value)
|
|
@@ -1267,13 +1267,13 @@ var TemplateEngine = class {
|
|
|
1267
1267
|
};
|
|
1268
1268
|
|
|
1269
1269
|
// src/iframe/templates/html/authorization.html
|
|
1270
|
-
var authorization_default = '<div\n style="\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n z-index: 10000;\n background: #000000;\n "\n>\n <div\n style="\n display: flex;\n flex-direction: column;\n gap: var(--iframe-gap);\n align-items: center;\n background: var(--iframe-modal-bg);\n border-radius: 20px;\n padding: 20px;\n max-width: 320px;\n width: calc(100% - 40px);\n max-height: 90vh;\n overflow-y: auto;\n "\n >\n <div style="display: flex; align-items: center; gap: 0; justify-content: center">\n <!-- 10px overlap -->\n <div style="width: 38px; height: 48px; z-index: 10; overflow: visible">\n {{#if userAvatar}}\n <img\n src="{{userAvatar}}"\n alt="{{userName}}"\n style="width: 48px; height: 48px; border-radius: 24px; object-fit: cover"\n />\n {{else}}\n <span\n style="\n display: flex;\n align-items: center;\n justify-content: center;\n width: 48px;\n height: 48px;\n border-radius: 24px;\n background-color: var(--iframe-button-bg);\n color: var(--iframe-button-text);\n font-size: 14px;\n font-weight: 600;\n "\n >U</span\n >\n {{/if}}\n </div>\n\n <!-- ------------------------------------------------------------ -->\n\n <div style="width: 38px; height: 48px; z-index: 5; overflow: visible">\n {{#if displayLogo}}\n <img\n src="{{displayLogo}}"\n alt="{{displayName}}"\n style="width: 48px; height: 48px; border-radius: 24px; object-fit: cover"\n />\n {{else}}\n <span\n style="\n display: flex;\n align-items: center;\n justify-content: center;\n width: 48px;\n height: 48px;\n border-radius: 24px;\n background-color: var(--iframe-button-bg);\n color: var(--iframe-button-text);\n font-size: 14px;\n font-weight: 600;\n "\n >A</span\n >\n {{/if}}\n </div>\n </div>\n\n <div style="display: flex; align-items: center; gap: var(--iframe-gap); justify-content: center">\n <span\n style="\n width: fit-content;\n font-size: var(--iframe-h2-fz);\n font-weight: var(--iframe-heading-fw);\n line-height: 150%;\n text-align: center;\n "\n >{{displayName}}</span\n >\n </div>\n\n <div class="domain-info {{domainStatusClass}}">\n <span style="display: block; width: 100%; font-size: 12px; text-align: center; font-family: monospace"\n >{{domainStatusIcon}} {{origin}}</span\n >\n </div>\n\n <!-- Permissions box -->\n <div\n id="data-auth-permissions-block"\n aria-expanded="false"\n style="display: flex; flex-direction: column; width: 100%"\n >\n <div\n id="data-auth-permissions-toggler"\n style="\n cursor: pointer;\n text-align: center;\n list-style: none;\n padding: 0 var(--iframe-pd);\n user-select: none;\n font-size: 12px;\n line-height: 20px;\n "\n >\n Show Permissions \u25BC\n </div>\n\n <div\n id="data-auth-permissions-content"\n style="width: 100%; height: var(--iframe-permissions-h, 0); overflow: hidden; transition: height 300ms ease"\n >\n <div\n style="\n width: 100%;\n padding: var(--iframe-gap);\n border-radius: var(--iframe-el-bdrs);\n background: var(--iframe-info);\n font-size: 10px;\n "\n >\n <div style="margin-bottom: var(--iframe-gap)">\n <strong>{{displayName}}</strong> by <span class="project-owner">{{displayName}}</span> wants to access your\n <strong>
|
|
1270
|
+
var authorization_default = '<div\n style="\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n z-index: 10000;\n background: #000000;\n "\n>\n <div\n style="\n display: flex;\n flex-direction: column;\n gap: var(--iframe-gap);\n align-items: center;\n background: var(--iframe-modal-bg);\n border-radius: 20px;\n padding: 20px;\n max-width: 320px;\n width: calc(100% - 40px);\n max-height: 90vh;\n overflow-y: auto;\n "\n >\n <div style="display: flex; align-items: center; gap: 0; justify-content: center">\n <!-- 10px overlap -->\n <div style="width: 38px; height: 48px; z-index: 10; overflow: visible">\n {{#if userAvatar}}\n <img\n src="{{userAvatar}}"\n alt="{{userName}}"\n style="width: 48px; height: 48px; border-radius: 24px; object-fit: cover"\n />\n {{else}}\n <span\n style="\n display: flex;\n align-items: center;\n justify-content: center;\n width: 48px;\n height: 48px;\n border-radius: 24px;\n background-color: var(--iframe-button-bg);\n color: var(--iframe-button-text);\n font-size: 14px;\n font-weight: 600;\n "\n >U</span\n >\n {{/if}}\n </div>\n\n <!-- ------------------------------------------------------------ -->\n\n <div style="width: 38px; height: 48px; z-index: 5; overflow: visible">\n {{#if displayLogo}}\n <img\n src="{{displayLogo}}"\n alt="{{displayName}}"\n style="width: 48px; height: 48px; border-radius: 24px; object-fit: cover"\n />\n {{else}}\n <span\n style="\n display: flex;\n align-items: center;\n justify-content: center;\n width: 48px;\n height: 48px;\n border-radius: 24px;\n background-color: var(--iframe-button-bg);\n color: var(--iframe-button-text);\n font-size: 14px;\n font-weight: 600;\n "\n >A</span\n >\n {{/if}}\n </div>\n </div>\n\n <div style="display: flex; align-items: center; gap: var(--iframe-gap); justify-content: center">\n <span\n style="\n width: fit-content;\n font-size: var(--iframe-h2-fz);\n font-weight: var(--iframe-heading-fw);\n line-height: 150%;\n text-align: center;\n "\n >{{displayName}}</span\n >\n </div>\n\n <div class="domain-info {{domainStatusClass}}">\n <span style="display: block; width: 100%; font-size: 12px; text-align: center; font-family: monospace"\n >{{domainStatusIcon}} {{origin}}</span\n >\n </div>\n\n <!-- Permissions box -->\n <div\n id="data-auth-permissions-block"\n aria-expanded="false"\n style="display: flex; flex-direction: column; width: 100%"\n >\n <div\n id="data-auth-permissions-toggler"\n style="\n cursor: pointer;\n text-align: center;\n list-style: none;\n padding: 0 var(--iframe-pd);\n user-select: none;\n font-size: 12px;\n line-height: 20px;\n "\n >\n Show Permissions \u25BC\n </div>\n\n <div\n id="data-auth-permissions-content"\n style="width: 100%; height: var(--iframe-permissions-h, 0); overflow: hidden; transition: height 300ms ease"\n >\n <div\n style="\n width: 100%;\n padding: var(--iframe-gap);\n border-radius: var(--iframe-el-bdrs);\n background: var(--iframe-info);\n font-size: 10px;\n "\n >\n <div style="margin-bottom: var(--iframe-gap)">\n <strong>{{displayName}}</strong> by <span class="project-owner">{{displayName}}</span> wants to access your\n <strong>EmbarkAI</strong> account\n </div>\n\n <div style="margin-bottom: var(--iframe-gap)">\n <strong style="margin-bottom: var(--iframe-gap)">Personal user data</strong>\n <ul style="list-style: none">\n <li style="padding-left: var(--iframe-pd)">Wallet address (read-only)</li>\n <li style="padding-left: var(--iframe-pd)">Public transaction history (read-only)</li>\n </ul>\n </div>\n\n <div style="width: 100%">\n <strong style="margin-bottom: var(--iframe-gap)">Permissions</strong>\n <ul style="list-style: none">\n <li style="padding-left: var(--iframe-pd)">Request transaction signatures</li>\n <li style="padding-left: var(--iframe-pd)">Initiate blockchain operations</li>\n </ul>\n </div>\n </div>\n </div>\n </div>\n\n {{#if showSecurityWarning}}\n <div class="security-warning">\n <strong>\u26A0\uFE0F Warning:</strong> This domain is not verified for {{displayName}}\n <div>Expected: {{expectedDomains}}</div>\n <div>Actual: {{origin}}</div>\n </div>\n {{else}}\n <span style="display: block; text-align: center; font-size: 10px; color: var(--iframe-text-secondary)"\n >By continuing you allow the app to use these permissions, you can revoke access at any time.</span\n >\n {{/if}}\n\n <!-- Action buttons -->\n <div style="display: flex; gap: var(--iframe-gap); width: 100%; align-items: center">\n <button class="cancel-btn">Cancel</button>\n <button class="authorize-btn" {{authorizeButtonState}}>Continue</button>\n </div>\n </div>\n</div>\n';
|
|
1271
1271
|
|
|
1272
1272
|
// src/iframe/templates/html/confirm-tx.html
|
|
1273
|
-
var confirm_tx_default = '<div\n style="\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n z-index: 10000;\n background: #000000;\n "\n>\n <div\n style="\n display: flex;\n flex-direction: column;\n gap: var(--iframe-gap);\n align-items: center;\n background: var(--iframe-modal-bg);\n border-radius: 20px;\n padding: 20px;\n max-width: 320px;\n width: calc(100% - 40px);\n max-height: 90vh;\n overflow-y: auto;\n "\n >\n <!-- Header with logos -->\n <div style="display: flex; align-items: center; gap: 0; justify-content: center">\n <div style="width: 38px; height: 48px; z-index: 10; overflow: visible">\n {{#if displayLogo}}\n <img\n src="{{displayLogo}}"\n alt="{{displayName}}"\n style="width: 48px; height: 48px; border-radius: 24px; object-fit: cover"\n />\n {{else}}\n <span\n style="\n display: flex;\n align-items: center;\n justify-content: center;\n width: 48px;\n height: 48px;\n border-radius: 24px;\n background-color: var(--iframe-button-bg);\n color: var(--iframe-button-text);\n font-size: 14px;\n font-weight: 600;\n "\n >A</span\n >\n {{/if}}\n </div>\n\n <!-- ------------------------------------------------------------ -->\n\n <div style="width: 38px; height: 48px; z-index: 5; overflow: visible">\n {{#if userAvatar}}\n <img\n src="{{userAvatar}}"\n alt="{{userName}}"\n style="width: 48px; height: 48px; border-radius: 24px; object-fit: cover"\n />\n {{else}}\n <span\n style="\n display: flex;\n align-items: center;\n justify-content: center;\n width: 48px;\n height: 48px;\n border-radius: 24px;\n background-color: var(--iframe-button-bg);\n color: var(--iframe-button-text);\n font-size: 14px;\n font-weight: 600;\n "\n >U</span\n >\n {{/if}}\n </div>\n </div>\n\n <!-- Transaction title -->\n\n <div style="display: flex; align-items: center; gap: var(--iframe-gap); justify-content: center">\n <span\n style="\n width: fit-content;\n font-size: var(--iframe-h1-fz);\n font-weight: var(--iframe-heading-fw);\n line-height: 150%;\n text-align: center;\n "\n >Confirm Transaction</span\n >\n </div>\n\n <!-- Application info -->\n <div class="domain-info {{domainStatusClass}}">\n <span style="display: block; width: 100%; font-size: 12px; text-align: center; font-family: monospace"\n >{{domainStatusText}} {{origin}}</span\n >\n </div>\n\n <!-- Transaction details box -->\n <div\n id="confirm-transaction-details-block"\n aria-expanded="false"\n style="display: flex; flex-direction: column; width: 100%"\n >\n <div\n id="confirm-transaction-details-toggler"\n style="\n cursor: pointer;\n text-align: center;\n list-style: none;\n padding: 0 var(--iframe-pd);\n user-select: none;\n font-size: 12px;\n line-height: 20px;\n "\n >\n Show Transaction Details \u25BC\n </div>\n\n <div\n id="confirm-transaction-details-content"\n style="\n display: flex;\n flex-direction: column;\n gap: var(--iframe-gap);\n width: 100%;\n max-height: var(--confirm-transaction-details-h, 0px);\n overflow: hidden;\n transition: max-height 300ms ease;\n "\n >\n <div\n style="\n display: flex;\n flex-direction: column;\n gap: var(--iframe-gap);\n width: 100%;\n padding: var(--iframe-gap);\n border-radius: var(--iframe-el-bdrs);\n background: var(--iframe-info);\n font-size: 10px;\n "\n >\n {{#if fromAddress}}\n <div style="display: flex; justify-content: space-between">\n <strong style="color: var(--iframe-text-secondary); display: block">From:</strong>\n <a\n href="{{fromAddressExplorerUrl}}"\n target="_blank"\n rel="noopener noreferrer"\n style="word-break: break-all; display: block; color: #3b82f6; text-decoration: none"\n title="{{fromAddressFull}}"\n >\n <code>{{fromAddress}}</code>\n </a>\n </div>\n {{/if}}\n\n <!-- ------------------------------------------------------------ -->\n\n {{#if toAddress}}\n <div style="display: flex; justify-content: space-between">\n <strong style="color: var(--iframe-text-secondary); display: block">To:</strong>\n <a\n href="{{toAddressExplorerUrl}}"\n target="_blank"\n rel="noopener noreferrer"\n style="word-break: break-all; display: block; color: #3b82f6; text-decoration: none"\n title="{{toAddressFull}}"\n >\n <code>{{toAddress}}</code>\n </a>\n </div>\n {{/if}}\n\n <!-- ------------------------------------------------------------ -->\n\n {{#if hasValue}}\n <div style="display: flex; justify-content: space-between">\n <strong style="color: var(--iframe-text-secondary); display: block">Amount:</strong>\n <code style="word-break: break-all; display: block">{{formattedValue}} LUMIA</code>\n </div>\n {{/if}}\n\n <!-- ------------------------------------------------------------ -->\n\n {{#if showContractInteraction}}\n <div style="display: flex; justify-content: space-between">\n <strong style="color: var(--iframe-text-secondary); display: block">Action:</strong>\n <code style="word-break: break-all; display: block">{{actionDescription}}</code>\n </div>\n {{/if}}\n\n <!-- ------------------------------------------------------------ -->\n\n <div style="display: flex; justify-content: space-between">\n <strong style="color: var(--iframe-text-secondary); display: block">Estimated Cost:</strong>\n <code style="word-break: break-all; display: block; color: #10b981">{{estimatedCost}}</code>\n </div>\n\n <!-- ------------------------------------------------------------ -->\n\n {{#if isSponsored}}\n <div\n style="\n display: flex;\n justify-content: space-between;\n background: var(--iframe-success);\n border-radius: var(--iframe-el-bdrs);\n padding: var(--iframe-gap);\n "\n >\n <span>Gas fees will be paid by the application (no cost to you)</span>\n </div>\n {{/if}}\n </div>\n </div>\n </div>\n\n <!-- ------------------------------------------------------------ -->\n\n {{#if showHighRisk}}\n <div class="security-warning">\n <strong>\u26A0\uFE0F {{riskLevel}} RISK</strong>\n {{riskReasons}}\n </div>\n {{else}}\n <div class="security-warning">Please verify this transaction carefully before confirming.</div>\n {{/if}}\n\n <!-- ------------------------------------------------------------ -->\n\n <div class="trust-app-section">\n <label class="trust-app-label">\n <input type="checkbox" class="trust-app-checkbox" />\n <span>Add this application to trusted list and skip confirmation for future transactions</span>\n </label>\n </div>\n\n <!-- Action buttons -->\n <div style="display: flex; gap: var(--iframe-gap); width: 100%; align-items: center">\n <button class="cancel-btn">Reject</button>\n <button class="confirm-btn">Confirm</button>\n </div>\n\n <!-- Footer notice -->\n <span\n style="\n display: block;\n padding: var(--iframe-gap);\n text-align: center;\n font-size: 10px;\n color: var(--iframe-text-secondary);\n "\n >\n You can manage trusted applications in your
|
|
1273
|
+
var confirm_tx_default = '<div\n style="\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n z-index: 10000;\n background: #000000;\n "\n>\n <div\n style="\n display: flex;\n flex-direction: column;\n gap: var(--iframe-gap);\n align-items: center;\n background: var(--iframe-modal-bg);\n border-radius: 20px;\n padding: 20px;\n max-width: 320px;\n width: calc(100% - 40px);\n max-height: 90vh;\n overflow-y: auto;\n "\n >\n <!-- Header with logos -->\n <div style="display: flex; align-items: center; gap: 0; justify-content: center">\n <div style="width: 38px; height: 48px; z-index: 10; overflow: visible">\n {{#if displayLogo}}\n <img\n src="{{displayLogo}}"\n alt="{{displayName}}"\n style="width: 48px; height: 48px; border-radius: 24px; object-fit: cover"\n />\n {{else}}\n <span\n style="\n display: flex;\n align-items: center;\n justify-content: center;\n width: 48px;\n height: 48px;\n border-radius: 24px;\n background-color: var(--iframe-button-bg);\n color: var(--iframe-button-text);\n font-size: 14px;\n font-weight: 600;\n "\n >A</span\n >\n {{/if}}\n </div>\n\n <!-- ------------------------------------------------------------ -->\n\n <div style="width: 38px; height: 48px; z-index: 5; overflow: visible">\n {{#if userAvatar}}\n <img\n src="{{userAvatar}}"\n alt="{{userName}}"\n style="width: 48px; height: 48px; border-radius: 24px; object-fit: cover"\n />\n {{else}}\n <span\n style="\n display: flex;\n align-items: center;\n justify-content: center;\n width: 48px;\n height: 48px;\n border-radius: 24px;\n background-color: var(--iframe-button-bg);\n color: var(--iframe-button-text);\n font-size: 14px;\n font-weight: 600;\n "\n >U</span\n >\n {{/if}}\n </div>\n </div>\n\n <!-- Transaction title -->\n\n <div style="display: flex; align-items: center; gap: var(--iframe-gap); justify-content: center">\n <span\n style="\n width: fit-content;\n font-size: var(--iframe-h1-fz);\n font-weight: var(--iframe-heading-fw);\n line-height: 150%;\n text-align: center;\n "\n >Confirm Transaction</span\n >\n </div>\n\n <!-- Application info -->\n <div class="domain-info {{domainStatusClass}}">\n <span style="display: block; width: 100%; font-size: 12px; text-align: center; font-family: monospace"\n >{{domainStatusText}} {{origin}}</span\n >\n </div>\n\n <!-- Transaction details box -->\n <div\n id="confirm-transaction-details-block"\n aria-expanded="false"\n style="display: flex; flex-direction: column; width: 100%"\n >\n <div\n id="confirm-transaction-details-toggler"\n style="\n cursor: pointer;\n text-align: center;\n list-style: none;\n padding: 0 var(--iframe-pd);\n user-select: none;\n font-size: 12px;\n line-height: 20px;\n "\n >\n Show Transaction Details \u25BC\n </div>\n\n <div\n id="confirm-transaction-details-content"\n style="\n display: flex;\n flex-direction: column;\n gap: var(--iframe-gap);\n width: 100%;\n max-height: var(--confirm-transaction-details-h, 0px);\n overflow: hidden;\n transition: max-height 300ms ease;\n "\n >\n <div\n style="\n display: flex;\n flex-direction: column;\n gap: var(--iframe-gap);\n width: 100%;\n padding: var(--iframe-gap);\n border-radius: var(--iframe-el-bdrs);\n background: var(--iframe-info);\n font-size: 10px;\n "\n >\n {{#if fromAddress}}\n <div style="display: flex; justify-content: space-between">\n <strong style="color: var(--iframe-text-secondary); display: block">From:</strong>\n <a\n href="{{fromAddressExplorerUrl}}"\n target="_blank"\n rel="noopener noreferrer"\n style="word-break: break-all; display: block; color: #3b82f6; text-decoration: none"\n title="{{fromAddressFull}}"\n >\n <code>{{fromAddress}}</code>\n </a>\n </div>\n {{/if}}\n\n <!-- ------------------------------------------------------------ -->\n\n {{#if toAddress}}\n <div style="display: flex; justify-content: space-between">\n <strong style="color: var(--iframe-text-secondary); display: block">To:</strong>\n <a\n href="{{toAddressExplorerUrl}}"\n target="_blank"\n rel="noopener noreferrer"\n style="word-break: break-all; display: block; color: #3b82f6; text-decoration: none"\n title="{{toAddressFull}}"\n >\n <code>{{toAddress}}</code>\n </a>\n </div>\n {{/if}}\n\n <!-- ------------------------------------------------------------ -->\n\n {{#if hasValue}}\n <div style="display: flex; justify-content: space-between">\n <strong style="color: var(--iframe-text-secondary); display: block">Amount:</strong>\n <code style="word-break: break-all; display: block">{{formattedValue}} LUMIA</code>\n </div>\n {{/if}}\n\n <!-- ------------------------------------------------------------ -->\n\n {{#if showContractInteraction}}\n <div style="display: flex; justify-content: space-between">\n <strong style="color: var(--iframe-text-secondary); display: block">Action:</strong>\n <code style="word-break: break-all; display: block">{{actionDescription}}</code>\n </div>\n {{/if}}\n\n <!-- ------------------------------------------------------------ -->\n\n <div style="display: flex; justify-content: space-between">\n <strong style="color: var(--iframe-text-secondary); display: block">Estimated Cost:</strong>\n <code style="word-break: break-all; display: block; color: #10b981">{{estimatedCost}}</code>\n </div>\n\n <!-- ------------------------------------------------------------ -->\n\n {{#if isSponsored}}\n <div\n style="\n display: flex;\n justify-content: space-between;\n background: var(--iframe-success);\n border-radius: var(--iframe-el-bdrs);\n padding: var(--iframe-gap);\n "\n >\n <span>Gas fees will be paid by the application (no cost to you)</span>\n </div>\n {{/if}}\n </div>\n </div>\n </div>\n\n <!-- ------------------------------------------------------------ -->\n\n {{#if showHighRisk}}\n <div class="security-warning">\n <strong>\u26A0\uFE0F {{riskLevel}} RISK</strong>\n {{riskReasons}}\n </div>\n {{else}}\n <div class="security-warning">Please verify this transaction carefully before confirming.</div>\n {{/if}}\n\n <!-- ------------------------------------------------------------ -->\n\n <div class="trust-app-section">\n <label class="trust-app-label">\n <input type="checkbox" class="trust-app-checkbox" />\n <span>Add this application to trusted list and skip confirmation for future transactions</span>\n </label>\n </div>\n\n <!-- Action buttons -->\n <div style="display: flex; gap: var(--iframe-gap); width: 100%; align-items: center">\n <button class="cancel-btn">Reject</button>\n <button class="confirm-btn">Confirm</button>\n </div>\n\n <!-- Footer notice -->\n <span\n style="\n display: block;\n padding: var(--iframe-gap);\n text-align: center;\n font-size: 10px;\n color: var(--iframe-text-secondary);\n "\n >\n You can manage trusted applications in your EmbarkAI settings.\n </span>\n </div>\n</div>\n';
|
|
1274
1274
|
|
|
1275
1275
|
// src/iframe/templates/html/ready-indicator.html
|
|
1276
|
-
var ready_indicator_default = '<div class="ready-indicator">\n <div class="status-icon">\u2713</div>\n <h2>Secure Wallet Ready</h2>\n <p>
|
|
1276
|
+
var ready_indicator_default = '<div class="ready-indicator">\n <div class="status-icon">\u2713</div>\n <h2>Secure Wallet Ready</h2>\n <p>EmbarkAI is ready for secure operations</p>\n <div class="info">\n <div class="info-row">\n <strong>Origin:</strong>\n <span>{{origin}}</span>\n </div>\n <div class="info-row">\n <strong>Status:</strong>\n <span class="status-active">Active</span>\n </div>\n <div class="info-row">\n <strong>Version:</strong>\n <span>{{iframeVersion}}</span>\n </div>\n <div class="info-row">\n <strong>Documentation:</strong>\n <a\n href="https://docs.lumiapassport.com/"\n target="_blank"\n rel="noopener noreferrer"\n style="color: #667eea; text-decoration: none"\n >docs.lumiapassport.com</a\n >\n </div>\n </div>\n</div>\n';
|
|
1277
1277
|
|
|
1278
1278
|
// src/iframe/templates/html/sign-eip712-tx.html
|
|
1279
1279
|
var sign_eip712_tx_default = '<div\n style="\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n z-index: 10000;\n background: #000000;\n "\n>\n <div\n style="\n display: flex;\n flex-direction: column;\n gap: var(--iframe-gap);\n align-items: center;\n background: var(--iframe-modal-bg);\n border-radius: 20px;\n padding: 20px;\n max-width: 320px;\n width: calc(100% - 40px);\n max-height: 90vh;\n overflow-y: auto;\n "\n >\n <!-- Header -->\n <div style="display: flex; align-items: center; gap: var(--iframe-gap); justify-content: center">\n {{#if metadataLogo}}\n <img\n src="{{metadataLogo}}"\n alt="{{metadataName}}"\n style="width: 48px; height: 48px; border-radius: 24px; object-fit: cover"\n />\n {{else}}\n <span\n style="\n display: flex;\n align-items: center;\n justify-content: center;\n width: 48px;\n height: 48px;\n border-radius: 24px;\n background-color: var(--iframe-button-bg);\n color: var(--iframe-button-text);\n font-size: 14px;\n font-weight: 600;\n "\n >\u{1F510}</span\n >\n {{/if}}\n\n <!-- ------------------------------------------------------------ -->\n <svg\n xmlns="http://www.w3.org/2000/svg"\n width="24"\n height="24"\n viewBox="0 0 24 24"\n fill="none"\n stroke="var(--iframe-text-secondary)"\n stroke-width="2"\n stroke-linecap="round"\n stroke-linejoin="round"\n >\n <path d="M5 12h14" />\n <path d="m12 5 7 7-7 7" />\n </svg>\n <!-- ------------------------------------------------------------ -->\n\n {{#if userAvatar}}\n <img\n src="{{userAvatar}}"\n alt="{{userName}}"\n style="width: 48px; height: 48px; border-radius: 24px; object-fit: cover"\n />\n {{else}}\n <span\n style="\n display: flex;\n align-items: center;\n justify-content: center;\n width: 48px;\n height: 48px;\n border-radius: 24px;\n background-color: var(--iframe-button-bg);\n color: var(--iframe-button-text);\n font-size: 14px;\n font-weight: 600;\n "\n >U</span\n >\n {{/if}}\n </div>\n\n <div style="display: flex; align-items: center; gap: var(--iframe-gap); justify-content: center">\n <span\n style="\n width: fit-content;\n font-size: var(--iframe-h2-fz);\n font-weight: var(--iframe-heading-fw);\n line-height: 150%;\n text-align: center;\n "\n >Signature Request</span\n >\n </div>\n\n <div class="domain-info verified">\n <span style="display: block; width: 100%; font-size: 12px; text-align: center; font-family: monospace">\n {{#if isVerifiedOrigin}}\n <span>\u2713</span>\n {{/if}} {{origin}}</span\n >\n </div>\n\n <!-- EIP712 Message Content -->\n <div class="eip712-content">\n <details class="eip712-details">\n <summary>Advanced details</summary>\n\n <div class="section-title">\u{1F4DD} Message</div>\n <div class="eip712-section">\n <div class="section-subtitle">{{primaryType}}</div>\n {{messageFields}}\n </div>\n\n <div class="section-title" style="margin-top: 16px">\u{1F50D} Domain</div>\n <div class="eip712-section">{{domainFields}}</div>\n\n <div class="section-title" style="margin-top: 16px">\u{1F4CB} Full Message</div>\n <pre class="eip712-raw"><code>{{fullMessageJson}}</code></pre>\n </details>\n </div>\n\n <!-- Trust App Option -->\n <div class="trust-app-section">\n <label class="trust-app-label">\n <input type="checkbox" class="trust-app-checkbox" />\n <span>Trust this application and skip confirmation for future signatures</span>\n </label>\n </div>\n\n <!-- Action Buttons -->\n <div class="actions">\n <button class="cancel-btn">Reject</button>\n <button class="confirm-btn">Sign</button>\n </div>\n\n <!-- Footer Notice -->\n <div class="footer-notice">\n <p class="footer-note">Only sign messages from applications you trust.</p>\n </div>\n </div>\n</div>\n';
|
|
@@ -1773,178 +1773,50 @@ var AuthorizationManager = class {
|
|
|
1773
1773
|
}
|
|
1774
1774
|
};
|
|
1775
1775
|
|
|
1776
|
-
// src/
|
|
1777
|
-
|
|
1776
|
+
// src/internal/constants.ts
|
|
1777
|
+
import { Key, Mail, Wallet2 } from "lucide-react";
|
|
1778
|
+
var LOOCAL_BACKUP_STATUS_KEY = "passport-backup-status";
|
|
1779
|
+
var BLOCKSCOUT_QUERY_STALE_TIME = 30 * 1e3;
|
|
1780
|
+
var BLOCKSCOUT_QUERY_GC_TIME = 5 * 60 * 1e3;
|
|
1781
|
+
|
|
1782
|
+
// src/iframe/lib/backup/cloud-backup.ts
|
|
1783
|
+
init_crypto_utils();
|
|
1784
|
+
|
|
1785
|
+
// src/iframe/lib/backup/cloudStorage.ts
|
|
1786
|
+
var GoogleDriveProvider = class {
|
|
1778
1787
|
constructor() {
|
|
1779
|
-
this.
|
|
1780
|
-
this.
|
|
1788
|
+
this.name = "Google Drive";
|
|
1789
|
+
this.id = "google-drive";
|
|
1790
|
+
this.icon = "\u{1F4C1}";
|
|
1791
|
+
// Can be replaced with proper icon
|
|
1792
|
+
this.accessToken = null;
|
|
1793
|
+
this.CLIENT_ID = typeof window.__GOOGLE_DRIVE_CLIENT_ID__ !== "undefined" && window.__GOOGLE_DRIVE_CLIENT_ID__ || "";
|
|
1794
|
+
this.DISCOVERY_DOC = "https://www.googleapis.com/discovery/v1/apis/drive/v3/rest";
|
|
1795
|
+
this.SCOPES = "https://www.googleapis.com/auth/drive.file https://www.googleapis.com/auth/drive.appdata";
|
|
1796
|
+
this.gapiInitialized = false;
|
|
1797
|
+
this.gisInitialized = false;
|
|
1798
|
+
this.initializeAPIs();
|
|
1781
1799
|
}
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
async apiCall(method, path, body, projectId, accessToken) {
|
|
1786
|
-
let url = `${this.tssUrl}${path}`;
|
|
1787
|
-
if (projectId) {
|
|
1788
|
-
const separator = url.includes("?") ? "&" : "?";
|
|
1789
|
-
url = `${url}${separator}projectId=${encodeURIComponent(projectId)}`;
|
|
1800
|
+
async initializeAPIs() {
|
|
1801
|
+
if (!window.gapi) {
|
|
1802
|
+
await this.loadScript("https://apis.google.com/js/api.js");
|
|
1790
1803
|
}
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
};
|
|
1794
|
-
if (accessToken) {
|
|
1795
|
-
headers["Authorization"] = `Bearer ${accessToken}`;
|
|
1804
|
+
if (!window.google?.accounts) {
|
|
1805
|
+
await this.loadScript("https://accounts.google.com/gsi/client");
|
|
1796
1806
|
}
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
headers,
|
|
1801
|
-
body: body ? JSON.stringify(body) : void 0,
|
|
1802
|
-
credentials: "include"
|
|
1803
|
-
// Send cookies
|
|
1807
|
+
if (!this.gapiInitialized) {
|
|
1808
|
+
await new Promise((resolve) => {
|
|
1809
|
+
window.gapi.load("client", resolve);
|
|
1804
1810
|
});
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
console.log("[iframe][TokenRefresh] TOKEN_EXPIRED detected, requesting new token from parent...");
|
|
1811
|
-
const newToken = await this.requestNewTokenFromParent();
|
|
1812
|
-
if (newToken) {
|
|
1813
|
-
console.log("[iframe][TokenRefresh] \u2705 Got new token, retrying request...");
|
|
1814
|
-
headers["Authorization"] = `Bearer ${newToken}`;
|
|
1815
|
-
const retryResponse = await fetch(url, {
|
|
1816
|
-
method,
|
|
1817
|
-
headers,
|
|
1818
|
-
body: body ? JSON.stringify(body) : void 0,
|
|
1819
|
-
credentials: "include"
|
|
1820
|
-
});
|
|
1821
|
-
if (!retryResponse.ok) {
|
|
1822
|
-
const retryErrorText = await retryResponse.text();
|
|
1823
|
-
throw new Error(`API call failed after token refresh (${retryResponse.status}): ${retryErrorText}`);
|
|
1824
|
-
}
|
|
1825
|
-
return await retryResponse.json();
|
|
1826
|
-
} else {
|
|
1827
|
-
throw new Error("Failed to refresh access token");
|
|
1828
|
-
}
|
|
1829
|
-
}
|
|
1830
|
-
} catch (parseError) {
|
|
1831
|
-
console.error("[iframe][TokenRefresh] Failed to parse 401 response:", parseError);
|
|
1832
|
-
}
|
|
1833
|
-
throw new Error(`API call failed (401): Authentication required`);
|
|
1834
|
-
}
|
|
1835
|
-
if (!response.ok) {
|
|
1836
|
-
const errorText = await response.text();
|
|
1837
|
-
throw new Error(`API call failed (${response.status}): ${errorText}`);
|
|
1838
|
-
}
|
|
1839
|
-
return await response.json();
|
|
1840
|
-
} catch (error) {
|
|
1841
|
-
console.error(`[iframe][TokenRefresh] API call failed: ${method} ${path}`, error);
|
|
1842
|
-
throw error instanceof Error ? error : new Error("Unknown API error");
|
|
1811
|
+
await window.gapi.client.init({
|
|
1812
|
+
discoveryDocs: [this.DISCOVERY_DOC]
|
|
1813
|
+
});
|
|
1814
|
+
this.gapiInitialized = true;
|
|
1815
|
+
console.log("[GoogleDrive] Google API client initialized");
|
|
1843
1816
|
}
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
*/
|
|
1848
|
-
async requestNewTokenFromParent() {
|
|
1849
|
-
if (this.pendingTokenRefresh) {
|
|
1850
|
-
console.log("[iframe][TokenRefresh] Token refresh already in progress, waiting...");
|
|
1851
|
-
return this.pendingTokenRefresh;
|
|
1852
|
-
}
|
|
1853
|
-
this.pendingTokenRefresh = new Promise((resolve, reject) => {
|
|
1854
|
-
const messageId = `token_refresh_${Date.now()}_${Math.random().toString(36).substring(2, 15)}`;
|
|
1855
|
-
const timeout = setTimeout(() => {
|
|
1856
|
-
cleanup();
|
|
1857
|
-
reject(new Error("Token refresh timeout"));
|
|
1858
|
-
}, 3e4);
|
|
1859
|
-
const messageHandler = (event) => {
|
|
1860
|
-
const message = event.data;
|
|
1861
|
-
if (message && message.type === "LUMIA_PASSPORT_TOKEN_REFRESHED" && message.messageId === messageId) {
|
|
1862
|
-
cleanup();
|
|
1863
|
-
if (message.accessToken) {
|
|
1864
|
-
console.log("[iframe][TokenRefresh] \u2705 Received new token from parent");
|
|
1865
|
-
resolve(message.accessToken);
|
|
1866
|
-
} else if (message.error) {
|
|
1867
|
-
console.error("[iframe][TokenRefresh] \u274C Token refresh failed:", message.error);
|
|
1868
|
-
reject(new Error(message.error));
|
|
1869
|
-
} else {
|
|
1870
|
-
reject(new Error("Invalid token refresh response"));
|
|
1871
|
-
}
|
|
1872
|
-
}
|
|
1873
|
-
};
|
|
1874
|
-
const cleanup = () => {
|
|
1875
|
-
clearTimeout(timeout);
|
|
1876
|
-
window.removeEventListener("message", messageHandler);
|
|
1877
|
-
this.pendingTokenRefresh = null;
|
|
1878
|
-
};
|
|
1879
|
-
window.addEventListener("message", messageHandler);
|
|
1880
|
-
if (window.parent && window.parent !== window) {
|
|
1881
|
-
window.parent.postMessage(
|
|
1882
|
-
{
|
|
1883
|
-
type: "LUMIA_PASSPORT_REQUEST_NEW_TOKEN",
|
|
1884
|
-
messageId,
|
|
1885
|
-
timestamp: Date.now()
|
|
1886
|
-
},
|
|
1887
|
-
"*"
|
|
1888
|
-
// Parent will validate origin
|
|
1889
|
-
);
|
|
1890
|
-
console.log("[iframe][TokenRefresh] \u{1F4E4} Sent LUMIA_PASSPORT_REQUEST_NEW_TOKEN to parent");
|
|
1891
|
-
} else {
|
|
1892
|
-
cleanup();
|
|
1893
|
-
reject(new Error("No parent window available"));
|
|
1894
|
-
}
|
|
1895
|
-
});
|
|
1896
|
-
try {
|
|
1897
|
-
return await this.pendingTokenRefresh;
|
|
1898
|
-
} catch (error) {
|
|
1899
|
-
this.pendingTokenRefresh = null;
|
|
1900
|
-
console.error("[iframe][TokenRefresh] Token refresh failed:", error);
|
|
1901
|
-
return null;
|
|
1902
|
-
}
|
|
1903
|
-
}
|
|
1904
|
-
};
|
|
1905
|
-
|
|
1906
|
-
// src/iframe/lib/backup-manager.ts
|
|
1907
|
-
init_server_backup();
|
|
1908
|
-
init_local_backup();
|
|
1909
|
-
|
|
1910
|
-
// src/iframe/lib/backup/cloud-backup.ts
|
|
1911
|
-
init_crypto_utils();
|
|
1912
|
-
|
|
1913
|
-
// src/iframe/lib/backup/cloudStorage.ts
|
|
1914
|
-
var GoogleDriveProvider = class {
|
|
1915
|
-
constructor() {
|
|
1916
|
-
this.name = "Google Drive";
|
|
1917
|
-
this.id = "google-drive";
|
|
1918
|
-
this.icon = "\u{1F4C1}";
|
|
1919
|
-
// Can be replaced with proper icon
|
|
1920
|
-
this.accessToken = null;
|
|
1921
|
-
this.CLIENT_ID = typeof window.__GOOGLE_DRIVE_CLIENT_ID__ !== "undefined" && window.__GOOGLE_DRIVE_CLIENT_ID__ || "";
|
|
1922
|
-
this.DISCOVERY_DOC = "https://www.googleapis.com/discovery/v1/apis/drive/v3/rest";
|
|
1923
|
-
this.SCOPES = "https://www.googleapis.com/auth/drive.file https://www.googleapis.com/auth/drive.appdata";
|
|
1924
|
-
this.gapiInitialized = false;
|
|
1925
|
-
this.gisInitialized = false;
|
|
1926
|
-
this.initializeAPIs();
|
|
1927
|
-
}
|
|
1928
|
-
async initializeAPIs() {
|
|
1929
|
-
if (!window.gapi) {
|
|
1930
|
-
await this.loadScript("https://apis.google.com/js/api.js");
|
|
1931
|
-
}
|
|
1932
|
-
if (!window.google?.accounts) {
|
|
1933
|
-
await this.loadScript("https://accounts.google.com/gsi/client");
|
|
1934
|
-
}
|
|
1935
|
-
if (!this.gapiInitialized) {
|
|
1936
|
-
await new Promise((resolve) => {
|
|
1937
|
-
window.gapi.load("client", resolve);
|
|
1938
|
-
});
|
|
1939
|
-
await window.gapi.client.init({
|
|
1940
|
-
discoveryDocs: [this.DISCOVERY_DOC]
|
|
1941
|
-
});
|
|
1942
|
-
this.gapiInitialized = true;
|
|
1943
|
-
console.log("[GoogleDrive] Google API client initialized");
|
|
1944
|
-
}
|
|
1945
|
-
if (!this.gisInitialized) {
|
|
1946
|
-
this.gisInitialized = true;
|
|
1947
|
-
console.log("[GoogleDrive] Google Identity Services initialized");
|
|
1817
|
+
if (!this.gisInitialized) {
|
|
1818
|
+
this.gisInitialized = true;
|
|
1819
|
+
console.log("[GoogleDrive] Google Identity Services initialized");
|
|
1948
1820
|
}
|
|
1949
1821
|
}
|
|
1950
1822
|
loadScript(src) {
|
|
@@ -2171,6 +2043,140 @@ function getCloudProviders() {
|
|
|
2171
2043
|
}));
|
|
2172
2044
|
}
|
|
2173
2045
|
|
|
2046
|
+
// src/iframe/lib/backup-manager.ts
|
|
2047
|
+
init_local_backup();
|
|
2048
|
+
init_server_backup();
|
|
2049
|
+
|
|
2050
|
+
// src/iframe/lib/token-refresh-client.ts
|
|
2051
|
+
var TokenRefreshApiClient = class {
|
|
2052
|
+
constructor() {
|
|
2053
|
+
this.pendingTokenRefresh = null;
|
|
2054
|
+
this.tssUrl = "https://api.lumiapassport.com/tss";
|
|
2055
|
+
}
|
|
2056
|
+
/**
|
|
2057
|
+
* Make API call with automatic token refresh on 401
|
|
2058
|
+
*/
|
|
2059
|
+
async apiCall(method, path, body, projectId, accessToken) {
|
|
2060
|
+
let url = `${this.tssUrl}${path}`;
|
|
2061
|
+
if (projectId) {
|
|
2062
|
+
const separator = url.includes("?") ? "&" : "?";
|
|
2063
|
+
url = `${url}${separator}projectId=${encodeURIComponent(projectId)}`;
|
|
2064
|
+
}
|
|
2065
|
+
const headers = {
|
|
2066
|
+
"Content-Type": "application/json"
|
|
2067
|
+
};
|
|
2068
|
+
if (accessToken) {
|
|
2069
|
+
headers["Authorization"] = `Bearer ${accessToken}`;
|
|
2070
|
+
}
|
|
2071
|
+
try {
|
|
2072
|
+
const response = await fetch(url, {
|
|
2073
|
+
method,
|
|
2074
|
+
headers,
|
|
2075
|
+
body: body ? JSON.stringify(body) : void 0,
|
|
2076
|
+
credentials: "include"
|
|
2077
|
+
// Send cookies
|
|
2078
|
+
});
|
|
2079
|
+
if (response.status === 401 && accessToken) {
|
|
2080
|
+
console.log("[iframe][TokenRefresh] Got 401, checking if TOKEN_EXPIRED...");
|
|
2081
|
+
try {
|
|
2082
|
+
const errorData = await response.json();
|
|
2083
|
+
if (errorData.error_code === "TOKEN_EXPIRED" || errorData.action === "REFRESH_TOKEN") {
|
|
2084
|
+
console.log("[iframe][TokenRefresh] TOKEN_EXPIRED detected, requesting new token from parent...");
|
|
2085
|
+
const newToken = await this.requestNewTokenFromParent();
|
|
2086
|
+
if (newToken) {
|
|
2087
|
+
console.log("[iframe][TokenRefresh] \u2705 Got new token, retrying request...");
|
|
2088
|
+
headers["Authorization"] = `Bearer ${newToken}`;
|
|
2089
|
+
const retryResponse = await fetch(url, {
|
|
2090
|
+
method,
|
|
2091
|
+
headers,
|
|
2092
|
+
body: body ? JSON.stringify(body) : void 0,
|
|
2093
|
+
credentials: "include"
|
|
2094
|
+
});
|
|
2095
|
+
if (!retryResponse.ok) {
|
|
2096
|
+
const retryErrorText = await retryResponse.text();
|
|
2097
|
+
throw new Error(`API call failed after token refresh (${retryResponse.status}): ${retryErrorText}`);
|
|
2098
|
+
}
|
|
2099
|
+
return await retryResponse.json();
|
|
2100
|
+
} else {
|
|
2101
|
+
throw new Error("Failed to refresh access token");
|
|
2102
|
+
}
|
|
2103
|
+
}
|
|
2104
|
+
} catch (parseError) {
|
|
2105
|
+
console.error("[iframe][TokenRefresh] Failed to parse 401 response:", parseError);
|
|
2106
|
+
}
|
|
2107
|
+
throw new Error(`API call failed (401): Authentication required`);
|
|
2108
|
+
}
|
|
2109
|
+
if (!response.ok) {
|
|
2110
|
+
const errorText = await response.text();
|
|
2111
|
+
throw new Error(`API call failed (${response.status}): ${errorText}`);
|
|
2112
|
+
}
|
|
2113
|
+
return await response.json();
|
|
2114
|
+
} catch (error) {
|
|
2115
|
+
console.error(`[iframe][TokenRefresh] API call failed: ${method} ${path}`, error);
|
|
2116
|
+
throw error instanceof Error ? error : new Error("Unknown API error");
|
|
2117
|
+
}
|
|
2118
|
+
}
|
|
2119
|
+
/**
|
|
2120
|
+
* Request new access token from parent window
|
|
2121
|
+
*/
|
|
2122
|
+
async requestNewTokenFromParent() {
|
|
2123
|
+
if (this.pendingTokenRefresh) {
|
|
2124
|
+
console.log("[iframe][TokenRefresh] Token refresh already in progress, waiting...");
|
|
2125
|
+
return this.pendingTokenRefresh;
|
|
2126
|
+
}
|
|
2127
|
+
this.pendingTokenRefresh = new Promise((resolve, reject) => {
|
|
2128
|
+
const messageId = `token_refresh_${Date.now()}_${Math.random().toString(36).substring(2, 15)}`;
|
|
2129
|
+
const timeout = setTimeout(() => {
|
|
2130
|
+
cleanup();
|
|
2131
|
+
reject(new Error("Token refresh timeout"));
|
|
2132
|
+
}, 3e4);
|
|
2133
|
+
const messageHandler = (event) => {
|
|
2134
|
+
const message = event.data;
|
|
2135
|
+
if (message && message.type === "LUMIA_PASSPORT_TOKEN_REFRESHED" && message.messageId === messageId) {
|
|
2136
|
+
cleanup();
|
|
2137
|
+
if (message.accessToken) {
|
|
2138
|
+
console.log("[iframe][TokenRefresh] \u2705 Received new token from parent");
|
|
2139
|
+
resolve(message.accessToken);
|
|
2140
|
+
} else if (message.error) {
|
|
2141
|
+
console.error("[iframe][TokenRefresh] \u274C Token refresh failed:", message.error);
|
|
2142
|
+
reject(new Error(message.error));
|
|
2143
|
+
} else {
|
|
2144
|
+
reject(new Error("Invalid token refresh response"));
|
|
2145
|
+
}
|
|
2146
|
+
}
|
|
2147
|
+
};
|
|
2148
|
+
const cleanup = () => {
|
|
2149
|
+
clearTimeout(timeout);
|
|
2150
|
+
window.removeEventListener("message", messageHandler);
|
|
2151
|
+
this.pendingTokenRefresh = null;
|
|
2152
|
+
};
|
|
2153
|
+
window.addEventListener("message", messageHandler);
|
|
2154
|
+
if (window.parent && window.parent !== window) {
|
|
2155
|
+
window.parent.postMessage(
|
|
2156
|
+
{
|
|
2157
|
+
type: "LUMIA_PASSPORT_REQUEST_NEW_TOKEN",
|
|
2158
|
+
messageId,
|
|
2159
|
+
timestamp: Date.now()
|
|
2160
|
+
},
|
|
2161
|
+
"*"
|
|
2162
|
+
// Parent will validate origin
|
|
2163
|
+
);
|
|
2164
|
+
console.log("[iframe][TokenRefresh] \u{1F4E4} Sent LUMIA_PASSPORT_REQUEST_NEW_TOKEN to parent");
|
|
2165
|
+
} else {
|
|
2166
|
+
cleanup();
|
|
2167
|
+
reject(new Error("No parent window available"));
|
|
2168
|
+
}
|
|
2169
|
+
});
|
|
2170
|
+
try {
|
|
2171
|
+
return await this.pendingTokenRefresh;
|
|
2172
|
+
} catch (error) {
|
|
2173
|
+
this.pendingTokenRefresh = null;
|
|
2174
|
+
console.error("[iframe][TokenRefresh] Token refresh failed:", error);
|
|
2175
|
+
return null;
|
|
2176
|
+
}
|
|
2177
|
+
}
|
|
2178
|
+
};
|
|
2179
|
+
|
|
2174
2180
|
// src/iframe/lib/backup-manager.ts
|
|
2175
2181
|
var BACKUP_VERSION = "1.0";
|
|
2176
2182
|
var BackupManager = class {
|
|
@@ -2215,7 +2221,7 @@ var BackupManager = class {
|
|
|
2215
2221
|
* Get backup status for all methods
|
|
2216
2222
|
*/
|
|
2217
2223
|
getBackupStatus(userId) {
|
|
2218
|
-
const statusData = localStorage.getItem(
|
|
2224
|
+
const statusData = localStorage.getItem(`${LOOCAL_BACKUP_STATUS_KEY}.${userId}`);
|
|
2219
2225
|
if (statusData) {
|
|
2220
2226
|
try {
|
|
2221
2227
|
return JSON.parse(statusData);
|
|
@@ -2235,10 +2241,7 @@ var BackupManager = class {
|
|
|
2235
2241
|
updateBackupStatus(userId, method, status) {
|
|
2236
2242
|
const currentStatus = this.getBackupStatus(userId);
|
|
2237
2243
|
currentStatus[method] = { ...currentStatus[method], ...status };
|
|
2238
|
-
localStorage.setItem(
|
|
2239
|
-
`lumia-passport.backup.status.${userId}`,
|
|
2240
|
-
JSON.stringify(currentStatus)
|
|
2241
|
-
);
|
|
2244
|
+
localStorage.setItem(`${LOOCAL_BACKUP_STATUS_KEY}.${userId}`, JSON.stringify(currentStatus));
|
|
2242
2245
|
console.log(`[iframe][BackupManager] Updated ${method} backup status for ${userId}`);
|
|
2243
2246
|
}
|
|
2244
2247
|
/**
|
|
@@ -2334,12 +2337,7 @@ var BackupManager = class {
|
|
|
2334
2337
|
encryptionPassword = result.password;
|
|
2335
2338
|
credentialId = result.credentialId;
|
|
2336
2339
|
}
|
|
2337
|
-
const encrypted = await encryptKeyshare2(
|
|
2338
|
-
data,
|
|
2339
|
-
encryptionPassword,
|
|
2340
|
-
password ? "password" : "passkey",
|
|
2341
|
-
credentialId
|
|
2342
|
-
);
|
|
2340
|
+
const encrypted = await encryptKeyshare2(data, encryptionPassword, password ? "password" : "passkey", credentialId);
|
|
2343
2341
|
console.log("[iframe][BackupManager] \u2705 Backup data encrypted");
|
|
2344
2342
|
return encrypted;
|
|
2345
2343
|
}
|
|
@@ -3230,18 +3228,221 @@ var RampnowOnrampAPI = class {
|
|
|
3230
3228
|
}
|
|
3231
3229
|
};
|
|
3232
3230
|
|
|
3233
|
-
// src/lib/
|
|
3234
|
-
var
|
|
3235
|
-
|
|
3236
|
-
|
|
3237
|
-
|
|
3238
|
-
|
|
3239
|
-
|
|
3240
|
-
|
|
3231
|
+
// src/iframe/lib/opted-out-apps-manager.ts
|
|
3232
|
+
var STORAGE_KEY = "lumia_passport_opted_out_apps";
|
|
3233
|
+
var OptedOutAppsManager = class {
|
|
3234
|
+
/**
|
|
3235
|
+
* Check if user has opted out of auto-trust for this app
|
|
3236
|
+
*/
|
|
3237
|
+
isOptedOut(userId, projectId, origin) {
|
|
3238
|
+
const optedOutApps = this.getOptedOutApps();
|
|
3239
|
+
const result = optedOutApps.some(
|
|
3240
|
+
(app) => app.userId === userId && app.projectId === projectId && app.origin === origin
|
|
3241
|
+
);
|
|
3242
|
+
return result;
|
|
3243
|
+
}
|
|
3244
|
+
/**
|
|
3245
|
+
* Add app to opted-out list (when user removes ecosystem app from trusted list)
|
|
3246
|
+
*/
|
|
3247
|
+
addOptedOut(userId, projectId, origin) {
|
|
3248
|
+
const optedOutApps = this.getOptedOutApps();
|
|
3249
|
+
const exists = optedOutApps.some(
|
|
3250
|
+
(app) => app.userId === userId && app.projectId === projectId && app.origin === origin
|
|
3251
|
+
);
|
|
3252
|
+
if (exists) {
|
|
3253
|
+
return;
|
|
3241
3254
|
}
|
|
3255
|
+
optedOutApps.push({
|
|
3256
|
+
userId,
|
|
3257
|
+
projectId,
|
|
3258
|
+
origin,
|
|
3259
|
+
optedOutAt: Date.now()
|
|
3260
|
+
});
|
|
3261
|
+
this.saveOptedOutApps(optedOutApps);
|
|
3262
|
+
}
|
|
3263
|
+
/**
|
|
3264
|
+
* Remove app from opted-out list (when user manually re-trusts via checkbox)
|
|
3265
|
+
*/
|
|
3266
|
+
removeOptedOut(userId, projectId, origin) {
|
|
3267
|
+
const optedOutApps = this.getOptedOutApps();
|
|
3268
|
+
const filtered = optedOutApps.filter(
|
|
3269
|
+
(app) => !(app.userId === userId && app.projectId === projectId && app.origin === origin)
|
|
3270
|
+
);
|
|
3271
|
+
this.saveOptedOutApps(filtered);
|
|
3272
|
+
}
|
|
3273
|
+
/**
|
|
3274
|
+
* Clear all opted-out apps for a user (when clearing all trusted apps)
|
|
3275
|
+
*/
|
|
3276
|
+
clearOptedOutForUser(userId) {
|
|
3277
|
+
const optedOutApps = this.getOptedOutApps();
|
|
3278
|
+
const filtered = optedOutApps.filter((app) => app.userId !== userId);
|
|
3279
|
+
this.saveOptedOutApps(filtered);
|
|
3280
|
+
}
|
|
3281
|
+
/**
|
|
3282
|
+
* Get all opted-out apps from storage
|
|
3283
|
+
*/
|
|
3284
|
+
getOptedOutApps() {
|
|
3285
|
+
try {
|
|
3286
|
+
const data = localStorage.getItem(STORAGE_KEY);
|
|
3287
|
+
if (!data) {
|
|
3288
|
+
return [];
|
|
3289
|
+
}
|
|
3290
|
+
const apps = JSON.parse(data);
|
|
3291
|
+
return Array.isArray(apps) ? apps : [];
|
|
3292
|
+
} catch (error) {
|
|
3293
|
+
console.error("[OptedOutApps] Error reading opted-out apps:", error);
|
|
3294
|
+
return [];
|
|
3295
|
+
}
|
|
3296
|
+
}
|
|
3297
|
+
/**
|
|
3298
|
+
* Save opted-out apps to storage
|
|
3299
|
+
*/
|
|
3300
|
+
saveOptedOutApps(apps) {
|
|
3301
|
+
try {
|
|
3302
|
+
localStorage.setItem(STORAGE_KEY, JSON.stringify(apps));
|
|
3303
|
+
} catch (error) {
|
|
3304
|
+
console.error("[OptedOutApps] Error saving opted-out apps:", error);
|
|
3305
|
+
}
|
|
3306
|
+
}
|
|
3307
|
+
};
|
|
3308
|
+
|
|
3309
|
+
// src/iframe/lib/sdk-channel-manager.ts
|
|
3310
|
+
var SdkChannelManager = class {
|
|
3311
|
+
// 30 minutes
|
|
3312
|
+
constructor() {
|
|
3313
|
+
this.channels = /* @__PURE__ */ new Map();
|
|
3314
|
+
this.CHANNEL_TIMEOUT = 30 * 60 * 1e3;
|
|
3315
|
+
setInterval(() => this.cleanupExpiredChannels(), 5 * 60 * 1e3);
|
|
3316
|
+
}
|
|
3317
|
+
/**
|
|
3318
|
+
* Create a new SDK channel
|
|
3319
|
+
*/
|
|
3320
|
+
createChannel(projectId, origin) {
|
|
3321
|
+
const channelToken = this.generateSecureToken();
|
|
3322
|
+
const channel = {
|
|
3323
|
+
token: channelToken,
|
|
3324
|
+
projectId,
|
|
3325
|
+
origin,
|
|
3326
|
+
createdAt: Date.now(),
|
|
3327
|
+
lastActivity: Date.now()
|
|
3328
|
+
};
|
|
3329
|
+
this.channels.set(channelToken, channel);
|
|
3330
|
+
console.log(`[iframe][SdkChannel] \u2713 Created: ${channelToken.slice(0, 16)}... origin=${origin}, total=${this.channels.size}`);
|
|
3331
|
+
return channelToken;
|
|
3332
|
+
}
|
|
3333
|
+
/**
|
|
3334
|
+
* Validate channel token and origin
|
|
3335
|
+
*/
|
|
3336
|
+
validateChannel(channelToken, origin) {
|
|
3337
|
+
const channel = this.channels.get(channelToken);
|
|
3338
|
+
if (!channel) {
|
|
3339
|
+
console.warn(`[iframe][SdkChannel] \u2717 NOT FOUND: ${channelToken.slice(0, 16)}... (active channels: ${this.channels.size})`);
|
|
3340
|
+
return false;
|
|
3341
|
+
}
|
|
3342
|
+
if (channel.origin !== origin) {
|
|
3343
|
+
console.error(`[iframe][SdkChannel] \u2717 ORIGIN MISMATCH: expected ${channel.origin}, got ${origin}`);
|
|
3344
|
+
return false;
|
|
3345
|
+
}
|
|
3346
|
+
const age = Date.now() - channel.lastActivity;
|
|
3347
|
+
if (age > this.CHANNEL_TIMEOUT) {
|
|
3348
|
+
console.warn(`[iframe][SdkChannel] \u2717 EXPIRED: age=${Math.round(age / 1e3)}s > timeout=${this.CHANNEL_TIMEOUT / 1e3}s`);
|
|
3349
|
+
this.channels.delete(channelToken);
|
|
3350
|
+
return false;
|
|
3351
|
+
}
|
|
3352
|
+
channel.lastActivity = Date.now();
|
|
3353
|
+
return true;
|
|
3354
|
+
}
|
|
3355
|
+
/**
|
|
3356
|
+
* Get channel by token
|
|
3357
|
+
*/
|
|
3358
|
+
getChannel(channelToken) {
|
|
3359
|
+
return this.channels.get(channelToken) || null;
|
|
3360
|
+
}
|
|
3361
|
+
/**
|
|
3362
|
+
* Invalidate channel
|
|
3363
|
+
*/
|
|
3364
|
+
invalidateChannel(channelToken) {
|
|
3365
|
+
const channel = this.channels.get(channelToken);
|
|
3366
|
+
if (channel) {
|
|
3367
|
+
console.log(`[iframe][SdkChannel] Invalidated channel for origin: ${channel.origin}`);
|
|
3368
|
+
this.channels.delete(channelToken);
|
|
3369
|
+
}
|
|
3370
|
+
}
|
|
3371
|
+
/**
|
|
3372
|
+
* Get all channels for a project
|
|
3373
|
+
*/
|
|
3374
|
+
getProjectChannels(projectId) {
|
|
3375
|
+
return Array.from(this.channels.values()).filter(
|
|
3376
|
+
(channel) => channel.projectId === projectId
|
|
3377
|
+
);
|
|
3378
|
+
}
|
|
3379
|
+
/**
|
|
3380
|
+
* Generate cryptographically secure channel token
|
|
3381
|
+
*/
|
|
3382
|
+
generateSecureToken() {
|
|
3383
|
+
const buffer = new Uint8Array(32);
|
|
3384
|
+
crypto.getRandomValues(buffer);
|
|
3385
|
+
return Array.from(buffer, (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
3386
|
+
}
|
|
3387
|
+
/**
|
|
3388
|
+
* Cleanup expired channels
|
|
3389
|
+
*/
|
|
3390
|
+
cleanupExpiredChannels() {
|
|
3391
|
+
const now = Date.now();
|
|
3392
|
+
let cleanedCount = 0;
|
|
3393
|
+
for (const [token, channel] of this.channels.entries()) {
|
|
3394
|
+
const age = now - channel.lastActivity;
|
|
3395
|
+
if (age > this.CHANNEL_TIMEOUT) {
|
|
3396
|
+
this.channels.delete(token);
|
|
3397
|
+
cleanedCount++;
|
|
3398
|
+
}
|
|
3399
|
+
}
|
|
3400
|
+
if (cleanedCount > 0) {
|
|
3401
|
+
console.log(`[iframe][SdkChannel] Cleaned up ${cleanedCount} expired channels`);
|
|
3402
|
+
}
|
|
3403
|
+
}
|
|
3404
|
+
/**
|
|
3405
|
+
* Check if there is an active channel for a given origin
|
|
3406
|
+
*/
|
|
3407
|
+
hasChannel(origin) {
|
|
3408
|
+
const now = Date.now();
|
|
3409
|
+
for (const channel of this.channels.values()) {
|
|
3410
|
+
if (channel.origin === origin) {
|
|
3411
|
+
const age = now - channel.lastActivity;
|
|
3412
|
+
if (age <= this.CHANNEL_TIMEOUT) {
|
|
3413
|
+
return true;
|
|
3414
|
+
}
|
|
3415
|
+
}
|
|
3416
|
+
}
|
|
3417
|
+
return false;
|
|
3418
|
+
}
|
|
3419
|
+
/**
|
|
3420
|
+
* Get channel statistics
|
|
3421
|
+
*/
|
|
3422
|
+
getStats() {
|
|
3423
|
+
const stats = {
|
|
3424
|
+
total: this.channels.size,
|
|
3425
|
+
byProject: {}
|
|
3426
|
+
};
|
|
3427
|
+
for (const channel of this.channels.values()) {
|
|
3428
|
+
stats.byProject[channel.projectId] = (stats.byProject[channel.projectId] || 0) + 1;
|
|
3429
|
+
}
|
|
3430
|
+
return stats;
|
|
3242
3431
|
}
|
|
3243
3432
|
};
|
|
3244
|
-
|
|
3433
|
+
|
|
3434
|
+
// src/lib/errors.ts
|
|
3435
|
+
var AccountAbstractionError = class _AccountAbstractionError extends Error {
|
|
3436
|
+
constructor(message, code) {
|
|
3437
|
+
super(message);
|
|
3438
|
+
this.code = code;
|
|
3439
|
+
this.name = "LumiaPassportError";
|
|
3440
|
+
if (Error.captureStackTrace) {
|
|
3441
|
+
Error.captureStackTrace(this, _AccountAbstractionError);
|
|
3442
|
+
}
|
|
3443
|
+
}
|
|
3444
|
+
};
|
|
3445
|
+
var _UserRejectedError = class _UserRejectedError extends AccountAbstractionError {
|
|
3245
3446
|
constructor(message = "User rejected transaction") {
|
|
3246
3447
|
super(message, _UserRejectedError.CODE);
|
|
3247
3448
|
this.name = "UserRejectedError";
|
|
@@ -3307,7 +3508,7 @@ var SecureMessenger = class {
|
|
|
3307
3508
|
return;
|
|
3308
3509
|
}
|
|
3309
3510
|
if (!this.validateMessageStructure(message)) {
|
|
3310
|
-
console.error("[iframe][Messenger] Invalid
|
|
3511
|
+
console.error("[iframe][Messenger] Invalid EmbarkAI message structure:", {
|
|
3311
3512
|
type: message.type,
|
|
3312
3513
|
hasMessageId: !!message.messageId,
|
|
3313
3514
|
hasTimestamp: !!message.timestamp,
|
|
@@ -3377,7 +3578,7 @@ var SecureMessenger = class {
|
|
|
3377
3578
|
return;
|
|
3378
3579
|
}
|
|
3379
3580
|
const errorMessage = error instanceof Error ? error.message : error;
|
|
3380
|
-
const errorCode = error instanceof
|
|
3581
|
+
const errorCode = error instanceof AccountAbstractionError ? error.code : void 0;
|
|
3381
3582
|
const response = {
|
|
3382
3583
|
type: "LUMIA_PASSPORT_ERROR",
|
|
3383
3584
|
messageId,
|
|
@@ -3419,7 +3620,7 @@ var SecureMessenger = class {
|
|
|
3419
3620
|
console.log("[iframe][Messenger] SDK channel token set");
|
|
3420
3621
|
}
|
|
3421
3622
|
/**
|
|
3422
|
-
* Quick check if message looks like
|
|
3623
|
+
* Quick check if message looks like EmbarkAI protocol
|
|
3423
3624
|
* This filters out browser extension messages, dev tools, etc.
|
|
3424
3625
|
*/
|
|
3425
3626
|
isLumiaMessage(message) {
|
|
@@ -3527,131 +3728,6 @@ var SecureMessenger = class {
|
|
|
3527
3728
|
}
|
|
3528
3729
|
};
|
|
3529
3730
|
|
|
3530
|
-
// src/iframe/lib/sdk-channel-manager.ts
|
|
3531
|
-
var SdkChannelManager = class {
|
|
3532
|
-
// 30 minutes
|
|
3533
|
-
constructor() {
|
|
3534
|
-
this.channels = /* @__PURE__ */ new Map();
|
|
3535
|
-
this.CHANNEL_TIMEOUT = 30 * 60 * 1e3;
|
|
3536
|
-
setInterval(() => this.cleanupExpiredChannels(), 5 * 60 * 1e3);
|
|
3537
|
-
}
|
|
3538
|
-
/**
|
|
3539
|
-
* Create a new SDK channel
|
|
3540
|
-
*/
|
|
3541
|
-
createChannel(projectId, origin) {
|
|
3542
|
-
const channelToken = this.generateSecureToken();
|
|
3543
|
-
const channel = {
|
|
3544
|
-
token: channelToken,
|
|
3545
|
-
projectId,
|
|
3546
|
-
origin,
|
|
3547
|
-
createdAt: Date.now(),
|
|
3548
|
-
lastActivity: Date.now()
|
|
3549
|
-
};
|
|
3550
|
-
this.channels.set(channelToken, channel);
|
|
3551
|
-
console.log(`[iframe][SdkChannel] \u2713 Created: ${channelToken.slice(0, 16)}... origin=${origin}, total=${this.channels.size}`);
|
|
3552
|
-
return channelToken;
|
|
3553
|
-
}
|
|
3554
|
-
/**
|
|
3555
|
-
* Validate channel token and origin
|
|
3556
|
-
*/
|
|
3557
|
-
validateChannel(channelToken, origin) {
|
|
3558
|
-
const channel = this.channels.get(channelToken);
|
|
3559
|
-
if (!channel) {
|
|
3560
|
-
console.warn(`[iframe][SdkChannel] \u2717 NOT FOUND: ${channelToken.slice(0, 16)}... (active channels: ${this.channels.size})`);
|
|
3561
|
-
return false;
|
|
3562
|
-
}
|
|
3563
|
-
if (channel.origin !== origin) {
|
|
3564
|
-
console.error(`[iframe][SdkChannel] \u2717 ORIGIN MISMATCH: expected ${channel.origin}, got ${origin}`);
|
|
3565
|
-
return false;
|
|
3566
|
-
}
|
|
3567
|
-
const age = Date.now() - channel.lastActivity;
|
|
3568
|
-
if (age > this.CHANNEL_TIMEOUT) {
|
|
3569
|
-
console.warn(`[iframe][SdkChannel] \u2717 EXPIRED: age=${Math.round(age / 1e3)}s > timeout=${this.CHANNEL_TIMEOUT / 1e3}s`);
|
|
3570
|
-
this.channels.delete(channelToken);
|
|
3571
|
-
return false;
|
|
3572
|
-
}
|
|
3573
|
-
channel.lastActivity = Date.now();
|
|
3574
|
-
return true;
|
|
3575
|
-
}
|
|
3576
|
-
/**
|
|
3577
|
-
* Get channel by token
|
|
3578
|
-
*/
|
|
3579
|
-
getChannel(channelToken) {
|
|
3580
|
-
return this.channels.get(channelToken) || null;
|
|
3581
|
-
}
|
|
3582
|
-
/**
|
|
3583
|
-
* Invalidate channel
|
|
3584
|
-
*/
|
|
3585
|
-
invalidateChannel(channelToken) {
|
|
3586
|
-
const channel = this.channels.get(channelToken);
|
|
3587
|
-
if (channel) {
|
|
3588
|
-
console.log(`[iframe][SdkChannel] Invalidated channel for origin: ${channel.origin}`);
|
|
3589
|
-
this.channels.delete(channelToken);
|
|
3590
|
-
}
|
|
3591
|
-
}
|
|
3592
|
-
/**
|
|
3593
|
-
* Get all channels for a project
|
|
3594
|
-
*/
|
|
3595
|
-
getProjectChannels(projectId) {
|
|
3596
|
-
return Array.from(this.channels.values()).filter(
|
|
3597
|
-
(channel) => channel.projectId === projectId
|
|
3598
|
-
);
|
|
3599
|
-
}
|
|
3600
|
-
/**
|
|
3601
|
-
* Generate cryptographically secure channel token
|
|
3602
|
-
*/
|
|
3603
|
-
generateSecureToken() {
|
|
3604
|
-
const buffer = new Uint8Array(32);
|
|
3605
|
-
crypto.getRandomValues(buffer);
|
|
3606
|
-
return Array.from(buffer, (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
3607
|
-
}
|
|
3608
|
-
/**
|
|
3609
|
-
* Cleanup expired channels
|
|
3610
|
-
*/
|
|
3611
|
-
cleanupExpiredChannels() {
|
|
3612
|
-
const now = Date.now();
|
|
3613
|
-
let cleanedCount = 0;
|
|
3614
|
-
for (const [token, channel] of this.channels.entries()) {
|
|
3615
|
-
const age = now - channel.lastActivity;
|
|
3616
|
-
if (age > this.CHANNEL_TIMEOUT) {
|
|
3617
|
-
this.channels.delete(token);
|
|
3618
|
-
cleanedCount++;
|
|
3619
|
-
}
|
|
3620
|
-
}
|
|
3621
|
-
if (cleanedCount > 0) {
|
|
3622
|
-
console.log(`[iframe][SdkChannel] Cleaned up ${cleanedCount} expired channels`);
|
|
3623
|
-
}
|
|
3624
|
-
}
|
|
3625
|
-
/**
|
|
3626
|
-
* Check if there is an active channel for a given origin
|
|
3627
|
-
*/
|
|
3628
|
-
hasChannel(origin) {
|
|
3629
|
-
const now = Date.now();
|
|
3630
|
-
for (const channel of this.channels.values()) {
|
|
3631
|
-
if (channel.origin === origin) {
|
|
3632
|
-
const age = now - channel.lastActivity;
|
|
3633
|
-
if (age <= this.CHANNEL_TIMEOUT) {
|
|
3634
|
-
return true;
|
|
3635
|
-
}
|
|
3636
|
-
}
|
|
3637
|
-
}
|
|
3638
|
-
return false;
|
|
3639
|
-
}
|
|
3640
|
-
/**
|
|
3641
|
-
* Get channel statistics
|
|
3642
|
-
*/
|
|
3643
|
-
getStats() {
|
|
3644
|
-
const stats = {
|
|
3645
|
-
total: this.channels.size,
|
|
3646
|
-
byProject: {}
|
|
3647
|
-
};
|
|
3648
|
-
for (const channel of this.channels.values()) {
|
|
3649
|
-
stats.byProject[channel.projectId] = (stats.byProject[channel.projectId] || 0) + 1;
|
|
3650
|
-
}
|
|
3651
|
-
return stats;
|
|
3652
|
-
}
|
|
3653
|
-
};
|
|
3654
|
-
|
|
3655
3731
|
// src/iframe/lib/storage-manager.ts
|
|
3656
3732
|
var StorageManager = class {
|
|
3657
3733
|
constructor() {
|
|
@@ -3782,7 +3858,7 @@ var StorageManager = class {
|
|
|
3782
3858
|
};
|
|
3783
3859
|
|
|
3784
3860
|
// src/iframe/lib/trusted-apps-manager.ts
|
|
3785
|
-
var
|
|
3861
|
+
var STORAGE_KEY2 = "passport-trusted-apps";
|
|
3786
3862
|
var TrustedAppsManager = class {
|
|
3787
3863
|
/**
|
|
3788
3864
|
* Check if app is trusted by user
|
|
@@ -3857,7 +3933,7 @@ var TrustedAppsManager = class {
|
|
|
3857
3933
|
*/
|
|
3858
3934
|
getTrustedApps() {
|
|
3859
3935
|
try {
|
|
3860
|
-
const data = localStorage.getItem(
|
|
3936
|
+
const data = localStorage.getItem(STORAGE_KEY2);
|
|
3861
3937
|
console.log("[TrustedApps] Raw storage data:", data);
|
|
3862
3938
|
if (!data) {
|
|
3863
3939
|
console.log("[TrustedApps] No data in storage");
|
|
@@ -3876,7 +3952,7 @@ var TrustedAppsManager = class {
|
|
|
3876
3952
|
*/
|
|
3877
3953
|
saveTrustedApps(apps) {
|
|
3878
3954
|
try {
|
|
3879
|
-
localStorage.setItem(
|
|
3955
|
+
localStorage.setItem(STORAGE_KEY2, JSON.stringify(apps));
|
|
3880
3956
|
} catch (error) {
|
|
3881
3957
|
console.error("[TrustedApps] Error saving trusted apps:", error);
|
|
3882
3958
|
throw new Error("Failed to save trusted apps");
|
|
@@ -3884,84 +3960,6 @@ var TrustedAppsManager = class {
|
|
|
3884
3960
|
}
|
|
3885
3961
|
};
|
|
3886
3962
|
|
|
3887
|
-
// src/iframe/lib/opted-out-apps-manager.ts
|
|
3888
|
-
var STORAGE_KEY2 = "lumia_passport_opted_out_apps";
|
|
3889
|
-
var OptedOutAppsManager = class {
|
|
3890
|
-
/**
|
|
3891
|
-
* Check if user has opted out of auto-trust for this app
|
|
3892
|
-
*/
|
|
3893
|
-
isOptedOut(userId, projectId, origin) {
|
|
3894
|
-
const optedOutApps = this.getOptedOutApps();
|
|
3895
|
-
const result = optedOutApps.some(
|
|
3896
|
-
(app) => app.userId === userId && app.projectId === projectId && app.origin === origin
|
|
3897
|
-
);
|
|
3898
|
-
return result;
|
|
3899
|
-
}
|
|
3900
|
-
/**
|
|
3901
|
-
* Add app to opted-out list (when user removes ecosystem app from trusted list)
|
|
3902
|
-
*/
|
|
3903
|
-
addOptedOut(userId, projectId, origin) {
|
|
3904
|
-
const optedOutApps = this.getOptedOutApps();
|
|
3905
|
-
const exists = optedOutApps.some(
|
|
3906
|
-
(app) => app.userId === userId && app.projectId === projectId && app.origin === origin
|
|
3907
|
-
);
|
|
3908
|
-
if (exists) {
|
|
3909
|
-
return;
|
|
3910
|
-
}
|
|
3911
|
-
optedOutApps.push({
|
|
3912
|
-
userId,
|
|
3913
|
-
projectId,
|
|
3914
|
-
origin,
|
|
3915
|
-
optedOutAt: Date.now()
|
|
3916
|
-
});
|
|
3917
|
-
this.saveOptedOutApps(optedOutApps);
|
|
3918
|
-
}
|
|
3919
|
-
/**
|
|
3920
|
-
* Remove app from opted-out list (when user manually re-trusts via checkbox)
|
|
3921
|
-
*/
|
|
3922
|
-
removeOptedOut(userId, projectId, origin) {
|
|
3923
|
-
const optedOutApps = this.getOptedOutApps();
|
|
3924
|
-
const filtered = optedOutApps.filter(
|
|
3925
|
-
(app) => !(app.userId === userId && app.projectId === projectId && app.origin === origin)
|
|
3926
|
-
);
|
|
3927
|
-
this.saveOptedOutApps(filtered);
|
|
3928
|
-
}
|
|
3929
|
-
/**
|
|
3930
|
-
* Clear all opted-out apps for a user (when clearing all trusted apps)
|
|
3931
|
-
*/
|
|
3932
|
-
clearOptedOutForUser(userId) {
|
|
3933
|
-
const optedOutApps = this.getOptedOutApps();
|
|
3934
|
-
const filtered = optedOutApps.filter((app) => app.userId !== userId);
|
|
3935
|
-
this.saveOptedOutApps(filtered);
|
|
3936
|
-
}
|
|
3937
|
-
/**
|
|
3938
|
-
* Get all opted-out apps from storage
|
|
3939
|
-
*/
|
|
3940
|
-
getOptedOutApps() {
|
|
3941
|
-
try {
|
|
3942
|
-
const data = localStorage.getItem(STORAGE_KEY2);
|
|
3943
|
-
if (!data) {
|
|
3944
|
-
return [];
|
|
3945
|
-
}
|
|
3946
|
-
const apps = JSON.parse(data);
|
|
3947
|
-
return Array.isArray(apps) ? apps : [];
|
|
3948
|
-
} catch (error) {
|
|
3949
|
-
console.error("[OptedOutApps] Error reading opted-out apps:", error);
|
|
3950
|
-
return [];
|
|
3951
|
-
}
|
|
3952
|
-
}
|
|
3953
|
-
/**
|
|
3954
|
-
* Save opted-out apps to storage
|
|
3955
|
-
*/
|
|
3956
|
-
saveOptedOutApps(apps) {
|
|
3957
|
-
try {
|
|
3958
|
-
localStorage.setItem(STORAGE_KEY2, JSON.stringify(apps));
|
|
3959
|
-
} catch (error) {
|
|
3960
|
-
console.error("[OptedOutApps] Error saving opted-out apps:", error);
|
|
3961
|
-
}
|
|
3962
|
-
}
|
|
3963
|
-
};
|
|
3964
|
-
|
|
3965
3963
|
// src/iframe/lib/signing-manager.ts
|
|
3966
3964
|
var SigningManager = class extends TokenRefreshApiClient {
|
|
3967
3965
|
constructor() {
|
|
@@ -4406,11 +4404,11 @@ var SigningManager = class extends TokenRefreshApiClient {
|
|
|
4406
4404
|
};
|
|
4407
4405
|
|
|
4408
4406
|
// src/iframe/main.ts
|
|
4409
|
-
var IFRAME_VERSION = "0.1.
|
|
4407
|
+
var IFRAME_VERSION = "0.1.3";
|
|
4410
4408
|
var IframeWallet = class {
|
|
4411
4409
|
constructor() {
|
|
4412
4410
|
console.log("=".repeat(60));
|
|
4413
|
-
console.log(`
|
|
4411
|
+
console.log(` EmbarkAI Secure Wallet - iframe version ${IFRAME_VERSION}`);
|
|
4414
4412
|
console.log("=".repeat(60));
|
|
4415
4413
|
this.messenger = new SecureMessenger();
|
|
4416
4414
|
this.sdkChannelManager = new SdkChannelManager();
|
|
@@ -4521,11 +4519,15 @@ var IframeWallet = class {
|
|
|
4521
4519
|
const { messageId } = message;
|
|
4522
4520
|
const isValid = this.sdkChannelManager.validateChannel(sessionToken, origin);
|
|
4523
4521
|
const stats = this.sdkChannelManager.getStats();
|
|
4524
|
-
this.messenger.sendResponse(
|
|
4525
|
-
|
|
4526
|
-
|
|
4527
|
-
|
|
4528
|
-
|
|
4522
|
+
this.messenger.sendResponse(
|
|
4523
|
+
messageId,
|
|
4524
|
+
{
|
|
4525
|
+
type: "LUMIA_PASSPORT_HEARTBEAT_RESPONSE",
|
|
4526
|
+
valid: isValid,
|
|
4527
|
+
channelsCount: stats.total
|
|
4528
|
+
},
|
|
4529
|
+
origin
|
|
4530
|
+
);
|
|
4529
4531
|
console.log(`[iframe] HEARTBEAT: valid=${isValid}, channels=${stats.total}`);
|
|
4530
4532
|
}
|
|
4531
4533
|
async handleSDKAuth(message, origin) {
|
|
@@ -4570,13 +4572,7 @@ var IframeWallet = class {
|
|
|
4570
4572
|
if (!isOptedOut) {
|
|
4571
4573
|
const metadata = await this.authManager.getProjectMetadata(projectId);
|
|
4572
4574
|
console.log(`[iframe] Got metadata for auto-trust:`, metadata?.name, metadata?.logo);
|
|
4573
|
-
this.trustedApps.addTrustedApp(
|
|
4574
|
-
userId,
|
|
4575
|
-
projectId,
|
|
4576
|
-
origin,
|
|
4577
|
-
metadata?.name,
|
|
4578
|
-
metadata?.logo
|
|
4579
|
-
);
|
|
4575
|
+
this.trustedApps.addTrustedApp(userId, projectId, origin, metadata?.name, metadata?.logo);
|
|
4580
4576
|
console.log(`[iframe] \u2705 Auto-added to trusted apps list for seamless transactions`);
|
|
4581
4577
|
} else {
|
|
4582
4578
|
console.log(`[iframe] \u2139\uFE0F User opted-out of auto-trust for this app, skipping`);
|