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