@haex-space/vault-sdk 2.3.16 → 2.5.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.
@@ -9,9 +9,47 @@ var HAEXTENSION_EVENTS = {
9
9
  /** Context (theme, locale, platform) has changed */
10
10
  CONTEXT_CHANGED: "haextension:context:changed",
11
11
  /** Search request from HaexHub */
12
- SEARCH_REQUEST: "haextension:search:request",
13
- /** External request from authorized client (browser extension, CLI, server, etc.) */
14
- EXTERNAL_REQUEST: "haextension:external:request"
12
+ SEARCH_REQUEST: "haextension:search:request"
13
+ };
14
+
15
+ // src/types.ts
16
+ var DEFAULT_TIMEOUT = 3e4;
17
+ var TABLE_SEPARATOR = "__";
18
+ function getTableName(publicKey, extensionName, tableName) {
19
+ return `${publicKey}${TABLE_SEPARATOR}${extensionName}${TABLE_SEPARATOR}${tableName}`;
20
+ }
21
+ var HaexVaultSdkError = class extends Error {
22
+ constructor(code, messageKey, details) {
23
+ super(messageKey);
24
+ this.code = code;
25
+ this.messageKey = messageKey;
26
+ this.details = details;
27
+ this.name = "HaexVaultSdkError";
28
+ }
29
+ /**
30
+ * Get localized error message
31
+ * @param locale - Locale code (e.g., 'en', 'de')
32
+ * @param translations - Translation object
33
+ */
34
+ getLocalizedMessage(locale = "en", translations) {
35
+ if (!translations || !translations[locale]) {
36
+ return this.messageKey;
37
+ }
38
+ let message = translations[locale][this.messageKey] || this.messageKey;
39
+ if (this.details) {
40
+ Object.entries(this.details).forEach(([key, value]) => {
41
+ message = message.replace(`{${key}}`, String(value));
42
+ });
43
+ }
44
+ return message;
45
+ }
46
+ toJSON() {
47
+ return {
48
+ code: this.code,
49
+ message: this.messageKey,
50
+ details: this.details
51
+ };
52
+ }
15
53
  };
16
54
 
17
55
  // src/methods.ts
@@ -75,54 +113,6 @@ var HAEXTENSION_METHODS = {
75
113
  }
76
114
  };
77
115
 
78
- // src/messages.ts
79
- var HAEXSPACE_MESSAGE_TYPES = {
80
- /** Debug message for development/troubleshooting */
81
- DEBUG: "haexspace:debug",
82
- /** Console forwarding from extension iframe */
83
- CONSOLE_FORWARD: "console.forward"
84
- };
85
-
86
- // src/types.ts
87
- var DEFAULT_TIMEOUT = 3e4;
88
- var TABLE_SEPARATOR = "__";
89
- function getTableName(publicKey, extensionName, tableName) {
90
- return `${publicKey}${TABLE_SEPARATOR}${extensionName}${TABLE_SEPARATOR}${tableName}`;
91
- }
92
- var HaexHubError = class extends Error {
93
- constructor(code, messageKey, details) {
94
- super(messageKey);
95
- this.code = code;
96
- this.messageKey = messageKey;
97
- this.details = details;
98
- this.name = "HaexHubError";
99
- }
100
- /**
101
- * Get localized error message
102
- * @param locale - Locale code (e.g., 'en', 'de')
103
- * @param translations - Translation object
104
- */
105
- getLocalizedMessage(locale = "en", translations) {
106
- if (!translations || !translations[locale]) {
107
- return this.messageKey;
108
- }
109
- let message = translations[locale][this.messageKey] || this.messageKey;
110
- if (this.details) {
111
- Object.entries(this.details).forEach(([key, value]) => {
112
- message = message.replace(`{${key}}`, String(value));
113
- });
114
- }
115
- return message;
116
- }
117
- toJSON() {
118
- return {
119
- code: this.code,
120
- message: this.messageKey,
121
- details: this.details
122
- };
123
- }
124
- };
125
-
126
116
  // src/api/storage.ts
127
117
  var StorageAPI = class {
128
118
  constructor(client) {
@@ -666,6 +656,14 @@ var PermissionsAPI = class {
666
656
  }
667
657
  };
668
658
 
659
+ // src/messages.ts
660
+ var HAEXSPACE_MESSAGE_TYPES = {
661
+ /** Debug message for development/troubleshooting */
662
+ DEBUG: "haexspace:debug",
663
+ /** Console forwarding from extension iframe */
664
+ CONSOLE_FORWARD: "console.forward"
665
+ };
666
+
669
667
  // src/polyfills/consoleForwarding.ts
670
668
  var originalConsole = {
671
669
  log: console.log,
@@ -731,548 +729,543 @@ function installConsoleForwarding(debug = false) {
731
729
  interceptConsole("debug");
732
730
  console.log("[HaexSpace] Console forwarding installed");
733
731
  }
734
- var HaexVaultClient = class {
735
- constructor(config = {}) {
736
- this.pendingRequests = /* @__PURE__ */ new Map();
737
- this.eventListeners = /* @__PURE__ */ new Map();
738
- this.externalRequestHandlers = /* @__PURE__ */ new Map();
739
- this.messageHandler = null;
740
- this.initialized = false;
741
- this.requestCounter = 0;
742
- this._extensionInfo = null;
743
- this._context = null;
744
- this.reactiveSubscribers = /* @__PURE__ */ new Set();
745
- this.isNativeWindow = false;
746
- // Wird im Konstruktor initialisiert
747
- this.setupPromise = null;
748
- this.setupHook = null;
749
- this._setupCompleted = false;
750
- this.orm = null;
751
- this.config = {
752
- debug: config.debug ?? false,
753
- timeout: config.timeout ?? DEFAULT_TIMEOUT,
754
- manifest: config.manifest
755
- };
756
- this.storage = new StorageAPI(this);
757
- this.database = new DatabaseAPI(this);
758
- this.filesystem = new FilesystemAPI(this);
759
- this.web = new WebAPI(this);
760
- this.permissions = new PermissionsAPI(this);
761
- installConsoleForwarding(this.config.debug);
762
- this.readyPromise = new Promise((resolve) => {
763
- this.resolveReady = resolve;
764
- });
765
- this.init();
766
- }
767
- /**
768
- * Gibt ein Promise zurück, das aufgelöst wird, sobald der Client
769
- * initialisiert ist und Extension-Infos empfangen hat.
770
- */
771
- async ready() {
772
- return this.readyPromise;
732
+
733
+ // src/client/tableName.ts
734
+ function validatePublicKey(publicKey) {
735
+ if (!publicKey || typeof publicKey !== "string" || publicKey.trim() === "") {
736
+ throw new HaexVaultSdkError(
737
+ "INVALID_PUBLIC_KEY" /* INVALID_PUBLIC_KEY */,
738
+ "errors.invalid_public_key",
739
+ { publicKey }
740
+ );
773
741
  }
774
- /**
775
- * Gibt zurück, ob das Setup bereits abgeschlossen wurde.
776
- */
777
- get setupCompleted() {
778
- return this._setupCompleted;
742
+ }
743
+ function validateExtensionName(extensionName) {
744
+ if (!extensionName || !/^[a-z][a-z0-9-]*$/i.test(extensionName)) {
745
+ throw new HaexVaultSdkError(
746
+ "INVALID_EXTENSION_NAME" /* INVALID_EXTENSION_NAME */,
747
+ "errors.invalid_extension_name",
748
+ { extensionName }
749
+ );
779
750
  }
780
- /**
781
- * Registriert eine Setup-Funktion, die nach der Initialisierung ausgeführt wird.
782
- * Diese Funktion sollte für Aufgaben wie Tabellenerstellung, Migrationen, etc. verwendet werden.
783
- * @param setupFn Die Setup-Funktion, die ausgeführt werden soll
784
- */
785
- onSetup(setupFn) {
786
- if (this.setupHook) {
787
- throw new Error("Setup hook already registered");
788
- }
789
- this.setupHook = setupFn;
751
+ if (extensionName.includes(TABLE_SEPARATOR)) {
752
+ throw new HaexVaultSdkError(
753
+ "INVALID_EXTENSION_NAME" /* INVALID_EXTENSION_NAME */,
754
+ "errors.extension_name_contains_separator",
755
+ { extensionName, separator: TABLE_SEPARATOR }
756
+ );
790
757
  }
791
- /**
792
- * Gibt ein Promise zurück, das aufgelöst wird, sobald der Client vollständig eingerichtet ist.
793
- * Dies umfasst die Initialisierung UND das Setup (z.B. Tabellenerstellung).
794
- * Falls kein Setup-Hook registriert wurde, entspricht dies ready().
795
- */
796
- async setupComplete() {
797
- await this.readyPromise;
798
- if (!this.setupHook || this.setupCompleted) {
799
- return;
800
- }
801
- if (!this.setupPromise) {
802
- this.setupPromise = this.runSetupAsync();
803
- }
804
- return this.setupPromise;
758
+ }
759
+ function validateTableName(tableName) {
760
+ if (!tableName || typeof tableName !== "string") {
761
+ throw new HaexVaultSdkError(
762
+ "INVALID_TABLE_NAME" /* INVALID_TABLE_NAME */,
763
+ "errors.table_name_empty"
764
+ );
805
765
  }
806
- async runSetupAsync() {
807
- if (!this.setupHook) return;
808
- try {
809
- this.log("[HaexSpace] Running setup hook...");
810
- await this.setupHook();
811
- this._setupCompleted = true;
812
- this.log("[HaexSpace] Setup completed successfully");
813
- this.notifySubscribers();
814
- } catch (error) {
815
- this.log("[HaexSpace] Setup failed:", error);
816
- throw error;
817
- }
766
+ if (tableName.includes(TABLE_SEPARATOR)) {
767
+ throw new HaexVaultSdkError(
768
+ "INVALID_TABLE_NAME" /* INVALID_TABLE_NAME */,
769
+ "errors.table_name_contains_separator",
770
+ { tableName, separator: TABLE_SEPARATOR }
771
+ );
818
772
  }
819
- /**
820
- * Initialisiert die Drizzle-Datenbankinstanz.
821
- * Muss nach der Definition des Schemas aufgerufen werden.
822
- * @param schema Das Drizzle-Schemaobjekt (mit bereits geprefixten Tabellennamen).
823
- * @returns Die typsichere Drizzle-Datenbankinstanz.
824
- */
825
- initializeDatabase(schema) {
826
- if (!this._extensionInfo) {
827
- throw new HaexHubError(
828
- "EXTENSION_INFO_UNAVAILABLE" /* EXTENSION_INFO_UNAVAILABLE */,
829
- "errors.client_not_ready"
830
- );
831
- }
832
- const dbInstance = drizzle(
833
- async (sql, params, method) => {
834
- try {
835
- if (method === "run" || method === "all") {
836
- const result2 = await this.request(
837
- HAEXTENSION_METHODS.database.execute,
838
- {
839
- query: sql,
840
- params
841
- }
842
- );
843
- if (method === "all") {
844
- return { rows: result2.rows || [] };
845
- }
846
- if (result2.rows && Array.isArray(result2.rows) && result2.rows.length > 0) {
847
- return { rows: result2.rows };
848
- }
849
- return result2;
850
- }
851
- const result = await this.request(HAEXTENSION_METHODS.database.query, {
852
- query: sql,
853
- params
854
- });
855
- const rows = result.rows;
856
- if (method === "get") {
857
- return { rows: rows.length > 0 ? rows.at(0) : void 0 };
858
- }
859
- return { rows };
860
- } catch (error) {
861
- this.log("Database operation failed:", error);
862
- throw error;
863
- }
864
- },
865
- {
866
- schema,
867
- logger: false
868
- }
773
+ if (!/^[a-z][a-z0-9-_]*$/i.test(tableName)) {
774
+ throw new HaexVaultSdkError(
775
+ "INVALID_TABLE_NAME" /* INVALID_TABLE_NAME */,
776
+ "errors.table_name_format",
777
+ { tableName }
869
778
  );
870
- this.orm = dbInstance;
871
- return dbInstance;
872
779
  }
873
- get extensionInfo() {
874
- return this._extensionInfo;
780
+ }
781
+ function getExtensionTableName(extensionInfo, tableName) {
782
+ if (!extensionInfo) {
783
+ throw new HaexVaultSdkError(
784
+ "EXTENSION_INFO_UNAVAILABLE" /* EXTENSION_INFO_UNAVAILABLE */,
785
+ "errors.extension_info_unavailable"
786
+ );
875
787
  }
876
- get context() {
877
- return this._context;
788
+ validateTableName(tableName);
789
+ const { publicKey, name } = extensionInfo;
790
+ return `"${getTableName(publicKey, name, tableName)}"`;
791
+ }
792
+ function getDependencyTableName(publicKey, extensionName, tableName) {
793
+ validatePublicKey(publicKey);
794
+ validateExtensionName(extensionName);
795
+ validateTableName(tableName);
796
+ return `"${getTableName(publicKey, extensionName, tableName)}"`;
797
+ }
798
+ function parseTableName(fullTableName) {
799
+ let cleanTableName = fullTableName;
800
+ if (cleanTableName.startsWith('"') && cleanTableName.endsWith('"')) {
801
+ cleanTableName = cleanTableName.slice(1, -1);
878
802
  }
879
- subscribe(callback) {
880
- this.reactiveSubscribers.add(callback);
881
- return () => {
882
- this.reactiveSubscribers.delete(callback);
883
- };
803
+ const parts = cleanTableName.split(TABLE_SEPARATOR);
804
+ if (parts.length !== 3) {
805
+ return null;
884
806
  }
885
- notifySubscribers() {
886
- this.reactiveSubscribers.forEach((callback) => callback());
807
+ const [publicKey, extensionName, tableName] = parts;
808
+ if (!publicKey || !extensionName || !tableName) {
809
+ return null;
887
810
  }
888
- async getDependencies() {
889
- return this.request("extensions.getDependencies");
811
+ return {
812
+ publicKey,
813
+ extensionName,
814
+ tableName
815
+ };
816
+ }
817
+
818
+ // src/client/init.ts
819
+ function isInIframe() {
820
+ return window.self !== window.top;
821
+ }
822
+ function hasTauri() {
823
+ return typeof window.__TAURI__ !== "undefined";
824
+ }
825
+ function getTauriCore() {
826
+ return window.__TAURI__.core;
827
+ }
828
+ function getTauriEvent() {
829
+ return window.__TAURI__.event;
830
+ }
831
+ async function initNativeMode(ctx, log, onEvent, onContextChange) {
832
+ const { invoke } = getTauriCore();
833
+ const extensionInfo = await invoke("webview_extension_get_info");
834
+ const context = await invoke("webview_extension_context_get");
835
+ ctx.state.isNativeWindow = true;
836
+ ctx.state.initialized = true;
837
+ ctx.state.extensionInfo = extensionInfo;
838
+ ctx.state.context = context;
839
+ log("HaexVault SDK initialized in native WebViewWindow mode");
840
+ log("Extension info:", extensionInfo);
841
+ log("Application context:", context);
842
+ await setupTauriEventListeners(ctx, log, onEvent, onContextChange);
843
+ return { extensionInfo, context };
844
+ }
845
+ async function setupTauriEventListeners(ctx, log, onEvent, onContextChange) {
846
+ const { listen } = getTauriEvent();
847
+ console.log("[HaexVault SDK] Setting up Tauri event listener for:", HAEXTENSION_EVENTS.CONTEXT_CHANGED);
848
+ try {
849
+ await listen(HAEXTENSION_EVENTS.CONTEXT_CHANGED, (event) => {
850
+ console.log("[HaexVault SDK] Received Tauri event:", HAEXTENSION_EVENTS.CONTEXT_CHANGED, event);
851
+ log("Received context change event:", event);
852
+ const payload = event.payload;
853
+ if (payload?.context) {
854
+ ctx.state.context = payload.context;
855
+ console.log("[HaexVault SDK] Updated context to:", ctx.state.context);
856
+ onContextChange(payload.context);
857
+ onEvent({
858
+ type: HAEXTENSION_EVENTS.CONTEXT_CHANGED,
859
+ data: { context: ctx.state.context },
860
+ timestamp: Date.now()
861
+ });
862
+ } else {
863
+ console.warn("[HaexVault SDK] Event received but no context in payload:", event);
864
+ }
865
+ });
866
+ console.log("[HaexVault SDK] Context change listener registered successfully");
867
+ } catch (error) {
868
+ console.error("[HaexVault SDK] Failed to setup context change listener:", error);
869
+ log("Failed to setup context change listener:", error);
870
+ }
871
+ try {
872
+ await listen(HAEXTENSION_EVENTS.EXTERNAL_REQUEST, (event) => {
873
+ log("Received external request event:", event);
874
+ if (event.payload) {
875
+ onEvent({
876
+ type: HAEXTENSION_EVENTS.EXTERNAL_REQUEST,
877
+ data: event.payload,
878
+ timestamp: Date.now()
879
+ });
880
+ }
881
+ });
882
+ console.log("[HaexVault SDK] External request listener registered successfully");
883
+ } catch (error) {
884
+ console.error("[HaexVault SDK] Failed to setup external request listener:", error);
885
+ log("Failed to setup external request listener:", error);
890
886
  }
891
- getTableName(tableName) {
892
- if (!this._extensionInfo) {
893
- throw new HaexHubError(
894
- "EXTENSION_INFO_UNAVAILABLE" /* EXTENSION_INFO_UNAVAILABLE */,
895
- "errors.extension_info_unavailable"
896
- );
897
- }
898
- this.validateTableName(tableName);
899
- const { publicKey, name } = this._extensionInfo;
900
- return `"${getTableName(publicKey, name, tableName)}"`;
887
+ }
888
+ async function initIframeMode(ctx, log, messageHandler, request) {
889
+ if (!isInIframe()) {
890
+ throw new HaexVaultSdkError("NOT_IN_IFRAME" /* NOT_IN_IFRAME */, "errors.not_in_iframe");
891
+ }
892
+ ctx.handlers.messageHandler = messageHandler;
893
+ window.addEventListener("message", messageHandler);
894
+ ctx.state.isNativeWindow = false;
895
+ ctx.state.initialized = true;
896
+ log("HaexVault SDK initialized in iframe mode");
897
+ if (ctx.config.manifest) {
898
+ ctx.state.extensionInfo = {
899
+ publicKey: ctx.config.manifest.publicKey,
900
+ name: ctx.config.manifest.name,
901
+ version: ctx.config.manifest.version,
902
+ displayName: ctx.config.manifest.name
903
+ };
904
+ log("Extension info loaded from manifest:", ctx.state.extensionInfo);
901
905
  }
902
- getDependencyTableName(publicKey, extensionName, tableName) {
903
- this.validatePublicKey(publicKey);
904
- this.validateExtensionName(extensionName);
905
- this.validateTableName(tableName);
906
- return `"${getTableName(publicKey, extensionName, tableName)}"`;
906
+ sendDebugInfo(ctx.config);
907
+ const context = await request(HAEXTENSION_METHODS.context.get);
908
+ ctx.state.context = context;
909
+ log("Application context received:", context);
910
+ return { context };
911
+ }
912
+ function sendDebugInfo(config) {
913
+ if (!config.debug) return;
914
+ if (typeof window === "undefined" || !window.parent) return;
915
+ const debugInfo = `SDK Debug:
916
+ window.parent exists: ${!!window.parent}
917
+ window.parent === window: ${window.parent === window}
918
+ window.self === window.top: ${window.self === window.top}`;
919
+ try {
920
+ window.parent.postMessage({
921
+ type: HAEXSPACE_MESSAGE_TYPES.DEBUG,
922
+ data: debugInfo
923
+ }, "*");
924
+ } catch (e) {
925
+ alert(debugInfo + `
926
+ postMessage error: ${e}`);
907
927
  }
908
- parseTableName(fullTableName) {
909
- let cleanTableName = fullTableName;
910
- if (cleanTableName.startsWith('"') && cleanTableName.endsWith('"')) {
911
- cleanTableName = cleanTableName.slice(1, -1);
912
- }
913
- const parts = cleanTableName.split(TABLE_SEPARATOR);
914
- if (parts.length !== 3) {
915
- return null;
916
- }
917
- const [publicKey, extensionName, tableName] = parts;
918
- if (!publicKey || !extensionName || !tableName) {
919
- return null;
920
- }
921
- return {
922
- publicKey,
923
- extensionName,
924
- tableName
925
- };
928
+ }
929
+
930
+ // src/commands.ts
931
+ var TAURI_COMMANDS = {
932
+ database: {
933
+ query: "webview_extension_db_query",
934
+ execute: "webview_extension_db_execute",
935
+ registerMigrations: "webview_extension_db_register_migrations"
936
+ },
937
+ permissions: {
938
+ checkWeb: "webview_extension_check_web_permission",
939
+ checkDatabase: "webview_extension_check_database_permission",
940
+ checkFilesystem: "webview_extension_check_filesystem_permission"
941
+ },
942
+ web: {
943
+ open: "webview_extension_web_open",
944
+ fetch: "webview_extension_web_request"
945
+ },
946
+ filesystem: {
947
+ saveFile: "webview_extension_fs_save_file",
948
+ openFile: "webview_extension_fs_open_file",
949
+ showImage: "webview_extension_fs_show_image"
950
+ },
951
+ external: {
952
+ // Response handling (called by extensions)
953
+ respond: "external_respond"},
954
+ filesync: {
955
+ // Spaces
956
+ listSpaces: "filesync_list_spaces",
957
+ createSpace: "filesync_create_space",
958
+ deleteSpace: "filesync_delete_space",
959
+ // Files
960
+ listFiles: "filesync_list_files",
961
+ getFile: "filesync_get_file",
962
+ uploadFile: "filesync_upload_file",
963
+ downloadFile: "filesync_download_file",
964
+ deleteFile: "filesync_delete_file",
965
+ // Backends
966
+ listBackends: "filesync_list_backends",
967
+ addBackend: "filesync_add_backend",
968
+ removeBackend: "filesync_remove_backend",
969
+ testBackend: "filesync_test_backend",
970
+ // Sync Rules
971
+ listSyncRules: "filesync_list_sync_rules",
972
+ addSyncRule: "filesync_add_sync_rule",
973
+ removeSyncRule: "filesync_remove_sync_rule",
974
+ // Sync Operations
975
+ getSyncStatus: "filesync_get_sync_status",
976
+ triggerSync: "filesync_trigger_sync",
977
+ pauseSync: "filesync_pause_sync",
978
+ resumeSync: "filesync_resume_sync",
979
+ // Conflict Resolution
980
+ resolveConflict: "filesync_resolve_conflict",
981
+ // UI Helpers
982
+ selectFolder: "filesync_select_folder"
926
983
  }
927
- /**
928
- * Execute a raw SQL query (SELECT)
929
- * Returns rows as an array of objects
930
- */
931
- async query(sql, params = []) {
932
- const result = await this.request(
933
- HAEXTENSION_METHODS.database.query,
934
- { query: sql, params }
935
- );
936
- if (this.config.debug) {
937
- console.log("[SDK query()] Raw result:", JSON.stringify(result, null, 2));
938
- }
939
- return result.rows;
984
+ };
985
+
986
+ // src/transport/handlers/database.ts
987
+ var databaseHandlers = {
988
+ [HAEXTENSION_METHODS.database.query]: {
989
+ command: TAURI_COMMANDS.database.query,
990
+ args: (p) => ({
991
+ query: p.query,
992
+ params: p.params || []
993
+ })
994
+ },
995
+ [HAEXTENSION_METHODS.database.execute]: {
996
+ command: TAURI_COMMANDS.database.execute,
997
+ args: (p) => ({
998
+ query: p.query,
999
+ params: p.params || []
1000
+ })
1001
+ },
1002
+ [HAEXTENSION_METHODS.database.registerMigrations]: {
1003
+ command: TAURI_COMMANDS.database.registerMigrations,
1004
+ args: (p) => ({
1005
+ extensionVersion: p.extensionVersion,
1006
+ migrations: p.migrations
1007
+ })
940
1008
  }
941
- /**
942
- * Alias for query() - more intuitive for SELECT statements
943
- */
944
- async select(sql, params = []) {
945
- return this.query(sql, params);
1009
+ };
1010
+
1011
+ // src/transport/handlers/permissions.ts
1012
+ var permissionsHandlers = {
1013
+ "permissions.web.check": {
1014
+ command: TAURI_COMMANDS.permissions.checkWeb,
1015
+ args: (p) => ({
1016
+ url: p.url
1017
+ })
1018
+ },
1019
+ "permissions.database.check": {
1020
+ command: TAURI_COMMANDS.permissions.checkDatabase,
1021
+ args: (p) => ({
1022
+ resource: p.resource,
1023
+ operation: p.operation
1024
+ })
1025
+ },
1026
+ "permissions.filesystem.check": {
1027
+ command: TAURI_COMMANDS.permissions.checkFilesystem,
1028
+ args: (p) => ({
1029
+ path: p.path,
1030
+ actionStr: p.action
1031
+ })
946
1032
  }
947
- /**
948
- * Execute a raw SQL statement (INSERT, UPDATE, DELETE, CREATE, etc.)
949
- * Returns rowsAffected and lastInsertId
950
- */
951
- async execute(sql, params = []) {
952
- const result = await this.request(
953
- HAEXTENSION_METHODS.database.execute,
954
- { query: sql, params }
955
- );
956
- return {
957
- rowsAffected: result.rowsAffected,
958
- lastInsertId: result.lastInsertId
959
- };
960
- }
961
- /**
962
- * Registers and applies extension migrations with HaexVault
963
- *
964
- * HaexVault will:
965
- * 1. Validate all SQL statements (ensure only extension's own tables are accessed)
966
- * 2. Store migrations with applied_at = NULL
967
- * 3. Query pending migrations sorted by name
968
- * 4. Apply pending migrations and set up CRDT triggers
969
- * 5. Mark successful migrations with applied_at timestamp
970
- *
971
- * @param extensionVersion - The version of the extension
972
- * @param migrations - Array of migration objects with name and SQL content
973
- * @returns Promise with migration result (applied count, already applied count, applied migration names)
974
- */
975
- async registerMigrationsAsync(extensionVersion, migrations) {
976
- return this.database.registerMigrationsAsync(extensionVersion, migrations);
977
- }
978
- async requestDatabasePermission(request) {
979
- return this.request("permissions.database.request", {
980
- resource: request.resource,
981
- operation: request.operation,
982
- reason: request.reason
983
- });
984
- }
985
- async checkDatabasePermission(resource, operation) {
986
- const response = await this.request(
987
- "permissions.database.check",
988
- {
989
- resource,
990
- operation
991
- }
992
- );
993
- return response.status === "granted";
994
- }
995
- async respondToSearch(requestId, results) {
996
- await this.request("search.respond", {
997
- requestId,
998
- results
999
- });
1000
- }
1001
- /**
1002
- * Register a handler for external requests (from browser extensions, CLI, servers, etc.)
1003
- *
1004
- * @param action - The action/method name to handle (e.g., "get-logins", "get-totp")
1005
- * @param handler - Function that processes the request and returns a response
1006
- * @returns Unsubscribe function to remove the handler
1007
- *
1008
- * @example
1009
- * ```typescript
1010
- * client.onExternalRequest("get-logins", async (request) => {
1011
- * const entries = await getMatchingEntries(request.payload.url);
1012
- * return {
1013
- * requestId: request.requestId,
1014
- * success: true,
1015
- * data: { entries }
1016
- * };
1017
- * });
1018
- * ```
1019
- */
1020
- onExternalRequest(action, handler) {
1021
- this.externalRequestHandlers.set(action, handler);
1022
- this.log(`[ExternalRequest] Registered handler for action: ${action}`);
1023
- return () => {
1024
- this.externalRequestHandlers.delete(action);
1025
- this.log(`[ExternalRequest] Unregistered handler for action: ${action}`);
1026
- };
1027
- }
1028
- /**
1029
- * Send a response to an external request back to haex-vault
1030
- * This is called internally after a handler processes a request
1031
- */
1032
- async respondToExternalRequest(response) {
1033
- await this.request("external.respond", response);
1034
- }
1035
- async request(method, params) {
1036
- const resolvedParams = params ?? {};
1037
- if (this.isNativeWindow && typeof window.__TAURI__ !== "undefined") {
1038
- return this.invoke(method, resolvedParams);
1039
- }
1040
- return this.postMessage(method, resolvedParams);
1041
- }
1042
- async postMessage(method, params) {
1043
- const requestId = this.generateRequestId();
1044
- const request = {
1045
- method,
1046
- params,
1047
- timestamp: Date.now()
1048
- };
1049
- return new Promise((resolve, reject) => {
1050
- const timeout = setTimeout(() => {
1051
- this.pendingRequests.delete(requestId);
1052
- reject(
1053
- new HaexHubError("TIMEOUT" /* TIMEOUT */, "errors.timeout", {
1054
- timeout: this.config.timeout
1055
- })
1056
- );
1057
- }, this.config.timeout);
1058
- this.pendingRequests.set(requestId, { resolve, reject, timeout });
1059
- const targetOrigin = "*";
1060
- if (this.config.debug) {
1061
- console.log("[SDK Debug] ========== Sending Request ==========");
1062
- console.log("[SDK Debug] Request ID:", requestId);
1063
- console.log("[SDK Debug] Method:", request.method);
1064
- console.log("[SDK Debug] Params:", request.params);
1065
- console.log("[SDK Debug] Target origin:", targetOrigin);
1066
- console.log("[SDK Debug] Extension info:", this._extensionInfo);
1067
- console.log("[SDK Debug] ========================================");
1068
- }
1069
- window.parent.postMessage({ id: requestId, ...request }, targetOrigin);
1070
- });
1071
- }
1072
- async invoke(method, params) {
1073
- const { invoke } = window.__TAURI__.core;
1074
- if (this.config.debug) {
1075
- console.log("[SDK Debug] ========== Invoke Request ==========");
1076
- console.log("[SDK Debug] Method:", method);
1077
- console.log("[SDK Debug] Params:", params);
1078
- console.log("[SDK Debug] =======================================");
1079
- }
1080
- switch (method) {
1081
- case HAEXTENSION_METHODS.database.query:
1082
- return invoke("webview_extension_db_query", {
1083
- query: params.query,
1084
- params: params.params || []
1085
- });
1086
- case HAEXTENSION_METHODS.database.execute:
1087
- return invoke("webview_extension_db_execute", {
1088
- query: params.query,
1089
- params: params.params || []
1090
- });
1091
- case "permissions.web.check":
1092
- return invoke("webview_extension_check_web_permission", {
1093
- url: params.url
1094
- });
1095
- case "permissions.database.check":
1096
- return invoke("webview_extension_check_database_permission", {
1097
- resource: params.resource,
1098
- operation: params.operation
1099
- });
1100
- case "permissions.filesystem.check":
1101
- return invoke("webview_extension_check_filesystem_permission", {
1102
- path: params.path,
1103
- actionStr: params.action
1104
- });
1105
- case HAEXTENSION_METHODS.application.open:
1106
- return invoke("webview_extension_web_open", {
1107
- url: params.url
1108
- });
1109
- case HAEXTENSION_METHODS.web.fetch:
1110
- return invoke("webview_extension_web_request", {
1111
- url: params.url,
1112
- method: params.method,
1113
- headers: params.headers,
1114
- body: params.body
1115
- });
1116
- case HAEXTENSION_METHODS.filesystem.saveFile:
1117
- return invoke("webview_extension_fs_save_file", {
1118
- data: params.data,
1119
- defaultPath: params.defaultPath,
1120
- title: params.title,
1121
- filters: params.filters
1122
- });
1123
- case HAEXTENSION_METHODS.filesystem.openFile:
1124
- return invoke("webview_extension_fs_open_file", {
1125
- data: params.data,
1126
- fileName: params.fileName
1127
- });
1128
- case HAEXTENSION_METHODS.database.registerMigrations:
1129
- return invoke("webview_extension_db_register_migrations", {
1130
- extensionVersion: params.extensionVersion,
1131
- migrations: params.migrations
1132
- });
1133
- case "external.respond":
1134
- return invoke("webview_extension_external_respond", {
1135
- requestId: params.requestId,
1136
- success: params.success,
1137
- data: params.data,
1138
- error: params.error
1139
- });
1140
- default:
1141
- throw new HaexHubError(
1142
- "METHOD_NOT_FOUND" /* METHOD_NOT_FOUND */,
1143
- "errors.method_not_found",
1144
- { method }
1145
- );
1146
- }
1033
+ };
1034
+
1035
+ // src/transport/handlers/web.ts
1036
+ var webHandlers = {
1037
+ [HAEXTENSION_METHODS.application.open]: {
1038
+ command: TAURI_COMMANDS.web.open,
1039
+ args: (p) => ({
1040
+ url: p.url
1041
+ })
1042
+ },
1043
+ [HAEXTENSION_METHODS.web.fetch]: {
1044
+ command: TAURI_COMMANDS.web.fetch,
1045
+ args: (p) => ({
1046
+ url: p.url,
1047
+ method: p.method,
1048
+ headers: p.headers,
1049
+ body: p.body
1050
+ })
1147
1051
  }
1148
- on(eventType, callback) {
1149
- if (!this.eventListeners.has(eventType)) {
1150
- this.eventListeners.set(eventType, /* @__PURE__ */ new Set());
1151
- }
1152
- this.eventListeners.get(eventType).add(callback);
1052
+ };
1053
+
1054
+ // src/transport/handlers/filesystem.ts
1055
+ var filesystemHandlers = {
1056
+ [HAEXTENSION_METHODS.filesystem.saveFile]: {
1057
+ command: TAURI_COMMANDS.filesystem.saveFile,
1058
+ args: (p) => ({
1059
+ data: p.data,
1060
+ defaultPath: p.defaultPath,
1061
+ title: p.title,
1062
+ filters: p.filters
1063
+ })
1064
+ },
1065
+ [HAEXTENSION_METHODS.filesystem.openFile]: {
1066
+ command: TAURI_COMMANDS.filesystem.openFile,
1067
+ args: (p) => ({
1068
+ data: p.data,
1069
+ fileName: p.fileName
1070
+ })
1071
+ },
1072
+ [HAEXTENSION_METHODS.filesystem.showImage]: {
1073
+ command: TAURI_COMMANDS.filesystem.showImage,
1074
+ args: (p) => ({
1075
+ dataUrl: p.dataUrl
1076
+ })
1153
1077
  }
1154
- off(eventType, callback) {
1155
- const listeners = this.eventListeners.get(eventType);
1156
- if (listeners) {
1157
- listeners.delete(callback);
1158
- }
1078
+ };
1079
+
1080
+ // src/transport/handlers/external.ts
1081
+ var externalHandlers = {
1082
+ "external.respond": {
1083
+ command: TAURI_COMMANDS.external.respond,
1084
+ args: (p) => ({
1085
+ requestId: p.requestId,
1086
+ success: p.success,
1087
+ data: p.data,
1088
+ error: p.error
1089
+ })
1159
1090
  }
1160
- destroy() {
1161
- if (this.messageHandler) {
1162
- window.removeEventListener("message", this.messageHandler);
1163
- }
1164
- this.pendingRequests.forEach(({ timeout }) => clearTimeout(timeout));
1165
- this.pendingRequests.clear();
1166
- this.eventListeners.clear();
1167
- this.initialized = false;
1168
- this.log("HaexHub SDK destroyed");
1091
+ };
1092
+
1093
+ // src/transport/handlers/filesync.ts
1094
+ var filesyncHandlers = {
1095
+ // ==========================================================================
1096
+ // Spaces
1097
+ // ==========================================================================
1098
+ [HAEXTENSION_METHODS.filesystem.sync.listSpaces]: {
1099
+ command: TAURI_COMMANDS.filesync.listSpaces,
1100
+ args: () => ({})
1101
+ },
1102
+ [HAEXTENSION_METHODS.filesystem.sync.createSpace]: {
1103
+ command: TAURI_COMMANDS.filesync.createSpace,
1104
+ args: (p) => ({ request: p })
1105
+ },
1106
+ [HAEXTENSION_METHODS.filesystem.sync.deleteSpace]: {
1107
+ command: TAURI_COMMANDS.filesync.deleteSpace,
1108
+ args: (p) => ({ spaceId: p.spaceId })
1109
+ },
1110
+ // ==========================================================================
1111
+ // Files
1112
+ // ==========================================================================
1113
+ [HAEXTENSION_METHODS.filesystem.sync.listFiles]: {
1114
+ command: TAURI_COMMANDS.filesync.listFiles,
1115
+ args: (p) => ({ request: p })
1116
+ },
1117
+ [HAEXTENSION_METHODS.filesystem.sync.getFile]: {
1118
+ command: TAURI_COMMANDS.filesync.getFile,
1119
+ args: (p) => ({ fileId: p.fileId })
1120
+ },
1121
+ [HAEXTENSION_METHODS.filesystem.sync.uploadFile]: {
1122
+ command: TAURI_COMMANDS.filesync.uploadFile,
1123
+ args: (p) => ({ request: p })
1124
+ },
1125
+ [HAEXTENSION_METHODS.filesystem.sync.downloadFile]: {
1126
+ command: TAURI_COMMANDS.filesync.downloadFile,
1127
+ args: (p) => ({ request: p })
1128
+ },
1129
+ [HAEXTENSION_METHODS.filesystem.sync.deleteFile]: {
1130
+ command: TAURI_COMMANDS.filesync.deleteFile,
1131
+ args: (p) => ({ fileId: p.fileId })
1132
+ },
1133
+ // ==========================================================================
1134
+ // Backends
1135
+ // ==========================================================================
1136
+ [HAEXTENSION_METHODS.filesystem.sync.listBackends]: {
1137
+ command: TAURI_COMMANDS.filesync.listBackends,
1138
+ args: () => ({})
1139
+ },
1140
+ [HAEXTENSION_METHODS.filesystem.sync.addBackend]: {
1141
+ command: TAURI_COMMANDS.filesync.addBackend,
1142
+ args: (p) => ({ request: p })
1143
+ },
1144
+ [HAEXTENSION_METHODS.filesystem.sync.removeBackend]: {
1145
+ command: TAURI_COMMANDS.filesync.removeBackend,
1146
+ args: (p) => ({ backendId: p.backendId })
1147
+ },
1148
+ [HAEXTENSION_METHODS.filesystem.sync.testBackend]: {
1149
+ command: TAURI_COMMANDS.filesync.testBackend,
1150
+ args: (p) => ({ backendId: p.backendId })
1151
+ },
1152
+ // ==========================================================================
1153
+ // Sync Rules
1154
+ // ==========================================================================
1155
+ [HAEXTENSION_METHODS.filesystem.sync.listSyncRules]: {
1156
+ command: TAURI_COMMANDS.filesync.listSyncRules,
1157
+ args: () => ({})
1158
+ },
1159
+ [HAEXTENSION_METHODS.filesystem.sync.addSyncRule]: {
1160
+ command: TAURI_COMMANDS.filesync.addSyncRule,
1161
+ args: (p) => ({ request: p })
1162
+ },
1163
+ [HAEXTENSION_METHODS.filesystem.sync.removeSyncRule]: {
1164
+ command: TAURI_COMMANDS.filesync.removeSyncRule,
1165
+ args: (p) => ({ ruleId: p.ruleId })
1166
+ },
1167
+ // ==========================================================================
1168
+ // Sync Operations
1169
+ // ==========================================================================
1170
+ [HAEXTENSION_METHODS.filesystem.sync.getSyncStatus]: {
1171
+ command: TAURI_COMMANDS.filesync.getSyncStatus,
1172
+ args: () => ({})
1173
+ },
1174
+ [HAEXTENSION_METHODS.filesystem.sync.triggerSync]: {
1175
+ command: TAURI_COMMANDS.filesync.triggerSync,
1176
+ args: () => ({})
1177
+ },
1178
+ [HAEXTENSION_METHODS.filesystem.sync.pauseSync]: {
1179
+ command: TAURI_COMMANDS.filesync.pauseSync,
1180
+ args: () => ({})
1181
+ },
1182
+ [HAEXTENSION_METHODS.filesystem.sync.resumeSync]: {
1183
+ command: TAURI_COMMANDS.filesync.resumeSync,
1184
+ args: () => ({})
1185
+ },
1186
+ // ==========================================================================
1187
+ // Conflict Resolution
1188
+ // ==========================================================================
1189
+ [HAEXTENSION_METHODS.filesystem.sync.resolveConflict]: {
1190
+ command: TAURI_COMMANDS.filesync.resolveConflict,
1191
+ args: (p) => ({ request: p })
1192
+ },
1193
+ // ==========================================================================
1194
+ // UI Helpers
1195
+ // ==========================================================================
1196
+ [HAEXTENSION_METHODS.filesystem.sync.selectFolder]: {
1197
+ command: TAURI_COMMANDS.filesync.selectFolder,
1198
+ args: () => ({})
1169
1199
  }
1170
- async init() {
1171
- if (this.initialized) return;
1172
- const isInIframe = window.self !== window.top;
1173
- if (!isInIframe) {
1174
- try {
1175
- if (typeof window.__TAURI__ !== "undefined") {
1176
- const { invoke } = window.__TAURI__.core;
1177
- this._extensionInfo = await invoke("webview_extension_get_info");
1178
- this._context = await invoke("webview_extension_context_get");
1179
- this.isNativeWindow = true;
1180
- this.initialized = true;
1181
- this.log("HaexHub SDK initialized in native WebViewWindow mode");
1182
- this.log("Extension info:", this._extensionInfo);
1183
- this.log("Application context:", this._context);
1184
- this.notifySubscribers();
1185
- const { listen } = window.__TAURI__.event;
1186
- console.log("[HaexSpace SDK] Setting up Tauri event listener for:", HAEXTENSION_EVENTS.CONTEXT_CHANGED);
1187
- try {
1188
- await listen(HAEXTENSION_EVENTS.CONTEXT_CHANGED, (event) => {
1189
- console.log("[HaexSpace SDK] Received Tauri event:", HAEXTENSION_EVENTS.CONTEXT_CHANGED, event);
1190
- this.log("Received context change event:", event);
1191
- if (event.payload?.context) {
1192
- this._context = event.payload.context;
1193
- console.log("[HaexSpace SDK] Updated context to:", this._context);
1194
- this.handleEvent({
1195
- type: HAEXTENSION_EVENTS.CONTEXT_CHANGED,
1196
- data: { context: this._context },
1197
- timestamp: Date.now()
1198
- });
1199
- } else {
1200
- console.warn("[HaexSpace SDK] Event received but no context in payload:", event);
1201
- }
1202
- });
1203
- console.log("[HaexSpace SDK] Context change listener registered successfully");
1204
- } catch (error) {
1205
- console.error("[HaexSpace SDK] Failed to setup context change listener:", error);
1206
- this.log("Failed to setup context change listener:", error);
1207
- }
1208
- try {
1209
- await listen(HAEXTENSION_EVENTS.EXTERNAL_REQUEST, (event) => {
1210
- this.log("Received external request event:", event);
1211
- if (event.payload) {
1212
- this.handleEvent({
1213
- type: HAEXTENSION_EVENTS.EXTERNAL_REQUEST,
1214
- data: event.payload,
1215
- timestamp: Date.now()
1216
- });
1217
- }
1218
- });
1219
- console.log("[HaexSpace SDK] External request listener registered successfully");
1220
- } catch (error) {
1221
- console.error("[HaexSpace SDK] Failed to setup external request listener:", error);
1222
- this.log("Failed to setup external request listener:", error);
1223
- }
1224
- this.resolveReady();
1225
- return;
1226
- }
1227
- } catch (error) {
1228
- this.log("Tauri commands failed, falling back to iframe mode", error);
1229
- }
1230
- }
1231
- if (window.self === window.top) {
1232
- throw new HaexHubError("NOT_IN_IFRAME" /* NOT_IN_IFRAME */, "errors.not_in_iframe");
1233
- }
1234
- this.messageHandler = this.handleMessage.bind(this);
1235
- window.addEventListener("message", this.messageHandler);
1236
- this.isNativeWindow = false;
1237
- this.initialized = true;
1238
- this.log("HaexSpace SDK initialized in iframe mode");
1239
- try {
1240
- if (this.config.manifest) {
1241
- this._extensionInfo = {
1242
- publicKey: this.config.manifest.publicKey,
1243
- name: this.config.manifest.name,
1244
- version: this.config.manifest.version,
1245
- displayName: this.config.manifest.name
1246
- };
1247
- this.log("Extension info loaded from manifest:", this._extensionInfo);
1248
- this.notifySubscribers();
1249
- }
1250
- if (typeof window !== "undefined" && window.parent) {
1251
- const debugInfo = `SDK Debug:
1252
- window.parent exists: ${!!window.parent}
1253
- window.parent === window: ${window.parent === window}
1254
- window.self === window.top: ${window.self === window.top}`;
1255
- try {
1256
- window.parent.postMessage({
1257
- type: HAEXSPACE_MESSAGE_TYPES.DEBUG,
1258
- data: debugInfo
1259
- }, "*");
1260
- } catch (e) {
1261
- alert(debugInfo + `
1262
- postMessage error: ${e}`);
1263
- }
1264
- }
1265
- this._context = await this.request(HAEXTENSION_METHODS.context.get);
1266
- this.log("Application context received:", this._context);
1267
- this.notifySubscribers();
1268
- this.resolveReady();
1269
- } catch (error) {
1270
- this.log("Failed to load extension info or context:", error);
1271
- throw error;
1200
+ };
1201
+
1202
+ // src/transport/handlers/index.ts
1203
+ var allHandlers = {
1204
+ ...databaseHandlers,
1205
+ ...permissionsHandlers,
1206
+ ...webHandlers,
1207
+ ...filesystemHandlers,
1208
+ ...externalHandlers,
1209
+ ...filesyncHandlers
1210
+ };
1211
+
1212
+ // src/client/transport.ts
1213
+ function generateRequestId(counter) {
1214
+ return `req_${counter}`;
1215
+ }
1216
+ function sendPostMessage(method, params, requestId, config, extensionInfo, pendingRequests) {
1217
+ const request = {
1218
+ method,
1219
+ params,
1220
+ timestamp: Date.now()
1221
+ };
1222
+ return new Promise((resolve, reject) => {
1223
+ const timeout = setTimeout(() => {
1224
+ pendingRequests.delete(requestId);
1225
+ reject(
1226
+ new HaexVaultSdkError("TIMEOUT" /* TIMEOUT */, "errors.timeout", {
1227
+ timeout: config.timeout
1228
+ })
1229
+ );
1230
+ }, config.timeout);
1231
+ pendingRequests.set(requestId, { resolve, reject, timeout });
1232
+ const targetOrigin = "*";
1233
+ if (config.debug) {
1234
+ console.log("[SDK Debug] ========== Sending Request ==========");
1235
+ console.log("[SDK Debug] Request ID:", requestId);
1236
+ console.log("[SDK Debug] Method:", request.method);
1237
+ console.log("[SDK Debug] Params:", request.params);
1238
+ console.log("[SDK Debug] Target origin:", targetOrigin);
1239
+ console.log("[SDK Debug] Extension info:", extensionInfo);
1240
+ console.log("[SDK Debug] ========================================");
1272
1241
  }
1273
- }
1274
- handleMessage(event) {
1275
- if (this.config.debug) {
1242
+ window.parent.postMessage({ id: requestId, ...request }, targetOrigin);
1243
+ });
1244
+ }
1245
+ async function sendInvoke(method, params, config, log) {
1246
+ const { invoke } = window.__TAURI__.core;
1247
+ if (config.debug) {
1248
+ console.log("[SDK Debug] ========== Invoke Request ==========");
1249
+ console.log("[SDK Debug] Method:", method);
1250
+ console.log("[SDK Debug] Params:", params);
1251
+ console.log("[SDK Debug] =======================================");
1252
+ }
1253
+ const handler = allHandlers[method];
1254
+ if (handler) {
1255
+ const args = handler.args(params);
1256
+ return invoke(handler.command, args);
1257
+ }
1258
+ throw new HaexVaultSdkError(
1259
+ "METHOD_NOT_FOUND" /* METHOD_NOT_FOUND */,
1260
+ "errors.method_not_found",
1261
+ { method }
1262
+ );
1263
+ }
1264
+
1265
+ // src/client/events.ts
1266
+ function createMessageHandler(config, pendingRequests, extensionInfo, onEvent) {
1267
+ return (event) => {
1268
+ if (config.debug) {
1276
1269
  console.log("[SDK Debug] ========== Message Received ==========");
1277
1270
  console.log("[SDK Debug] Event origin:", event.origin);
1278
1271
  console.log(
@@ -1280,160 +1273,534 @@ postMessage error: ${e}`);
1280
1273
  event.source === window.parent ? "parent window" : "unknown"
1281
1274
  );
1282
1275
  console.log("[SDK Debug] Event data:", event.data);
1283
- console.log("[SDK Debug] Extension info loaded:", !!this._extensionInfo);
1276
+ console.log("[SDK Debug] Extension info loaded:", !!extensionInfo());
1284
1277
  console.log(
1285
1278
  "[SDK Debug] Pending requests count:",
1286
- this.pendingRequests.size
1279
+ pendingRequests.size
1287
1280
  );
1288
1281
  }
1289
1282
  if (event.source !== window.parent) {
1290
- if (this.config.debug) {
1283
+ if (config.debug) {
1291
1284
  console.error("[SDK Debug] \u274C REJECTED: Message not from parent window!");
1292
1285
  }
1293
1286
  return;
1294
1287
  }
1295
1288
  const data = event.data;
1296
- if ("id" in data && this.pendingRequests.has(data.id)) {
1297
- if (this.config.debug) {
1289
+ if ("id" in data && pendingRequests.has(data.id)) {
1290
+ if (config.debug) {
1298
1291
  console.log("[SDK Debug] \u2705 Found pending request for ID:", data.id);
1299
1292
  }
1300
- const pending = this.pendingRequests.get(data.id);
1293
+ const pending = pendingRequests.get(data.id);
1301
1294
  clearTimeout(pending.timeout);
1302
- this.pendingRequests.delete(data.id);
1295
+ pendingRequests.delete(data.id);
1303
1296
  if (data.error) {
1304
- if (this.config.debug) {
1297
+ if (config.debug) {
1305
1298
  console.error("[SDK Debug] \u274C Request failed:", data.error);
1306
1299
  }
1307
1300
  pending.reject(data.error);
1308
1301
  } else {
1309
- if (this.config.debug) {
1302
+ if (config.debug) {
1310
1303
  console.log("[SDK Debug] \u2705 Request succeeded:", data.result);
1311
1304
  }
1312
1305
  pending.resolve(data.result);
1313
1306
  }
1314
1307
  return;
1315
1308
  }
1316
- if ("id" in data && !this.pendingRequests.has(data.id)) {
1317
- if (this.config.debug) {
1309
+ if ("id" in data && !pendingRequests.has(data.id)) {
1310
+ if (config.debug) {
1318
1311
  console.warn(
1319
1312
  "[SDK Debug] \u26A0\uFE0F Received response for unknown request ID:",
1320
1313
  data.id
1321
1314
  );
1322
1315
  console.warn(
1323
1316
  "[SDK Debug] Known IDs:",
1324
- Array.from(this.pendingRequests.keys())
1317
+ Array.from(pendingRequests.keys())
1325
1318
  );
1326
1319
  }
1327
1320
  }
1328
1321
  if ("type" in data && data.type) {
1329
- if (this.config.debug) {
1322
+ if (config.debug) {
1330
1323
  console.log("[SDK Debug] Event received:", data.type);
1331
1324
  }
1332
- this.handleEvent(data);
1325
+ onEvent(data);
1333
1326
  }
1334
- if (this.config.debug) {
1327
+ if (config.debug) {
1335
1328
  console.log("[SDK Debug] ========== End Message ==========");
1336
1329
  }
1330
+ };
1331
+ }
1332
+ function processEvent(event, log, eventListeners, onContextChanged, onExternalRequest) {
1333
+ if (event.type === HAEXTENSION_EVENTS.CONTEXT_CHANGED) {
1334
+ const contextEvent = event;
1335
+ onContextChanged(contextEvent.data.context);
1336
+ log("Context updated:", contextEvent.data.context);
1337
+ }
1338
+ if (event.type === HAEXTENSION_EVENTS.EXTERNAL_REQUEST) {
1339
+ const externalEvent = event;
1340
+ onExternalRequest(externalEvent);
1341
+ return;
1337
1342
  }
1338
- handleEvent(event) {
1339
- if (event.type === HAEXTENSION_EVENTS.CONTEXT_CHANGED) {
1340
- const contextEvent = event;
1341
- this._context = contextEvent.data.context;
1342
- this.log("Context updated:", this._context);
1343
- this.notifySubscribers();
1343
+ emitEvent(event, log, eventListeners);
1344
+ }
1345
+ function emitEvent(event, log, eventListeners) {
1346
+ log("Event received:", event);
1347
+ const listeners = eventListeners.get(event.type);
1348
+ if (listeners) {
1349
+ listeners.forEach((callback) => callback(event));
1350
+ }
1351
+ }
1352
+ function addEventListener(eventType, callback, eventListeners) {
1353
+ if (!eventListeners.has(eventType)) {
1354
+ eventListeners.set(eventType, /* @__PURE__ */ new Set());
1355
+ }
1356
+ eventListeners.get(eventType).add(callback);
1357
+ }
1358
+ function removeEventListener(eventType, callback, eventListeners) {
1359
+ const listeners = eventListeners.get(eventType);
1360
+ if (listeners) {
1361
+ listeners.delete(callback);
1362
+ }
1363
+ }
1364
+ function notifySubscribers(subscribers) {
1365
+ subscribers.forEach((callback) => callback());
1366
+ }
1367
+ function createDrizzleInstance(schema, extensionInfo, request, log) {
1368
+ if (!extensionInfo) {
1369
+ throw new HaexVaultSdkError(
1370
+ "EXTENSION_INFO_UNAVAILABLE" /* EXTENSION_INFO_UNAVAILABLE */,
1371
+ "errors.client_not_ready"
1372
+ );
1373
+ }
1374
+ return drizzle(
1375
+ async (sql, params, method) => {
1376
+ try {
1377
+ if (method === "run" || method === "all") {
1378
+ const result2 = await request(
1379
+ HAEXTENSION_METHODS.database.execute,
1380
+ {
1381
+ query: sql,
1382
+ params
1383
+ }
1384
+ );
1385
+ if (method === "all") {
1386
+ return { rows: result2.rows || [] };
1387
+ }
1388
+ if (result2.rows && Array.isArray(result2.rows) && result2.rows.length > 0) {
1389
+ return { rows: result2.rows };
1390
+ }
1391
+ return result2;
1392
+ }
1393
+ const result = await request(HAEXTENSION_METHODS.database.query, {
1394
+ query: sql,
1395
+ params
1396
+ });
1397
+ const rows = result.rows;
1398
+ if (method === "get") {
1399
+ return { rows: rows.length > 0 ? rows.at(0) : void 0 };
1400
+ }
1401
+ return { rows };
1402
+ } catch (error) {
1403
+ log("Database operation failed:", error);
1404
+ throw error;
1405
+ }
1406
+ },
1407
+ {
1408
+ schema,
1409
+ logger: false
1344
1410
  }
1345
- if (event.type === HAEXTENSION_EVENTS.EXTERNAL_REQUEST) {
1346
- const externalEvent = event;
1347
- this.handleExternalRequest(externalEvent.data);
1348
- return;
1411
+ );
1412
+ }
1413
+ async function queryRaw(sql, params, request, debug) {
1414
+ const result = await request(
1415
+ HAEXTENSION_METHODS.database.query,
1416
+ { query: sql, params }
1417
+ );
1418
+ if (debug) {
1419
+ console.log("[SDK query()] Raw result:", JSON.stringify(result, null, 2));
1420
+ }
1421
+ return result.rows;
1422
+ }
1423
+ async function executeRaw(sql, params, request) {
1424
+ const result = await request(
1425
+ HAEXTENSION_METHODS.database.execute,
1426
+ { query: sql, params }
1427
+ );
1428
+ return {
1429
+ rowsAffected: result.rowsAffected,
1430
+ lastInsertId: result.lastInsertId
1431
+ };
1432
+ }
1433
+
1434
+ // src/client/external.ts
1435
+ function registerExternalHandler(action, handler, handlers, log) {
1436
+ handlers.set(action, handler);
1437
+ log(`[ExternalRequest] Registered handler for action: ${action}`);
1438
+ return () => {
1439
+ handlers.delete(action);
1440
+ log(`[ExternalRequest] Unregistered handler for action: ${action}`);
1441
+ };
1442
+ }
1443
+ async function handleExternalRequest(request, handlers, respond, log) {
1444
+ log(`[ExternalRequest] Received request: ${request.action} from ${request.publicKey.substring(0, 20)}...`);
1445
+ const handler = handlers.get(request.action);
1446
+ if (!handler) {
1447
+ log(`[ExternalRequest] No handler for action: ${request.action}`);
1448
+ await respond({
1449
+ requestId: request.requestId,
1450
+ success: false,
1451
+ error: `No handler registered for action: ${request.action}`
1452
+ });
1453
+ return;
1454
+ }
1455
+ try {
1456
+ const response = await handler(request);
1457
+ await respond(response);
1458
+ log(`[ExternalRequest] Response sent for: ${request.action}`);
1459
+ } catch (error) {
1460
+ log(`[ExternalRequest] Handler error:`, error);
1461
+ await respond({
1462
+ requestId: request.requestId,
1463
+ success: false,
1464
+ error: error instanceof Error ? error.message : String(error)
1465
+ });
1466
+ }
1467
+ }
1468
+ async function respondToExternalRequest(response, request) {
1469
+ await request("external.respond", response);
1470
+ }
1471
+
1472
+ // src/client.ts
1473
+ var HaexVaultClient = class {
1474
+ constructor(config = {}) {
1475
+ // State
1476
+ this.initialized = false;
1477
+ this.isNativeWindow = false;
1478
+ this.requestCounter = 0;
1479
+ this._extensionInfo = null;
1480
+ this._context = null;
1481
+ this._setupCompleted = false;
1482
+ // Collections
1483
+ this.pendingRequests = /* @__PURE__ */ new Map();
1484
+ this.eventListeners = /* @__PURE__ */ new Map();
1485
+ this.externalRequestHandlers = /* @__PURE__ */ new Map();
1486
+ this.reactiveSubscribers = /* @__PURE__ */ new Set();
1487
+ // Handlers
1488
+ this.messageHandler = null;
1489
+ this.setupPromise = null;
1490
+ this.setupHook = null;
1491
+ // Public APIs
1492
+ this.orm = null;
1493
+ this.config = {
1494
+ debug: config.debug ?? false,
1495
+ timeout: config.timeout ?? DEFAULT_TIMEOUT,
1496
+ manifest: config.manifest
1497
+ };
1498
+ this.storage = new StorageAPI(this);
1499
+ this.database = new DatabaseAPI(this);
1500
+ this.filesystem = new FilesystemAPI(this);
1501
+ this.web = new WebAPI(this);
1502
+ this.permissions = new PermissionsAPI(this);
1503
+ installConsoleForwarding(this.config.debug);
1504
+ this.readyPromise = new Promise((resolve) => {
1505
+ this.resolveReady = resolve;
1506
+ });
1507
+ this.init();
1508
+ }
1509
+ // ==========================================================================
1510
+ // Lifecycle
1511
+ // ==========================================================================
1512
+ async ready() {
1513
+ return this.readyPromise;
1514
+ }
1515
+ get setupCompleted() {
1516
+ return this._setupCompleted;
1517
+ }
1518
+ onSetup(setupFn) {
1519
+ if (this.setupHook) {
1520
+ throw new Error("Setup hook already registered");
1349
1521
  }
1350
- this.emitEvent(event);
1351
- }
1352
- async handleExternalRequest(request) {
1353
- this.log(`[ExternalRequest] Received request: ${request.action} from ${request.publicKey.substring(0, 20)}...`);
1354
- const handler = this.externalRequestHandlers.get(request.action);
1355
- if (!handler) {
1356
- this.log(`[ExternalRequest] No handler for action: ${request.action}`);
1357
- await this.respondToExternalRequest({
1358
- requestId: request.requestId,
1359
- success: false,
1360
- error: `No handler registered for action: ${request.action}`
1361
- });
1522
+ this.setupHook = setupFn;
1523
+ }
1524
+ async setupComplete() {
1525
+ await this.readyPromise;
1526
+ if (!this.setupHook || this.setupCompleted) {
1362
1527
  return;
1363
1528
  }
1364
- try {
1365
- const response = await handler(request);
1366
- await this.respondToExternalRequest(response);
1367
- this.log(`[ExternalRequest] Response sent for: ${request.action}`);
1368
- } catch (error) {
1369
- this.log(`[ExternalRequest] Handler error:`, error);
1370
- await this.respondToExternalRequest({
1371
- requestId: request.requestId,
1372
- success: false,
1373
- error: error instanceof Error ? error.message : String(error)
1374
- });
1529
+ if (!this.setupPromise) {
1530
+ this.setupPromise = this.runSetupAsync();
1375
1531
  }
1532
+ return this.setupPromise;
1376
1533
  }
1377
- emitEvent(event) {
1378
- this.log("Event received:", event);
1379
- const listeners = this.eventListeners.get(event.type);
1380
- if (listeners) {
1381
- listeners.forEach((callback) => callback(event));
1534
+ destroy() {
1535
+ if (this.messageHandler) {
1536
+ window.removeEventListener("message", this.messageHandler);
1382
1537
  }
1538
+ this.pendingRequests.forEach(({ timeout }) => clearTimeout(timeout));
1539
+ this.pendingRequests.clear();
1540
+ this.eventListeners.clear();
1541
+ this.initialized = false;
1542
+ this.log("HaexVault SDK destroyed");
1383
1543
  }
1384
- generateRequestId() {
1385
- return `req_${++this.requestCounter}`;
1544
+ // ==========================================================================
1545
+ // Properties
1546
+ // ==========================================================================
1547
+ get extensionInfo() {
1548
+ return this._extensionInfo;
1386
1549
  }
1387
- validatePublicKey(publicKey) {
1388
- if (!publicKey || typeof publicKey !== "string" || publicKey.trim() === "") {
1389
- throw new HaexHubError(
1390
- "INVALID_PUBLIC_KEY" /* INVALID_PUBLIC_KEY */,
1391
- "errors.invalid_public_key",
1392
- { publicKey }
1393
- );
1394
- }
1550
+ get context() {
1551
+ return this._context;
1395
1552
  }
1396
- validateExtensionName(extensionName) {
1397
- if (!extensionName || !/^[a-z][a-z0-9-]*$/i.test(extensionName)) {
1398
- throw new HaexHubError(
1399
- "INVALID_EXTENSION_NAME" /* INVALID_EXTENSION_NAME */,
1400
- "errors.invalid_extension_name",
1401
- { extensionName }
1402
- );
1403
- }
1404
- if (extensionName.includes(TABLE_SEPARATOR)) {
1405
- throw new HaexHubError(
1406
- "INVALID_EXTENSION_NAME" /* INVALID_EXTENSION_NAME */,
1407
- "errors.extension_name_contains_separator",
1408
- { extensionName, separator: TABLE_SEPARATOR }
1409
- );
1553
+ // ==========================================================================
1554
+ // Subscriptions
1555
+ // ==========================================================================
1556
+ subscribe(callback) {
1557
+ this.reactiveSubscribers.add(callback);
1558
+ return () => {
1559
+ this.reactiveSubscribers.delete(callback);
1560
+ };
1561
+ }
1562
+ // ==========================================================================
1563
+ // Table Name Utilities
1564
+ // ==========================================================================
1565
+ getTableName(tableName) {
1566
+ return getExtensionTableName(this._extensionInfo, tableName);
1567
+ }
1568
+ getDependencyTableName(publicKey, extensionName, tableName) {
1569
+ return getDependencyTableName(publicKey, extensionName, tableName);
1570
+ }
1571
+ parseTableName(fullTableName) {
1572
+ return parseTableName(fullTableName);
1573
+ }
1574
+ // ==========================================================================
1575
+ // Database
1576
+ // ==========================================================================
1577
+ initializeDatabase(schema) {
1578
+ const db = createDrizzleInstance(schema, this._extensionInfo, this.request.bind(this), this.log.bind(this));
1579
+ this.orm = db;
1580
+ return db;
1581
+ }
1582
+ async query(sql, params = []) {
1583
+ return queryRaw(sql, params, this.request.bind(this), this.config.debug);
1584
+ }
1585
+ async select(sql, params = []) {
1586
+ return this.query(sql, params);
1587
+ }
1588
+ async execute(sql, params = []) {
1589
+ return executeRaw(sql, params, this.request.bind(this));
1590
+ }
1591
+ async registerMigrationsAsync(extensionVersion, migrations) {
1592
+ return this.database.registerMigrationsAsync(extensionVersion, migrations);
1593
+ }
1594
+ // ==========================================================================
1595
+ // Dependencies
1596
+ // ==========================================================================
1597
+ async getDependencies() {
1598
+ return this.request("extensions.getDependencies");
1599
+ }
1600
+ // ==========================================================================
1601
+ // Permissions
1602
+ // ==========================================================================
1603
+ async requestDatabasePermission(request) {
1604
+ return this.request("permissions.database.request", {
1605
+ resource: request.resource,
1606
+ operation: request.operation,
1607
+ reason: request.reason
1608
+ });
1609
+ }
1610
+ async checkDatabasePermission(resource, operation) {
1611
+ const response = await this.request("permissions.database.check", { resource, operation });
1612
+ return response.status === "granted";
1613
+ }
1614
+ // ==========================================================================
1615
+ // Search
1616
+ // ==========================================================================
1617
+ async respondToSearch(requestId, results) {
1618
+ await this.request("search.respond", { requestId, results });
1619
+ }
1620
+ // ==========================================================================
1621
+ // External Requests
1622
+ // ==========================================================================
1623
+ onExternalRequest(action, handler) {
1624
+ return registerExternalHandler(action, handler, this.externalRequestHandlers, this.log.bind(this));
1625
+ }
1626
+ async respondToExternalRequest(response) {
1627
+ await respondToExternalRequest(response, this.request.bind(this));
1628
+ }
1629
+ // ==========================================================================
1630
+ // Events
1631
+ // ==========================================================================
1632
+ on(eventType, callback) {
1633
+ addEventListener(eventType, callback, this.eventListeners);
1634
+ }
1635
+ off(eventType, callback) {
1636
+ removeEventListener(eventType, callback, this.eventListeners);
1637
+ }
1638
+ // ==========================================================================
1639
+ // Communication
1640
+ // ==========================================================================
1641
+ async request(method, params) {
1642
+ const resolvedParams = params ?? {};
1643
+ if (this.isNativeWindow && hasTauri()) {
1644
+ return sendInvoke(method, resolvedParams, this.config, this.log.bind(this));
1410
1645
  }
1646
+ const requestId = generateRequestId(++this.requestCounter);
1647
+ return sendPostMessage(method, resolvedParams, requestId, this.config, this._extensionInfo, this.pendingRequests);
1411
1648
  }
1412
- validateTableName(tableName) {
1413
- if (!tableName || typeof tableName !== "string") {
1414
- throw new HaexHubError(
1415
- "INVALID_TABLE_NAME" /* INVALID_TABLE_NAME */,
1416
- "errors.table_name_empty"
1417
- );
1649
+ // ==========================================================================
1650
+ // Private: Initialization
1651
+ // ==========================================================================
1652
+ async init() {
1653
+ if (this.initialized) return;
1654
+ if (!isInIframe() && hasTauri()) {
1655
+ try {
1656
+ await this.initNative();
1657
+ return;
1658
+ } catch (error) {
1659
+ this.log("Tauri commands failed, falling back to iframe mode", error);
1660
+ }
1418
1661
  }
1419
- if (tableName.includes(TABLE_SEPARATOR)) {
1420
- throw new HaexHubError(
1421
- "INVALID_TABLE_NAME" /* INVALID_TABLE_NAME */,
1422
- "errors.table_name_contains_separator",
1423
- { tableName, separator: TABLE_SEPARATOR }
1424
- );
1662
+ await this.initIframe();
1663
+ }
1664
+ async initNative() {
1665
+ const { extensionInfo, context } = await initNativeMode(
1666
+ {
1667
+ config: this.config,
1668
+ state: {
1669
+ initialized: this.initialized,
1670
+ isNativeWindow: this.isNativeWindow,
1671
+ requestCounter: this.requestCounter,
1672
+ setupCompleted: this._setupCompleted,
1673
+ extensionInfo: this._extensionInfo,
1674
+ context: this._context,
1675
+ orm: this.orm
1676
+ },
1677
+ collections: {
1678
+ pendingRequests: this.pendingRequests,
1679
+ eventListeners: this.eventListeners,
1680
+ externalRequestHandlers: this.externalRequestHandlers,
1681
+ reactiveSubscribers: this.reactiveSubscribers
1682
+ },
1683
+ promises: {
1684
+ readyPromise: this.readyPromise,
1685
+ resolveReady: this.resolveReady,
1686
+ setupPromise: this.setupPromise,
1687
+ setupHook: this.setupHook
1688
+ },
1689
+ handlers: {
1690
+ messageHandler: this.messageHandler
1691
+ }
1692
+ },
1693
+ this.log.bind(this),
1694
+ this.handleEvent.bind(this),
1695
+ (ctx) => {
1696
+ this._context = ctx;
1697
+ this.notifySubscribersInternal();
1698
+ }
1699
+ );
1700
+ this._extensionInfo = extensionInfo;
1701
+ this._context = context;
1702
+ this.isNativeWindow = true;
1703
+ this.initialized = true;
1704
+ this.notifySubscribersInternal();
1705
+ this.resolveReady();
1706
+ }
1707
+ async initIframe() {
1708
+ this.messageHandler = createMessageHandler(
1709
+ this.config,
1710
+ this.pendingRequests,
1711
+ () => this._extensionInfo,
1712
+ this.handleEvent.bind(this)
1713
+ );
1714
+ const { context } = await initIframeMode(
1715
+ {
1716
+ config: this.config,
1717
+ state: {
1718
+ initialized: this.initialized,
1719
+ isNativeWindow: this.isNativeWindow,
1720
+ requestCounter: this.requestCounter,
1721
+ setupCompleted: this._setupCompleted,
1722
+ extensionInfo: this._extensionInfo,
1723
+ context: this._context,
1724
+ orm: this.orm
1725
+ },
1726
+ collections: {
1727
+ pendingRequests: this.pendingRequests,
1728
+ eventListeners: this.eventListeners,
1729
+ externalRequestHandlers: this.externalRequestHandlers,
1730
+ reactiveSubscribers: this.reactiveSubscribers
1731
+ },
1732
+ promises: {
1733
+ readyPromise: this.readyPromise,
1734
+ resolveReady: this.resolveReady,
1735
+ setupPromise: this.setupPromise,
1736
+ setupHook: this.setupHook
1737
+ },
1738
+ handlers: {
1739
+ messageHandler: this.messageHandler
1740
+ }
1741
+ },
1742
+ this.log.bind(this),
1743
+ this.messageHandler,
1744
+ this.request.bind(this)
1745
+ );
1746
+ if (this.config.manifest) {
1747
+ this._extensionInfo = {
1748
+ publicKey: this.config.manifest.publicKey,
1749
+ name: this.config.manifest.name,
1750
+ version: this.config.manifest.version,
1751
+ displayName: this.config.manifest.name
1752
+ };
1753
+ this.notifySubscribersInternal();
1425
1754
  }
1426
- if (!/^[a-z][a-z0-9-_]*$/i.test(tableName)) {
1427
- throw new HaexHubError(
1428
- "INVALID_TABLE_NAME" /* INVALID_TABLE_NAME */,
1429
- "errors.table_name_format",
1430
- { tableName }
1431
- );
1755
+ this._context = context;
1756
+ this.isNativeWindow = false;
1757
+ this.initialized = true;
1758
+ this.notifySubscribersInternal();
1759
+ this.resolveReady();
1760
+ }
1761
+ // ==========================================================================
1762
+ // Private: Event Handling
1763
+ // ==========================================================================
1764
+ handleEvent(event) {
1765
+ processEvent(
1766
+ event,
1767
+ this.log.bind(this),
1768
+ this.eventListeners,
1769
+ (ctx) => {
1770
+ this._context = ctx;
1771
+ this.notifySubscribersInternal();
1772
+ },
1773
+ (extEvent) => this.handleExternalRequestInternal(extEvent.data)
1774
+ );
1775
+ }
1776
+ async handleExternalRequestInternal(request) {
1777
+ await handleExternalRequest(request, this.externalRequestHandlers, this.respondToExternalRequest.bind(this), this.log.bind(this));
1778
+ }
1779
+ // ==========================================================================
1780
+ // Private: Setup
1781
+ // ==========================================================================
1782
+ async runSetupAsync() {
1783
+ if (!this.setupHook) return;
1784
+ try {
1785
+ this.log("[HaexVault] Running setup hook...");
1786
+ await this.setupHook();
1787
+ this._setupCompleted = true;
1788
+ this.log("[HaexVault] Setup completed successfully");
1789
+ this.notifySubscribersInternal();
1790
+ } catch (error) {
1791
+ this.log("[HaexVault] Setup failed:", error);
1792
+ throw error;
1432
1793
  }
1433
1794
  }
1795
+ // ==========================================================================
1796
+ // Private: Utilities
1797
+ // ==========================================================================
1798
+ notifySubscribersInternal() {
1799
+ notifySubscribers(this.reactiveSubscribers);
1800
+ }
1434
1801
  log(...args) {
1435
1802
  if (this.config.debug) {
1436
- console.log("[HaexSpace SDK]", ...args);
1803
+ console.log("[HaexVault SDK]", ...args);
1437
1804
  }
1438
1805
  }
1439
1806
  };