@haex-space/vault-sdk 3.0.0 → 3.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.
Files changed (45) hide show
  1. package/dist/{client-dpJHb3FP.d.ts → client-GeColu97.d.mts} +270 -2
  2. package/dist/{client-BXtzUBWv.d.mts → client-z1jTcuQE.d.ts} +270 -2
  3. package/dist/index.d.mts +93 -6
  4. package/dist/index.d.ts +93 -6
  5. package/dist/index.js +301 -45
  6. package/dist/index.js.map +1 -1
  7. package/dist/index.mjs +298 -46
  8. package/dist/index.mjs.map +1 -1
  9. package/dist/node.d.mts +1 -1
  10. package/dist/node.d.ts +1 -1
  11. package/dist/nuxt.js +34 -7
  12. package/dist/nuxt.js.map +1 -1
  13. package/dist/nuxt.mjs +34 -7
  14. package/dist/nuxt.mjs.map +1 -1
  15. package/dist/react.d.mts +2 -2
  16. package/dist/react.d.ts +2 -2
  17. package/dist/react.js +294 -44
  18. package/dist/react.js.map +1 -1
  19. package/dist/react.mjs +294 -44
  20. package/dist/react.mjs.map +1 -1
  21. package/dist/runtime/nuxt.plugin.client.d.mts +2 -2
  22. package/dist/runtime/nuxt.plugin.client.d.ts +2 -2
  23. package/dist/runtime/nuxt.plugin.client.js +294 -48
  24. package/dist/runtime/nuxt.plugin.client.js.map +1 -1
  25. package/dist/runtime/nuxt.plugin.client.mjs +294 -48
  26. package/dist/runtime/nuxt.plugin.client.mjs.map +1 -1
  27. package/dist/svelte.d.mts +2 -2
  28. package/dist/svelte.d.ts +2 -2
  29. package/dist/svelte.js +294 -44
  30. package/dist/svelte.js.map +1 -1
  31. package/dist/svelte.mjs +294 -44
  32. package/dist/svelte.mjs.map +1 -1
  33. package/dist/{types-DmCSegdY.d.mts → types-CDMBvvjl.d.mts} +2 -0
  34. package/dist/{types-DmCSegdY.d.ts → types-CDMBvvjl.d.ts} +2 -0
  35. package/dist/vite.js +33 -6
  36. package/dist/vite.js.map +1 -1
  37. package/dist/vite.mjs +33 -6
  38. package/dist/vite.mjs.map +1 -1
  39. package/dist/vue.d.mts +2 -2
  40. package/dist/vue.d.ts +2 -2
  41. package/dist/vue.js +294 -44
  42. package/dist/vue.js.map +1 -1
  43. package/dist/vue.mjs +294 -44
  44. package/dist/vue.mjs.map +1 -1
  45. package/package.json +2 -1
package/dist/index.js CHANGED
@@ -541,7 +541,24 @@ var HAEXSPACE_MESSAGE_TYPES = {
541
541
  /** Debug message for development/troubleshooting */
542
542
  DEBUG: "haexspace:debug",
543
543
  /** Console forwarding from extension iframe */
544
- CONSOLE_FORWARD: "console.forward"
544
+ CONSOLE_FORWARD: "console.forward",
545
+ /**
546
+ * Sent from main window to iframe on the shared window listener, carrying
547
+ * one `MessagePort` in `event.ports[0]`. Once received, the SDK switches
548
+ * to port-based messaging and never reads the window listener again.
549
+ *
550
+ * Payload: `{ type: PORT_INIT }` — no data. The port itself is the payload.
551
+ */
552
+ PORT_INIT: "haexspace:port:init",
553
+ /**
554
+ * Sent from SDK to main window *over the MessagePort* after the port is
555
+ * installed. Main uses this to mark the iframe as ready and flush any
556
+ * events buffered during the handshake window. Only valid on the port —
557
+ * a READY sent over window.postMessage is ignored.
558
+ *
559
+ * Payload: `{ type: PORT_READY }` — no data.
560
+ */
561
+ PORT_READY: "haexspace:port:ready"
545
562
  };
546
563
 
547
564
  // src/polyfills/debug.ts
@@ -1032,6 +1049,40 @@ var SHELL_COMMANDS = {
1032
1049
  close: "extension_shell_close"
1033
1050
  };
1034
1051
 
1052
+ // src/commands/passwords.ts
1053
+ var PASSWORD_COMMANDS = {
1054
+ /** List items (no secrets) within the extension's tag scope */
1055
+ list: "extension_password_list",
1056
+ /** Read full item including secrets, by id */
1057
+ read: "extension_password_read",
1058
+ /** Create item — must include >=1 tag in scope */
1059
+ create: "extension_password_create",
1060
+ /** Update item — keeps >=1 tag in scope */
1061
+ update: "extension_password_update",
1062
+ /** Delete item — must be in scope */
1063
+ delete: "extension_password_delete"
1064
+ };
1065
+
1066
+ // src/commands/mail.ts
1067
+ var MAIL_COMMANDS = {
1068
+ /** LIST mailboxes + optional STATUS counts */
1069
+ listMailboxes: "extension_mail_list_mailboxes",
1070
+ /** Lightweight envelope fetch for list views */
1071
+ fetchEnvelopes: "extension_mail_fetch_envelopes",
1072
+ /** Full message fetch (envelope + body + attachment metadata) */
1073
+ fetchMessage: "extension_mail_fetch_message",
1074
+ /** Set or unset IMAP flags on a UID set */
1075
+ setFlags: "extension_mail_set_flags",
1076
+ /** MOVE messages between mailboxes (COPY+EXPUNGE fallback) */
1077
+ moveMessages: "extension_mail_move_messages",
1078
+ /** APPEND a base64-encoded RFC822 message into a mailbox */
1079
+ appendMessage: "extension_mail_append_message",
1080
+ /** SMTP send. Returns the assigned Message-ID. */
1081
+ sendMessage: "extension_mail_send_message",
1082
+ /** Build RFC822 bytes without sending (for Drafts via APPEND) */
1083
+ buildRfc822: "extension_mail_build_rfc822"
1084
+ };
1085
+
1035
1086
  // src/commands/index.ts
1036
1087
  var TAURI_COMMANDS = {
1037
1088
  database: DATABASE_COMMANDS,
@@ -1044,7 +1095,9 @@ var TAURI_COMMANDS = {
1044
1095
  remoteStorage: REMOTE_STORAGE_COMMANDS,
1045
1096
  localsend: LOCALSEND_COMMANDS,
1046
1097
  spaces: SPACE_COMMANDS,
1047
- shell: SHELL_COMMANDS
1098
+ shell: SHELL_COMMANDS,
1099
+ passwords: PASSWORD_COMMANDS,
1100
+ mail: MAIL_COMMANDS
1048
1101
  };
1049
1102
 
1050
1103
  // src/api/storage.ts
@@ -1070,6 +1123,12 @@ var StorageAPI = class {
1070
1123
  };
1071
1124
 
1072
1125
  // src/api/database.ts
1126
+ function quoteIdent(identifier) {
1127
+ if (identifier.startsWith('"') && identifier.endsWith('"')) {
1128
+ return identifier;
1129
+ }
1130
+ return `"${identifier.replace(/"/g, '""')}"`;
1131
+ }
1073
1132
  var DatabaseAPI = class {
1074
1133
  constructor(client) {
1075
1134
  this.client = client;
@@ -1100,11 +1159,11 @@ var DatabaseAPI = class {
1100
1159
  });
1101
1160
  }
1102
1161
  async createTable(tableName, columns) {
1103
- const query = `CREATE TABLE IF NOT EXISTS ${tableName} (${columns})`;
1162
+ const query = `CREATE TABLE IF NOT EXISTS ${quoteIdent(tableName)} (${columns})`;
1104
1163
  await this.execute(query);
1105
1164
  }
1106
1165
  async dropTable(tableName) {
1107
- const query = `DROP TABLE IF EXISTS ${tableName}`;
1166
+ const query = `DROP TABLE IF EXISTS ${quoteIdent(tableName)}`;
1108
1167
  await this.execute(query);
1109
1168
  }
1110
1169
  /**
@@ -1133,18 +1192,17 @@ var DatabaseAPI = class {
1133
1192
  async insert(tableName, data) {
1134
1193
  const keys = Object.keys(data);
1135
1194
  const values = Object.values(data);
1195
+ const quotedCols = keys.map(quoteIdent).join(", ");
1136
1196
  const placeholders = keys.map(() => "?").join(", ");
1137
- const query = `INSERT INTO ${tableName} (${keys.join(
1138
- ", "
1139
- )}) VALUES (${placeholders})`;
1197
+ const query = `INSERT INTO ${quoteIdent(tableName)} (${quotedCols}) VALUES (${placeholders})`;
1140
1198
  const result = await this.execute(query, values);
1141
1199
  return result.lastInsertId ?? -1;
1142
1200
  }
1143
1201
  async update(tableName, data, where, whereParams) {
1144
1202
  const keys = Object.keys(data);
1145
1203
  const values = Object.values(data);
1146
- const setClause = keys.map((key) => `${key} = ?`).join(", ");
1147
- const query = `UPDATE ${tableName} SET ${setClause} WHERE ${where}`;
1204
+ const setClause = keys.map((key) => `${quoteIdent(key)} = ?`).join(", ");
1205
+ const query = `UPDATE ${quoteIdent(tableName)} SET ${setClause} WHERE ${where}`;
1148
1206
  const result = await this.execute(query, [
1149
1207
  ...values,
1150
1208
  ...whereParams || []
@@ -1152,12 +1210,12 @@ var DatabaseAPI = class {
1152
1210
  return result.rowsAffected;
1153
1211
  }
1154
1212
  async delete(tableName, where, whereParams) {
1155
- const query = `DELETE FROM ${tableName} WHERE ${where}`;
1213
+ const query = `DELETE FROM ${quoteIdent(tableName)} WHERE ${where}`;
1156
1214
  const result = await this.execute(query, whereParams);
1157
1215
  return result.rowsAffected;
1158
1216
  }
1159
1217
  async count(tableName, where, whereParams) {
1160
- const query = where ? `SELECT COUNT(*) as count FROM ${tableName} WHERE ${where}` : `SELECT COUNT(*) as count FROM ${tableName}`;
1218
+ const query = where ? `SELECT COUNT(*) as count FROM ${quoteIdent(tableName)} WHERE ${where}` : `SELECT COUNT(*) as count FROM ${quoteIdent(tableName)}`;
1161
1219
  const result = await this.queryOne(query, whereParams);
1162
1220
  return result?.count ?? 0;
1163
1221
  }
@@ -1988,6 +2046,140 @@ var ShellAPI = class {
1988
2046
  }
1989
2047
  };
1990
2048
 
2049
+ // src/api/passwords.ts
2050
+ var PasswordsAPI = class {
2051
+ constructor(client) {
2052
+ this.client = client;
2053
+ }
2054
+ /** List items in scope — summaries only, no secrets. */
2055
+ async listAsync() {
2056
+ return this.client.request(
2057
+ PASSWORD_COMMANDS.list,
2058
+ {}
2059
+ );
2060
+ }
2061
+ /** Read a single item by id with full secrets. */
2062
+ async readAsync(itemId) {
2063
+ return this.client.request(PASSWORD_COMMANDS.read, {
2064
+ itemId
2065
+ });
2066
+ }
2067
+ /**
2068
+ * Create a new password item. `input.tags` must contain at least one
2069
+ * tag within the extension's permission scope, otherwise the write
2070
+ * is rejected as a security violation.
2071
+ *
2072
+ * Returns the new item id.
2073
+ */
2074
+ async createAsync(input) {
2075
+ return this.client.request(PASSWORD_COMMANDS.create, { input });
2076
+ }
2077
+ /**
2078
+ * Update an existing item. The item must already be in scope, and
2079
+ * the new tag set must keep at least one tag in scope (extensions
2080
+ * cannot orphan an item out of their own reach).
2081
+ */
2082
+ async updateAsync(itemId, input) {
2083
+ return this.client.request(PASSWORD_COMMANDS.update, {
2084
+ itemId,
2085
+ input
2086
+ });
2087
+ }
2088
+ /** Delete an item by id. Item must be in scope. */
2089
+ async deleteAsync(itemId) {
2090
+ return this.client.request(PASSWORD_COMMANDS.delete, { itemId });
2091
+ }
2092
+ };
2093
+
2094
+ // src/api/mail.ts
2095
+ var MailAPI = class {
2096
+ constructor(client) {
2097
+ this.client = client;
2098
+ }
2099
+ /**
2100
+ * LIST mailboxes for an IMAP account. Pass `includeStatus=true` for
2101
+ * EXISTS/UNSEEN/UIDVALIDITY/UIDNEXT per box (one extra round-trip
2102
+ * per mailbox — fine for typical accounts, expensive for large
2103
+ * trees).
2104
+ */
2105
+ async listMailboxesAsync(imap, options = {}) {
2106
+ return this.client.request(MAIL_COMMANDS.listMailboxes, {
2107
+ imap,
2108
+ reference: options.reference,
2109
+ pattern: options.pattern,
2110
+ includeStatus: options.includeStatus
2111
+ });
2112
+ }
2113
+ /** Fetch lightweight envelopes for a mailbox + range (for list views). */
2114
+ async fetchEnvelopesAsync(imap, mailbox, range) {
2115
+ return this.client.request(
2116
+ MAIL_COMMANDS.fetchEnvelopes,
2117
+ { imap, mailbox, range }
2118
+ );
2119
+ }
2120
+ /** Fetch a full message (envelope + body + attachment metadata) by UID. */
2121
+ async fetchMessageAsync(imap, mailbox, uid) {
2122
+ return this.client.request(MAIL_COMMANDS.fetchMessage, {
2123
+ imap,
2124
+ mailbox,
2125
+ uid
2126
+ });
2127
+ }
2128
+ /**
2129
+ * Set or unset IMAP flags. Use `flags=["\\Seen"]` + `add=true` to
2130
+ * mark messages as read; `add=false` removes the flag(s).
2131
+ */
2132
+ async setFlagsAsync(imap, mailbox, uids, flags, add) {
2133
+ return this.client.request(MAIL_COMMANDS.setFlags, {
2134
+ imap,
2135
+ mailbox,
2136
+ uids,
2137
+ flags,
2138
+ add
2139
+ });
2140
+ }
2141
+ /** Move messages between mailboxes. Falls back to COPY+EXPUNGE on servers without MOVE. */
2142
+ async moveMessagesAsync(imap, sourceMailbox, destinationMailbox, uids) {
2143
+ return this.client.request(MAIL_COMMANDS.moveMessages, {
2144
+ imap,
2145
+ sourceMailbox,
2146
+ destinationMailbox,
2147
+ uids
2148
+ });
2149
+ }
2150
+ /**
2151
+ * APPEND a base64-encoded RFC822 message into a mailbox. Combine
2152
+ * with `buildRfc822Async` to save drafts, or with the bytes returned
2153
+ * after `sendMessageAsync` to mirror the sent copy into "Sent".
2154
+ */
2155
+ async appendMessageAsync(imap, mailbox, rfc822Base64, flags) {
2156
+ return this.client.request(MAIL_COMMANDS.appendMessage, {
2157
+ imap,
2158
+ mailbox,
2159
+ rfc822Base64,
2160
+ flags
2161
+ });
2162
+ }
2163
+ /** Send a message via SMTP. Returns the assigned Message-ID (no angle brackets). */
2164
+ async sendMessageAsync(smtp, message) {
2165
+ return this.client.request(MAIL_COMMANDS.sendMessage, {
2166
+ smtp,
2167
+ message
2168
+ });
2169
+ }
2170
+ /**
2171
+ * Build RFC822 bytes for a message without sending — useful for
2172
+ * drafts that get APPENDed to a "Drafts" folder. Permission-wise
2173
+ * this is a fetch operation (no SMTP host involved).
2174
+ */
2175
+ async buildRfc822Async(imapHost, message) {
2176
+ return this.client.request(MAIL_COMMANDS.buildRfc822, {
2177
+ imapHost,
2178
+ message
2179
+ });
2180
+ }
2181
+ };
2182
+
1991
2183
  // src/client/tableName.ts
1992
2184
  function validatePublicKey(publicKey) {
1993
2185
  if (!publicKey || typeof publicKey !== "string" || publicKey.trim() === "") {
@@ -2074,6 +2266,7 @@ function parseTableName(fullTableName) {
2074
2266
  }
2075
2267
 
2076
2268
  // src/client/init.ts
2269
+ var PORT_HANDSHAKE_TIMEOUT_MS = 1e4;
2077
2270
  function isInIframe() {
2078
2271
  return window.self !== window.top;
2079
2272
  }
@@ -2330,17 +2523,20 @@ async function initIframeMode(ctx, log, messageHandler, request) {
2330
2523
  if (!isInIframe()) {
2331
2524
  throw new HaexVaultSdkError("NOT_IN_IFRAME" /* NOT_IN_IFRAME */, "errors.not_in_iframe");
2332
2525
  }
2526
+ const port = await waitForHostPortAsync(log);
2333
2527
  ctx.handlers.messageHandler = messageHandler;
2334
- window.addEventListener("message", messageHandler);
2528
+ port.addEventListener("message", messageHandler);
2529
+ port.start();
2530
+ port.postMessage({ type: HAEXSPACE_MESSAGE_TYPES.PORT_READY });
2335
2531
  ctx.state.isNativeWindow = false;
2336
2532
  ctx.state.initialized = true;
2337
- log("HaexVault SDK initialized in iframe mode");
2533
+ log("HaexVault SDK initialized in iframe mode (MessagePort transport)");
2338
2534
  if (ctx.config.manifest) {
2339
2535
  ctx.state.extensionInfo = {
2340
2536
  publicKey: ctx.config.manifest.publicKey,
2341
2537
  name: ctx.config.manifest.name,
2342
2538
  version: ctx.config.manifest.version,
2343
- displayName: ctx.config.manifest.name
2539
+ displayName: ctx.config.manifest.displayName ?? ctx.config.manifest.name
2344
2540
  };
2345
2541
  log("Extension info loaded from manifest:", ctx.state.extensionInfo);
2346
2542
  }
@@ -2348,7 +2544,42 @@ async function initIframeMode(ctx, log, messageHandler, request) {
2348
2544
  const context = await request(EXTENSION_COMMANDS.getContext);
2349
2545
  ctx.state.context = context;
2350
2546
  log("Application context received:", context);
2351
- return { context };
2547
+ return { context, port };
2548
+ }
2549
+ function waitForHostPortAsync(log) {
2550
+ return new Promise((resolve, reject) => {
2551
+ let settled = false;
2552
+ const cleanup = () => {
2553
+ window.removeEventListener("message", handler);
2554
+ };
2555
+ const timeoutId = setTimeout(() => {
2556
+ if (settled) return;
2557
+ settled = true;
2558
+ cleanup();
2559
+ reject(
2560
+ new HaexVaultSdkError(
2561
+ "TIMEOUT" /* TIMEOUT */,
2562
+ "errors.port_handshake_timeout",
2563
+ { timeout: PORT_HANDSHAKE_TIMEOUT_MS }
2564
+ )
2565
+ );
2566
+ }, PORT_HANDSHAKE_TIMEOUT_MS);
2567
+ const handler = (event) => {
2568
+ const type = event.data?.type;
2569
+ if (type !== HAEXSPACE_MESSAGE_TYPES.PORT_INIT) return;
2570
+ const port = event.ports[0];
2571
+ if (!port) {
2572
+ log("PORT_INIT received but event.ports is empty \u2014 ignoring");
2573
+ return;
2574
+ }
2575
+ if (settled) return;
2576
+ settled = true;
2577
+ clearTimeout(timeoutId);
2578
+ cleanup();
2579
+ resolve(port);
2580
+ };
2581
+ window.addEventListener("message", handler);
2582
+ });
2352
2583
  }
2353
2584
  function sendDebugInfo(config) {
2354
2585
  if (!config.debug) return;
@@ -2372,7 +2603,15 @@ postMessage error: ${e}`);
2372
2603
  function generateRequestId(counter) {
2373
2604
  return `req_${counter}`;
2374
2605
  }
2375
- function sendPostMessage(method, params, requestId, config, extensionInfo, pendingRequests) {
2606
+ function sendPostMessage(method, params, requestId, config, extensionInfo, pendingRequests, port) {
2607
+ if (!port) {
2608
+ return Promise.reject(
2609
+ new HaexVaultSdkError(
2610
+ "EXTENSION_NOT_INITIALIZED" /* EXTENSION_NOT_INITIALIZED */,
2611
+ "errors.port_not_connected"
2612
+ )
2613
+ );
2614
+ }
2376
2615
  const request = {
2377
2616
  method,
2378
2617
  params,
@@ -2388,17 +2627,16 @@ function sendPostMessage(method, params, requestId, config, extensionInfo, pendi
2388
2627
  );
2389
2628
  }, config.timeout);
2390
2629
  pendingRequests.set(requestId, { resolve, reject, timeout });
2391
- const targetOrigin = "*";
2392
2630
  if (config.debug) {
2393
2631
  console.log("[SDK Debug] ========== Sending Request ==========");
2394
2632
  console.log("[SDK Debug] Request ID:", requestId);
2395
2633
  console.log("[SDK Debug] Method:", request.method);
2396
2634
  console.log("[SDK Debug] Params:", request.params);
2397
- console.log("[SDK Debug] Target origin:", targetOrigin);
2635
+ console.log("[SDK Debug] Transport: MessagePort");
2398
2636
  console.log("[SDK Debug] Extension info:", extensionInfo);
2399
2637
  console.log("[SDK Debug] ========================================");
2400
2638
  }
2401
- window.parent.postMessage({ id: requestId, ...request }, targetOrigin);
2639
+ port.postMessage({ id: requestId, ...request });
2402
2640
  });
2403
2641
  }
2404
2642
  async function sendInvoke(method, params, config, _log) {
@@ -2417,11 +2655,6 @@ function createMessageHandler(config, pendingRequests, extensionInfo, onEvent) {
2417
2655
  return (event) => {
2418
2656
  if (config.debug) {
2419
2657
  console.log("[SDK Debug] ========== Message Received ==========");
2420
- console.log("[SDK Debug] Event origin:", event.origin);
2421
- console.log(
2422
- "[SDK Debug] Event source:",
2423
- event.source === window.parent ? "parent window" : "unknown"
2424
- );
2425
2658
  console.log("[SDK Debug] Event data:", event.data);
2426
2659
  console.log("[SDK Debug] Extension info loaded:", !!extensionInfo());
2427
2660
  console.log(
@@ -2429,12 +2662,6 @@ function createMessageHandler(config, pendingRequests, extensionInfo, onEvent) {
2429
2662
  pendingRequests.size
2430
2663
  );
2431
2664
  }
2432
- if (event.source !== window.parent) {
2433
- if (config.debug) {
2434
- console.error("[SDK Debug] \u274C REJECTED: Message not from parent window!");
2435
- }
2436
- return;
2437
- }
2438
2665
  const data = event.data;
2439
2666
  if ("id" in data && pendingRequests.has(data.id)) {
2440
2667
  if (config.debug) {
@@ -2500,11 +2727,10 @@ function processEvent(event, log, eventListeners, onContextChanged, onExternalRe
2500
2727
  emitEvent(event, log, eventListeners);
2501
2728
  }
2502
2729
  function emitEvent(event, log, eventListeners) {
2503
- console.log("[HaexVault SDK] emitEvent called with:", event.type, event);
2504
- console.log("[HaexVault SDK] Registered event types:", Array.from(eventListeners.keys()));
2505
- log("Event received:", event);
2730
+ log("emitEvent called with:", event.type, event);
2731
+ log("Registered event types:", Array.from(eventListeners.keys()));
2506
2732
  const listeners = eventListeners.get(event.type);
2507
- console.log("[HaexVault SDK] Listeners for", event.type, ":", listeners?.size ?? 0);
2733
+ log("Listeners for", event.type, ":", listeners?.size ?? 0);
2508
2734
  if (listeners) {
2509
2735
  listeners.forEach((callback) => callback(event));
2510
2736
  }
@@ -2601,9 +2827,9 @@ function registerExternalHandler(action, handler, handlers, log) {
2601
2827
  };
2602
2828
  }
2603
2829
  async function handleExternalRequest(request, handlers, respond, log) {
2604
- console.log("[SDK Debug] handleExternalRequest called!");
2605
- console.log("[SDK Debug] Request:", JSON.stringify(request, null, 2));
2606
- console.log("[SDK Debug] Available handlers:", Array.from(handlers.keys()));
2830
+ log("handleExternalRequest called");
2831
+ log("Request:", request);
2832
+ log("Available handlers:", Array.from(handlers.keys()));
2607
2833
  log(`[ExternalRequest] Received request: ${request.action} from ${request.publicKey.substring(0, 20)}...`);
2608
2834
  const handler = handlers.get(request.action);
2609
2835
  if (!handler) {
@@ -2629,7 +2855,6 @@ async function handleExternalRequest(request, handlers, respond, log) {
2629
2855
  }
2630
2856
  }
2631
2857
  async function respondToExternalRequest(response, request) {
2632
- console.log("[SDK Debug] respondToExternalRequest called with:", JSON.stringify(response, null, 2));
2633
2858
  await request(EXTERNAL_BRIDGE_COMMANDS.respond, response);
2634
2859
  }
2635
2860
 
@@ -2656,6 +2881,13 @@ var HaexVaultSdk = class {
2656
2881
  this.reactiveSubscribers = /* @__PURE__ */ new Set();
2657
2882
  // Handlers
2658
2883
  this.messageHandler = null;
2884
+ /**
2885
+ * MessagePort obtained from the main window during iframe-mode handshake.
2886
+ * `null` until `initIframe()` completes successfully. Every outbound
2887
+ * request in iframe mode flows through this port; `sendPostMessage` rejects
2888
+ * if called before the handshake finishes.
2889
+ */
2890
+ this.hostPort = null;
2659
2891
  this.setupPromise = null;
2660
2892
  this.setupHook = null;
2661
2893
  // Public APIs
@@ -2680,11 +2912,17 @@ var HaexVaultSdk = class {
2680
2912
  this.localsend = new LocalSendAPI(this);
2681
2913
  this.spaces = new SpacesAPI(this);
2682
2914
  this.shell = new ShellAPI(this);
2915
+ this.passwords = new PasswordsAPI(this);
2916
+ this.mail = new MailAPI(this);
2683
2917
  installConsoleForwarding(this.config.debug);
2684
- this.readyPromise = new Promise((resolve) => {
2918
+ this.readyPromise = new Promise((resolve, reject) => {
2685
2919
  this.resolveReady = resolve;
2920
+ this.rejectReady = reject;
2921
+ });
2922
+ this.init().catch((error) => {
2923
+ this.log("Init failed:", error);
2924
+ this.rejectReady(error);
2686
2925
  });
2687
- this.init();
2688
2926
  }
2689
2927
  // ==========================================================================
2690
2928
  // Lifecycle
@@ -2712,9 +2950,14 @@ var HaexVaultSdk = class {
2712
2950
  return this.setupPromise;
2713
2951
  }
2714
2952
  destroy() {
2715
- if (this.messageHandler) {
2716
- window.removeEventListener("message", this.messageHandler);
2953
+ if (this.messageHandler && this.hostPort) {
2954
+ this.hostPort.removeEventListener("message", this.messageHandler);
2717
2955
  }
2956
+ if (this.hostPort) {
2957
+ this.hostPort.close();
2958
+ this.hostPort = null;
2959
+ }
2960
+ this.messageHandler = null;
2718
2961
  this.pendingRequests.forEach(({ timeout }) => clearTimeout(timeout));
2719
2962
  this.pendingRequests.clear();
2720
2963
  this.eventListeners.clear();
@@ -2829,7 +3072,15 @@ var HaexVaultSdk = class {
2829
3072
  return sendInvoke(method, paramsWithCredentials, this.config, this.log.bind(this));
2830
3073
  }
2831
3074
  const requestId = generateRequestId(++this.requestCounter);
2832
- return sendPostMessage(method, resolvedParams, requestId, this.config, this._extensionInfo, this.pendingRequests);
3075
+ return sendPostMessage(
3076
+ method,
3077
+ resolvedParams,
3078
+ requestId,
3079
+ this.config,
3080
+ this._extensionInfo,
3081
+ this.pendingRequests,
3082
+ this.hostPort
3083
+ );
2833
3084
  }
2834
3085
  // ==========================================================================
2835
3086
  // Private: Initialization
@@ -2896,7 +3147,7 @@ var HaexVaultSdk = class {
2896
3147
  () => this._extensionInfo,
2897
3148
  this.handleEvent.bind(this)
2898
3149
  );
2899
- const { context } = await initIframeMode(
3150
+ const { context, port } = await initIframeMode(
2900
3151
  {
2901
3152
  config: this.config,
2902
3153
  state: {
@@ -2928,12 +3179,13 @@ var HaexVaultSdk = class {
2928
3179
  this.messageHandler,
2929
3180
  this.request.bind(this)
2930
3181
  );
3182
+ this.hostPort = port;
2931
3183
  if (this.config.manifest) {
2932
3184
  this._extensionInfo = {
2933
3185
  publicKey: this.config.manifest.publicKey,
2934
3186
  name: this.config.manifest.name,
2935
3187
  version: this.config.manifest.version,
2936
- displayName: this.config.manifest.name
3188
+ displayName: this.config.manifest.displayName ?? this.config.manifest.name
2937
3189
  };
2938
3190
  this.notifySubscribersInternal();
2939
3191
  }
@@ -3603,6 +3855,10 @@ exports.HaexVaultSdkError = HaexVaultSdkError;
3603
3855
  exports.KnownPath = KnownPath;
3604
3856
  exports.LOCALSEND_EVENTS = LOCALSEND_EVENTS;
3605
3857
  exports.LocalSendAPI = LocalSendAPI;
3858
+ exports.MAIL_COMMANDS = MAIL_COMMANDS;
3859
+ exports.MailAPI = MailAPI;
3860
+ exports.PASSWORD_COMMANDS = PASSWORD_COMMANDS;
3861
+ exports.PasswordsAPI = PasswordsAPI;
3606
3862
  exports.PermissionErrorCode = PermissionErrorCode;
3607
3863
  exports.PermissionStatus = PermissionStatus;
3608
3864
  exports.PermissionsAPI = PermissionsAPI;