@haex-space/vault-sdk 3.2.6 → 3.2.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -709,7 +709,11 @@ var HAEXTENSION_EVENTS = {
709
709
  /** File system change detected (from native file watcher) */
710
710
  FILE_CHANGED: "filesync:file-changed",
711
711
  /** Tables have been updated via sync (CRDT pull from server) */
712
- SYNC_TABLES_UPDATED: "haextension:sync:tables-updated"
712
+ SYNC_TABLES_UPDATED: "haextension:sync:tables-updated",
713
+ /** A runtime permission prompt was resolved (granted/denied) by the user.
714
+ * The SDK uses this to auto-retry the original request; extensions may also
715
+ * subscribe via `client.on(HAEXTENSION_EVENTS.PERMISSION_RESOLVED, ...)`. */
716
+ PERMISSION_RESOLVED: "extension:permission-resolved"
713
717
  };
714
718
  var EXTERNAL_EVENTS = {
715
719
  /** External request from authorized client */
@@ -1482,7 +1486,10 @@ var WebAPI = class {
1482
1486
  } else if (options.body instanceof Blob) {
1483
1487
  bodyParam = await this.blobToBase64(options.body);
1484
1488
  } else {
1485
- bodyParam = options.body;
1489
+ const bytes = new TextEncoder().encode(options.body);
1490
+ bodyParam = this.arrayBufferToBase64(
1491
+ bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength)
1492
+ );
1486
1493
  }
1487
1494
  }
1488
1495
  const response = await this.client.request(WEB_COMMANDS.fetch, {
@@ -2295,6 +2302,21 @@ async function initNativeMode(ctx, log, onEvent, onContextChange) {
2295
2302
  await setupTauriEventListeners(ctx, log, onEvent, onContextChange);
2296
2303
  return { extensionInfo, context };
2297
2304
  }
2305
+ async function forwardEvent(listen, log, onEvent, listenOptions, eventName, shape) {
2306
+ try {
2307
+ await listen(eventName, (event) => {
2308
+ if (event.payload == null) {
2309
+ log(`Event '${eventName}' received with no payload`);
2310
+ return;
2311
+ }
2312
+ const extra = shape ? shape(event.payload) : { data: event.payload };
2313
+ onEvent({ type: eventName, timestamp: Date.now(), ...extra });
2314
+ }, listenOptions);
2315
+ log(`Listener registered: ${eventName}`);
2316
+ } catch (error) {
2317
+ log(`Failed to register listener '${eventName}':`, error);
2318
+ }
2319
+ }
2298
2320
  async function setupTauriEventListeners(ctx, log, onEvent, onContextChange) {
2299
2321
  const { listen } = getTauriEvent();
2300
2322
  const webviewLabel = getCurrentWebviewLabel();
@@ -2326,207 +2348,29 @@ async function setupTauriEventListeners(ctx, log, onEvent, onContextChange) {
2326
2348
  } catch (error) {
2327
2349
  log("Failed to setup context change listener:", error);
2328
2350
  }
2329
- try {
2330
- await listen(EXTERNAL_EVENTS.REQUEST, (event) => {
2331
- log("====== EXTERNAL REQUEST RECEIVED ======");
2332
- log("Event payload:", JSON.stringify(event.payload, null, 2));
2333
- if (event.payload) {
2334
- onEvent({
2335
- type: EXTERNAL_EVENTS.REQUEST,
2336
- data: event.payload,
2337
- timestamp: Date.now()
2338
- });
2339
- } else {
2340
- log("External request event has no payload!");
2341
- }
2342
- }, listenOptions);
2343
- log("External request listener registered successfully");
2344
- } catch (error) {
2345
- log("Failed to setup external request listener:", error);
2346
- }
2347
- try {
2348
- await listen(EXTERNAL_EVENTS.ACTION_REQUEST, (event) => {
2349
- log("====== AI ACTION REQUEST RECEIVED ======");
2350
- log("Payload:", JSON.stringify(event.payload));
2351
- if (event.payload) {
2352
- onEvent({
2353
- type: EXTERNAL_EVENTS.ACTION_REQUEST,
2354
- data: event.payload,
2355
- timestamp: Date.now()
2356
- });
2357
- } else {
2358
- log("AI action request event has no payload!");
2359
- }
2360
- }, listenOptions);
2361
- log("AI action request listener registered successfully");
2362
- } catch (error) {
2363
- log("Failed to setup AI action request listener:", error);
2364
- }
2365
- log("Registering file change listener for:", HAEXTENSION_EVENTS.FILE_CHANGED);
2366
- try {
2367
- await listen(HAEXTENSION_EVENTS.FILE_CHANGED, (event) => {
2368
- log("File change event received:", event.payload);
2369
- if (event.payload) {
2370
- const payload = event.payload;
2371
- onEvent({
2372
- type: HAEXTENSION_EVENTS.FILE_CHANGED,
2373
- ruleId: payload.ruleId,
2374
- changeType: payload.changeType,
2375
- path: payload.path,
2376
- timestamp: Date.now()
2377
- });
2378
- }
2379
- }, listenOptions);
2380
- log("File change listener registered successfully");
2381
- } catch (error) {
2382
- log("Failed to setup file change listener:", error);
2383
- }
2384
- log("Registering sync tables updated listener for:", HAEXTENSION_EVENTS.SYNC_TABLES_UPDATED);
2385
- try {
2386
- await listen(HAEXTENSION_EVENTS.SYNC_TABLES_UPDATED, (event) => {
2387
- log("Sync tables updated event received:", event.payload);
2388
- if (event.payload) {
2389
- const payload = event.payload;
2390
- onEvent({
2391
- type: HAEXTENSION_EVENTS.SYNC_TABLES_UPDATED,
2392
- data: { tables: payload.tables },
2393
- timestamp: Date.now()
2394
- });
2395
- }
2396
- }, listenOptions);
2397
- log("Sync tables updated listener registered successfully");
2398
- } catch (error) {
2399
- log("Failed to setup sync tables updated listener:", error);
2400
- }
2401
- log("Setting up LocalSend event listeners");
2402
- try {
2403
- await setupLocalSendEventListeners(log, onEvent, listenOptions);
2404
- log("LocalSend event listeners setup complete");
2405
- } catch (error) {
2406
- log("Failed to setup LocalSend event listeners:", error);
2407
- }
2408
- log("Setting up Shell event listeners");
2409
- try {
2410
- await listen(SHELL_EVENTS.OUTPUT, (event) => {
2411
- if (event.payload) {
2412
- const payload = event.payload;
2413
- onEvent({
2414
- type: SHELL_EVENTS.OUTPUT,
2415
- timestamp: Date.now(),
2416
- sessionId: payload.sessionId,
2417
- data: payload.data
2418
- });
2419
- }
2420
- }, listenOptions);
2421
- log("Shell output listener registered");
2422
- await listen(SHELL_EVENTS.EXIT, (event) => {
2423
- if (event.payload) {
2424
- const payload = event.payload;
2425
- onEvent({
2426
- type: SHELL_EVENTS.EXIT,
2427
- timestamp: Date.now(),
2428
- sessionId: payload.sessionId,
2429
- exitCode: payload.exitCode
2430
- });
2431
- }
2432
- }, listenOptions);
2433
- log("Shell exit listener registered");
2434
- } catch (error) {
2435
- log("Failed to setup Shell event listeners:", error);
2436
- }
2437
- }
2438
- async function setupLocalSendEventListeners(log, onEvent, listenOptions) {
2439
- const { listen } = getTauriEvent();
2440
- try {
2441
- await listen(LOCALSEND_EVENTS.deviceDiscovered, (event) => {
2442
- log("LocalSend device discovered:", event.payload);
2443
- if (event.payload) {
2444
- onEvent({
2445
- type: LOCALSEND_EVENTS.deviceDiscovered,
2446
- data: event.payload,
2447
- timestamp: Date.now()
2448
- });
2449
- }
2450
- }, listenOptions);
2451
- log("LocalSend device discovered listener registered");
2452
- } catch (error) {
2453
- log("Failed to setup LocalSend device discovered listener:", error);
2454
- }
2455
- try {
2456
- await listen(LOCALSEND_EVENTS.deviceLost, (event) => {
2457
- log("LocalSend device lost:", event.payload);
2458
- if (event.payload) {
2459
- onEvent({
2460
- type: LOCALSEND_EVENTS.deviceLost,
2461
- data: event.payload,
2462
- timestamp: Date.now()
2463
- });
2464
- }
2465
- }, listenOptions);
2466
- log("LocalSend device lost listener registered");
2467
- } catch (error) {
2468
- log("Failed to setup LocalSend device lost listener:", error);
2469
- }
2470
- try {
2471
- await listen(LOCALSEND_EVENTS.transferRequest, (event) => {
2472
- log("LocalSend transfer request:", event.payload);
2473
- if (event.payload) {
2474
- onEvent({
2475
- type: LOCALSEND_EVENTS.transferRequest,
2476
- data: event.payload,
2477
- timestamp: Date.now()
2478
- });
2479
- }
2480
- }, listenOptions);
2481
- log("LocalSend transfer request listener registered");
2482
- } catch (error) {
2483
- log("Failed to setup LocalSend transfer request listener:", error);
2484
- }
2485
- try {
2486
- await listen(LOCALSEND_EVENTS.transferProgress, (event) => {
2487
- log("LocalSend transfer progress event:", event);
2488
- if (event.payload) {
2489
- onEvent({
2490
- type: LOCALSEND_EVENTS.transferProgress,
2491
- data: event.payload,
2492
- timestamp: Date.now()
2493
- });
2494
- }
2495
- }, listenOptions);
2496
- log("LocalSend transfer progress listener registered");
2497
- } catch (error) {
2498
- log("Failed to setup LocalSend transfer progress listener:", error);
2499
- }
2500
- try {
2501
- await listen(LOCALSEND_EVENTS.transferComplete, (event) => {
2502
- log("LocalSend transfer complete:", event.payload);
2503
- if (event.payload) {
2504
- onEvent({
2505
- type: LOCALSEND_EVENTS.transferComplete,
2506
- data: event.payload,
2507
- timestamp: Date.now()
2508
- });
2509
- }
2510
- }, listenOptions);
2511
- log("LocalSend transfer complete listener registered");
2512
- } catch (error) {
2513
- log("Failed to setup LocalSend transfer complete listener:", error);
2514
- }
2515
- try {
2516
- await listen(LOCALSEND_EVENTS.transferFailed, (event) => {
2517
- log("LocalSend transfer failed:", event.payload);
2518
- if (event.payload) {
2519
- onEvent({
2520
- type: LOCALSEND_EVENTS.transferFailed,
2521
- data: event.payload,
2522
- timestamp: Date.now()
2523
- });
2524
- }
2525
- }, listenOptions);
2526
- log("LocalSend transfer failed listener registered");
2527
- } catch (error) {
2528
- log("Failed to setup LocalSend transfer failed listener:", error);
2529
- }
2351
+ for (const eventName of [
2352
+ HAEXTENSION_EVENTS.PERMISSION_RESOLVED,
2353
+ EXTERNAL_EVENTS.REQUEST,
2354
+ EXTERNAL_EVENTS.ACTION_REQUEST,
2355
+ ...Object.values(LOCALSEND_EVENTS)
2356
+ ]) {
2357
+ await forwardEvent(listen, log, onEvent, listenOptions, eventName);
2358
+ }
2359
+ await forwardEvent(listen, log, onEvent, listenOptions, HAEXTENSION_EVENTS.SYNC_TABLES_UPDATED, (payload) => ({
2360
+ data: { tables: payload.tables }
2361
+ }));
2362
+ await forwardEvent(listen, log, onEvent, listenOptions, HAEXTENSION_EVENTS.FILE_CHANGED, (payload) => {
2363
+ const { ruleId, changeType, path } = payload;
2364
+ return { ruleId, changeType, path };
2365
+ });
2366
+ await forwardEvent(listen, log, onEvent, listenOptions, SHELL_EVENTS.OUTPUT, (payload) => {
2367
+ const { sessionId, data } = payload;
2368
+ return { sessionId, data };
2369
+ });
2370
+ await forwardEvent(listen, log, onEvent, listenOptions, SHELL_EVENTS.EXIT, (payload) => {
2371
+ const { sessionId, exitCode } = payload;
2372
+ return { sessionId, exitCode };
2373
+ });
2530
2374
  }
2531
2375
  async function initIframeMode(ctx, log, messageHandler) {
2532
2376
  if (!isInIframe()) {
@@ -2870,6 +2714,68 @@ var AI_COMMANDS = {
2870
2714
  actionRespond: "ai_action_respond"
2871
2715
  };
2872
2716
 
2717
+ // src/client/permissionRetry.ts
2718
+ var PERMISSION_DECISION_TIMEOUT_MS = 5 * 60 * 1e3;
2719
+ var MAX_PERMISSION_RETRIES = 3;
2720
+ function permissionKey(resourceType, action, target) {
2721
+ return `${resourceType}:${action}:${target}`;
2722
+ }
2723
+ var PermissionWaiterRegistry = class {
2724
+ constructor() {
2725
+ this.waiters = /* @__PURE__ */ new Map();
2726
+ }
2727
+ /** Wait for a decision on `key`; resolves "timeout" if none arrives in time. */
2728
+ wait(key, timeoutMs = PERMISSION_DECISION_TIMEOUT_MS) {
2729
+ return new Promise((resolve) => {
2730
+ const set = this.waiters.get(key) ?? /* @__PURE__ */ new Set();
2731
+ this.waiters.set(key, set);
2732
+ const settle = (outcome) => {
2733
+ if (!set.has(callback)) return;
2734
+ set.delete(callback);
2735
+ if (set.size === 0) this.waiters.delete(key);
2736
+ clearTimeout(timer);
2737
+ resolve(outcome);
2738
+ };
2739
+ const callback = (outcome) => settle(outcome);
2740
+ const timer = setTimeout(() => settle("timeout"), timeoutMs);
2741
+ set.add(callback);
2742
+ });
2743
+ }
2744
+ /** Resolve everyone waiting on `key` with the user's decision. */
2745
+ resolve(key, decision) {
2746
+ const set = this.waiters.get(key);
2747
+ if (!set) return;
2748
+ for (const callback of [...set]) callback(decision);
2749
+ }
2750
+ };
2751
+ function toDeniedError(error) {
2752
+ return { ...error, code: 1002 /* DENIED */ };
2753
+ }
2754
+ async function withPermissionRetry(send, registry, log, timeoutMs = PERMISSION_DECISION_TIMEOUT_MS) {
2755
+ for (let attempt = 0; ; attempt++) {
2756
+ try {
2757
+ return await send();
2758
+ } catch (error) {
2759
+ if (!isPermissionPromptError(error) || attempt >= MAX_PERMISSION_RETRIES) {
2760
+ throw error;
2761
+ }
2762
+ const key = permissionKey(error.resourceType, error.action, error.target);
2763
+ log(`Permission prompt required for ${key} \u2014 waiting for user decision`);
2764
+ const outcome = await registry.wait(key, timeoutMs);
2765
+ if (outcome === "granted") {
2766
+ log(`Permission ${key} granted \u2014 retrying request`);
2767
+ continue;
2768
+ }
2769
+ if (outcome === "denied") {
2770
+ log(`Permission ${key} denied`);
2771
+ throw toDeniedError(error);
2772
+ }
2773
+ log(`Permission ${key} prompt timed out`);
2774
+ throw error;
2775
+ }
2776
+ }
2777
+ }
2778
+
2873
2779
  // src/client.ts
2874
2780
  var HaexVaultSdk = class {
2875
2781
  constructor(config = {}) {
@@ -2885,6 +2791,7 @@ var HaexVaultSdk = class {
2885
2791
  this.eventListeners = /* @__PURE__ */ new Map();
2886
2792
  this.externalRequestHandlers = /* @__PURE__ */ new Map();
2887
2793
  this.reactiveSubscribers = /* @__PURE__ */ new Set();
2794
+ this.permissionWaiters = new PermissionWaiterRegistry();
2888
2795
  // Handlers
2889
2796
  this.messageHandler = null;
2890
2797
  /**
@@ -2921,6 +2828,14 @@ var HaexVaultSdk = class {
2921
2828
  this.passwords = new PasswordsAPI(this);
2922
2829
  this.mail = new MailAPI(this);
2923
2830
  installConsoleForwarding(this.config.debug);
2831
+ this.on(HAEXTENSION_EVENTS.PERMISSION_RESOLVED, (event) => {
2832
+ const data = event.data;
2833
+ if (!data) return;
2834
+ this.permissionWaiters.resolve(
2835
+ permissionKey(data.resourceType, data.action, data.target),
2836
+ data.decision === "denied" ? "denied" : "granted"
2837
+ );
2838
+ });
2924
2839
  this.readyPromise = new Promise((resolve, reject) => {
2925
2840
  this.resolveReady = resolve;
2926
2841
  this.rejectReady = reject;
@@ -3069,24 +2984,27 @@ var HaexVaultSdk = class {
3069
2984
  // ==========================================================================
3070
2985
  async request(method, params) {
3071
2986
  const resolvedParams = params ?? {};
3072
- if (this.isNativeWindow && hasTauri()) {
3073
- const paramsWithCredentials = {
3074
- ...resolvedParams,
3075
- publicKey: this._extensionInfo?.publicKey,
3076
- name: this._extensionInfo?.name
3077
- };
3078
- return sendInvoke(method, paramsWithCredentials, this.config, this.log.bind(this));
3079
- }
3080
- const requestId = generateRequestId(++this.requestCounter);
3081
- return sendPostMessage(
3082
- method,
3083
- resolvedParams,
3084
- requestId,
3085
- this.config,
3086
- this._extensionInfo,
3087
- this.pendingRequests,
3088
- this.hostPort
3089
- );
2987
+ const send = () => {
2988
+ if (this.isNativeWindow && hasTauri()) {
2989
+ const paramsWithCredentials = {
2990
+ ...resolvedParams,
2991
+ publicKey: this._extensionInfo?.publicKey,
2992
+ name: this._extensionInfo?.name
2993
+ };
2994
+ return sendInvoke(method, paramsWithCredentials, this.config, this.log.bind(this));
2995
+ }
2996
+ const requestId = generateRequestId(++this.requestCounter);
2997
+ return sendPostMessage(
2998
+ method,
2999
+ resolvedParams,
3000
+ requestId,
3001
+ this.config,
3002
+ this._extensionInfo,
3003
+ this.pendingRequests,
3004
+ this.hostPort
3005
+ );
3006
+ };
3007
+ return withPermissionRetry(send, this.permissionWaiters, this.log.bind(this));
3090
3008
  }
3091
3009
  // ==========================================================================
3092
3010
  // Private: Initialization