@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.
package/dist/vue.mjs CHANGED
@@ -335,9 +335,47 @@ var HAEXTENSION_EVENTS = {
335
335
  /** Context (theme, locale, platform) has changed */
336
336
  CONTEXT_CHANGED: "haextension:context:changed",
337
337
  /** Search request from HaexHub */
338
- SEARCH_REQUEST: "haextension:search:request",
339
- /** External request from authorized client (browser extension, CLI, server, etc.) */
340
- EXTERNAL_REQUEST: "haextension:external:request"
338
+ SEARCH_REQUEST: "haextension:search:request"
339
+ };
340
+
341
+ // src/types.ts
342
+ var DEFAULT_TIMEOUT = 3e4;
343
+ var TABLE_SEPARATOR = "__";
344
+ function getTableName(publicKey, extensionName, tableName) {
345
+ return `${publicKey}${TABLE_SEPARATOR}${extensionName}${TABLE_SEPARATOR}${tableName}`;
346
+ }
347
+ var HaexVaultSdkError = class extends Error {
348
+ constructor(code, messageKey, details) {
349
+ super(messageKey);
350
+ this.code = code;
351
+ this.messageKey = messageKey;
352
+ this.details = details;
353
+ this.name = "HaexVaultSdkError";
354
+ }
355
+ /**
356
+ * Get localized error message
357
+ * @param locale - Locale code (e.g., 'en', 'de')
358
+ * @param translations - Translation object
359
+ */
360
+ getLocalizedMessage(locale = "en", translations) {
361
+ if (!translations || !translations[locale]) {
362
+ return this.messageKey;
363
+ }
364
+ let message = translations[locale][this.messageKey] || this.messageKey;
365
+ if (this.details) {
366
+ Object.entries(this.details).forEach(([key, value]) => {
367
+ message = message.replace(`{${key}}`, String(value));
368
+ });
369
+ }
370
+ return message;
371
+ }
372
+ toJSON() {
373
+ return {
374
+ code: this.code,
375
+ message: this.messageKey,
376
+ details: this.details
377
+ };
378
+ }
341
379
  };
342
380
 
343
381
  // src/methods.ts
@@ -401,46 +439,6 @@ var HAEXTENSION_METHODS = {
401
439
  }
402
440
  };
403
441
 
404
- // src/types.ts
405
- var DEFAULT_TIMEOUT = 3e4;
406
- var TABLE_SEPARATOR = "__";
407
- function getTableName(publicKey, extensionName, tableName) {
408
- return `${publicKey}${TABLE_SEPARATOR}${extensionName}${TABLE_SEPARATOR}${tableName}`;
409
- }
410
- var HaexHubError = class extends Error {
411
- constructor(code, messageKey, details) {
412
- super(messageKey);
413
- this.code = code;
414
- this.messageKey = messageKey;
415
- this.details = details;
416
- this.name = "HaexHubError";
417
- }
418
- /**
419
- * Get localized error message
420
- * @param locale - Locale code (e.g., 'en', 'de')
421
- * @param translations - Translation object
422
- */
423
- getLocalizedMessage(locale = "en", translations) {
424
- if (!translations || !translations[locale]) {
425
- return this.messageKey;
426
- }
427
- let message = translations[locale][this.messageKey] || this.messageKey;
428
- if (this.details) {
429
- Object.entries(this.details).forEach(([key, value]) => {
430
- message = message.replace(`{${key}}`, String(value));
431
- });
432
- }
433
- return message;
434
- }
435
- toJSON() {
436
- return {
437
- code: this.code,
438
- message: this.messageKey,
439
- details: this.details
440
- };
441
- }
442
- };
443
-
444
442
  // src/api/storage.ts
445
443
  var StorageAPI = class {
446
444
  constructor(client) {
@@ -983,548 +981,543 @@ var PermissionsAPI = class {
983
981
  return response.status === "granted";
984
982
  }
985
983
  };
986
- var HaexVaultClient = class {
987
- constructor(config = {}) {
988
- this.pendingRequests = /* @__PURE__ */ new Map();
989
- this.eventListeners = /* @__PURE__ */ new Map();
990
- this.externalRequestHandlers = /* @__PURE__ */ new Map();
991
- this.messageHandler = null;
992
- this.initialized = false;
993
- this.requestCounter = 0;
994
- this._extensionInfo = null;
995
- this._context = null;
996
- this.reactiveSubscribers = /* @__PURE__ */ new Set();
997
- this.isNativeWindow = false;
998
- // Wird im Konstruktor initialisiert
999
- this.setupPromise = null;
1000
- this.setupHook = null;
1001
- this._setupCompleted = false;
1002
- this.orm = null;
1003
- this.config = {
1004
- debug: config.debug ?? false,
1005
- timeout: config.timeout ?? DEFAULT_TIMEOUT,
1006
- manifest: config.manifest
1007
- };
1008
- this.storage = new StorageAPI(this);
1009
- this.database = new DatabaseAPI(this);
1010
- this.filesystem = new FilesystemAPI(this);
1011
- this.web = new WebAPI(this);
1012
- this.permissions = new PermissionsAPI(this);
1013
- installConsoleForwarding(this.config.debug);
1014
- this.readyPromise = new Promise((resolve) => {
1015
- this.resolveReady = resolve;
1016
- });
1017
- this.init();
984
+
985
+ // src/client/tableName.ts
986
+ function validatePublicKey(publicKey) {
987
+ if (!publicKey || typeof publicKey !== "string" || publicKey.trim() === "") {
988
+ throw new HaexVaultSdkError(
989
+ "INVALID_PUBLIC_KEY" /* INVALID_PUBLIC_KEY */,
990
+ "errors.invalid_public_key",
991
+ { publicKey }
992
+ );
1018
993
  }
1019
- /**
1020
- * Gibt ein Promise zurück, das aufgelöst wird, sobald der Client
1021
- * initialisiert ist und Extension-Infos empfangen hat.
1022
- */
1023
- async ready() {
1024
- return this.readyPromise;
994
+ }
995
+ function validateExtensionName(extensionName) {
996
+ if (!extensionName || !/^[a-z][a-z0-9-]*$/i.test(extensionName)) {
997
+ throw new HaexVaultSdkError(
998
+ "INVALID_EXTENSION_NAME" /* INVALID_EXTENSION_NAME */,
999
+ "errors.invalid_extension_name",
1000
+ { extensionName }
1001
+ );
1025
1002
  }
1026
- /**
1027
- * Gibt zurück, ob das Setup bereits abgeschlossen wurde.
1028
- */
1029
- get setupCompleted() {
1030
- return this._setupCompleted;
1003
+ if (extensionName.includes(TABLE_SEPARATOR)) {
1004
+ throw new HaexVaultSdkError(
1005
+ "INVALID_EXTENSION_NAME" /* INVALID_EXTENSION_NAME */,
1006
+ "errors.extension_name_contains_separator",
1007
+ { extensionName, separator: TABLE_SEPARATOR }
1008
+ );
1031
1009
  }
1032
- /**
1033
- * Registriert eine Setup-Funktion, die nach der Initialisierung ausgeführt wird.
1034
- * Diese Funktion sollte für Aufgaben wie Tabellenerstellung, Migrationen, etc. verwendet werden.
1035
- * @param setupFn Die Setup-Funktion, die ausgeführt werden soll
1036
- */
1037
- onSetup(setupFn) {
1038
- if (this.setupHook) {
1039
- throw new Error("Setup hook already registered");
1040
- }
1041
- this.setupHook = setupFn;
1010
+ }
1011
+ function validateTableName(tableName) {
1012
+ if (!tableName || typeof tableName !== "string") {
1013
+ throw new HaexVaultSdkError(
1014
+ "INVALID_TABLE_NAME" /* INVALID_TABLE_NAME */,
1015
+ "errors.table_name_empty"
1016
+ );
1042
1017
  }
1043
- /**
1044
- * Gibt ein Promise zurück, das aufgelöst wird, sobald der Client vollständig eingerichtet ist.
1045
- * Dies umfasst die Initialisierung UND das Setup (z.B. Tabellenerstellung).
1046
- * Falls kein Setup-Hook registriert wurde, entspricht dies ready().
1047
- */
1048
- async setupComplete() {
1049
- await this.readyPromise;
1050
- if (!this.setupHook || this.setupCompleted) {
1051
- return;
1052
- }
1053
- if (!this.setupPromise) {
1054
- this.setupPromise = this.runSetupAsync();
1055
- }
1056
- return this.setupPromise;
1018
+ if (tableName.includes(TABLE_SEPARATOR)) {
1019
+ throw new HaexVaultSdkError(
1020
+ "INVALID_TABLE_NAME" /* INVALID_TABLE_NAME */,
1021
+ "errors.table_name_contains_separator",
1022
+ { tableName, separator: TABLE_SEPARATOR }
1023
+ );
1057
1024
  }
1058
- async runSetupAsync() {
1059
- if (!this.setupHook) return;
1060
- try {
1061
- this.log("[HaexSpace] Running setup hook...");
1062
- await this.setupHook();
1063
- this._setupCompleted = true;
1064
- this.log("[HaexSpace] Setup completed successfully");
1065
- this.notifySubscribers();
1066
- } catch (error) {
1067
- this.log("[HaexSpace] Setup failed:", error);
1068
- throw error;
1069
- }
1025
+ if (!/^[a-z][a-z0-9-_]*$/i.test(tableName)) {
1026
+ throw new HaexVaultSdkError(
1027
+ "INVALID_TABLE_NAME" /* INVALID_TABLE_NAME */,
1028
+ "errors.table_name_format",
1029
+ { tableName }
1030
+ );
1070
1031
  }
1071
- /**
1072
- * Initialisiert die Drizzle-Datenbankinstanz.
1073
- * Muss nach der Definition des Schemas aufgerufen werden.
1074
- * @param schema Das Drizzle-Schemaobjekt (mit bereits geprefixten Tabellennamen).
1075
- * @returns Die typsichere Drizzle-Datenbankinstanz.
1076
- */
1077
- initializeDatabase(schema) {
1078
- if (!this._extensionInfo) {
1079
- throw new HaexHubError(
1080
- "EXTENSION_INFO_UNAVAILABLE" /* EXTENSION_INFO_UNAVAILABLE */,
1081
- "errors.client_not_ready"
1082
- );
1083
- }
1084
- const dbInstance = drizzle(
1085
- async (sql, params, method) => {
1086
- try {
1087
- if (method === "run" || method === "all") {
1088
- const result2 = await this.request(
1089
- HAEXTENSION_METHODS.database.execute,
1090
- {
1091
- query: sql,
1092
- params
1093
- }
1094
- );
1095
- if (method === "all") {
1096
- return { rows: result2.rows || [] };
1097
- }
1098
- if (result2.rows && Array.isArray(result2.rows) && result2.rows.length > 0) {
1099
- return { rows: result2.rows };
1100
- }
1101
- return result2;
1102
- }
1103
- const result = await this.request(HAEXTENSION_METHODS.database.query, {
1104
- query: sql,
1105
- params
1106
- });
1107
- const rows = result.rows;
1108
- if (method === "get") {
1109
- return { rows: rows.length > 0 ? rows.at(0) : void 0 };
1110
- }
1111
- return { rows };
1112
- } catch (error) {
1113
- this.log("Database operation failed:", error);
1114
- throw error;
1115
- }
1116
- },
1117
- {
1118
- schema,
1119
- logger: false
1120
- }
1032
+ }
1033
+ function getExtensionTableName(extensionInfo2, tableName) {
1034
+ if (!extensionInfo2) {
1035
+ throw new HaexVaultSdkError(
1036
+ "EXTENSION_INFO_UNAVAILABLE" /* EXTENSION_INFO_UNAVAILABLE */,
1037
+ "errors.extension_info_unavailable"
1121
1038
  );
1122
- this.orm = dbInstance;
1123
- return dbInstance;
1124
1039
  }
1125
- get extensionInfo() {
1126
- return this._extensionInfo;
1040
+ validateTableName(tableName);
1041
+ const { publicKey, name } = extensionInfo2;
1042
+ return `"${getTableName(publicKey, name, tableName)}"`;
1043
+ }
1044
+ function getDependencyTableName(publicKey, extensionName, tableName) {
1045
+ validatePublicKey(publicKey);
1046
+ validateExtensionName(extensionName);
1047
+ validateTableName(tableName);
1048
+ return `"${getTableName(publicKey, extensionName, tableName)}"`;
1049
+ }
1050
+ function parseTableName(fullTableName) {
1051
+ let cleanTableName = fullTableName;
1052
+ if (cleanTableName.startsWith('"') && cleanTableName.endsWith('"')) {
1053
+ cleanTableName = cleanTableName.slice(1, -1);
1127
1054
  }
1128
- get context() {
1129
- return this._context;
1055
+ const parts = cleanTableName.split(TABLE_SEPARATOR);
1056
+ if (parts.length !== 3) {
1057
+ return null;
1130
1058
  }
1131
- subscribe(callback) {
1132
- this.reactiveSubscribers.add(callback);
1133
- return () => {
1134
- this.reactiveSubscribers.delete(callback);
1135
- };
1059
+ const [publicKey, extensionName, tableName] = parts;
1060
+ if (!publicKey || !extensionName || !tableName) {
1061
+ return null;
1136
1062
  }
1137
- notifySubscribers() {
1138
- this.reactiveSubscribers.forEach((callback) => callback());
1063
+ return {
1064
+ publicKey,
1065
+ extensionName,
1066
+ tableName
1067
+ };
1068
+ }
1069
+
1070
+ // src/client/init.ts
1071
+ function isInIframe() {
1072
+ return window.self !== window.top;
1073
+ }
1074
+ function hasTauri() {
1075
+ return typeof window.__TAURI__ !== "undefined";
1076
+ }
1077
+ function getTauriCore() {
1078
+ return window.__TAURI__.core;
1079
+ }
1080
+ function getTauriEvent() {
1081
+ return window.__TAURI__.event;
1082
+ }
1083
+ async function initNativeMode(ctx, log, onEvent, onContextChange) {
1084
+ const { invoke } = getTauriCore();
1085
+ const extensionInfo2 = await invoke("webview_extension_get_info");
1086
+ const context2 = await invoke("webview_extension_context_get");
1087
+ ctx.state.isNativeWindow = true;
1088
+ ctx.state.initialized = true;
1089
+ ctx.state.extensionInfo = extensionInfo2;
1090
+ ctx.state.context = context2;
1091
+ log("HaexVault SDK initialized in native WebViewWindow mode");
1092
+ log("Extension info:", extensionInfo2);
1093
+ log("Application context:", context2);
1094
+ await setupTauriEventListeners(ctx, log, onEvent, onContextChange);
1095
+ return { extensionInfo: extensionInfo2, context: context2 };
1096
+ }
1097
+ async function setupTauriEventListeners(ctx, log, onEvent, onContextChange) {
1098
+ const { listen } = getTauriEvent();
1099
+ console.log("[HaexVault SDK] Setting up Tauri event listener for:", HAEXTENSION_EVENTS.CONTEXT_CHANGED);
1100
+ try {
1101
+ await listen(HAEXTENSION_EVENTS.CONTEXT_CHANGED, (event) => {
1102
+ console.log("[HaexVault SDK] Received Tauri event:", HAEXTENSION_EVENTS.CONTEXT_CHANGED, event);
1103
+ log("Received context change event:", event);
1104
+ const payload = event.payload;
1105
+ if (payload?.context) {
1106
+ ctx.state.context = payload.context;
1107
+ console.log("[HaexVault SDK] Updated context to:", ctx.state.context);
1108
+ onContextChange(payload.context);
1109
+ onEvent({
1110
+ type: HAEXTENSION_EVENTS.CONTEXT_CHANGED,
1111
+ data: { context: ctx.state.context },
1112
+ timestamp: Date.now()
1113
+ });
1114
+ } else {
1115
+ console.warn("[HaexVault SDK] Event received but no context in payload:", event);
1116
+ }
1117
+ });
1118
+ console.log("[HaexVault SDK] Context change listener registered successfully");
1119
+ } catch (error) {
1120
+ console.error("[HaexVault SDK] Failed to setup context change listener:", error);
1121
+ log("Failed to setup context change listener:", error);
1139
1122
  }
1140
- async getDependencies() {
1141
- return this.request("extensions.getDependencies");
1123
+ try {
1124
+ await listen(HAEXTENSION_EVENTS.EXTERNAL_REQUEST, (event) => {
1125
+ log("Received external request event:", event);
1126
+ if (event.payload) {
1127
+ onEvent({
1128
+ type: HAEXTENSION_EVENTS.EXTERNAL_REQUEST,
1129
+ data: event.payload,
1130
+ timestamp: Date.now()
1131
+ });
1132
+ }
1133
+ });
1134
+ console.log("[HaexVault SDK] External request listener registered successfully");
1135
+ } catch (error) {
1136
+ console.error("[HaexVault SDK] Failed to setup external request listener:", error);
1137
+ log("Failed to setup external request listener:", error);
1142
1138
  }
1143
- getTableName(tableName) {
1144
- if (!this._extensionInfo) {
1145
- throw new HaexHubError(
1146
- "EXTENSION_INFO_UNAVAILABLE" /* EXTENSION_INFO_UNAVAILABLE */,
1147
- "errors.extension_info_unavailable"
1148
- );
1149
- }
1150
- this.validateTableName(tableName);
1151
- const { publicKey, name } = this._extensionInfo;
1152
- return `"${getTableName(publicKey, name, tableName)}"`;
1139
+ }
1140
+ async function initIframeMode(ctx, log, messageHandler, request) {
1141
+ if (!isInIframe()) {
1142
+ throw new HaexVaultSdkError("NOT_IN_IFRAME" /* NOT_IN_IFRAME */, "errors.not_in_iframe");
1143
+ }
1144
+ ctx.handlers.messageHandler = messageHandler;
1145
+ window.addEventListener("message", messageHandler);
1146
+ ctx.state.isNativeWindow = false;
1147
+ ctx.state.initialized = true;
1148
+ log("HaexVault SDK initialized in iframe mode");
1149
+ if (ctx.config.manifest) {
1150
+ ctx.state.extensionInfo = {
1151
+ publicKey: ctx.config.manifest.publicKey,
1152
+ name: ctx.config.manifest.name,
1153
+ version: ctx.config.manifest.version,
1154
+ displayName: ctx.config.manifest.name
1155
+ };
1156
+ log("Extension info loaded from manifest:", ctx.state.extensionInfo);
1153
1157
  }
1154
- getDependencyTableName(publicKey, extensionName, tableName) {
1155
- this.validatePublicKey(publicKey);
1156
- this.validateExtensionName(extensionName);
1157
- this.validateTableName(tableName);
1158
- return `"${getTableName(publicKey, extensionName, tableName)}"`;
1158
+ sendDebugInfo(ctx.config);
1159
+ const context2 = await request(HAEXTENSION_METHODS.context.get);
1160
+ ctx.state.context = context2;
1161
+ log("Application context received:", context2);
1162
+ return { context: context2 };
1163
+ }
1164
+ function sendDebugInfo(config) {
1165
+ if (!config.debug) return;
1166
+ if (typeof window === "undefined" || !window.parent) return;
1167
+ const debugInfo = `SDK Debug:
1168
+ window.parent exists: ${!!window.parent}
1169
+ window.parent === window: ${window.parent === window}
1170
+ window.self === window.top: ${window.self === window.top}`;
1171
+ try {
1172
+ window.parent.postMessage({
1173
+ type: HAEXSPACE_MESSAGE_TYPES.DEBUG,
1174
+ data: debugInfo
1175
+ }, "*");
1176
+ } catch (e) {
1177
+ alert(debugInfo + `
1178
+ postMessage error: ${e}`);
1159
1179
  }
1160
- parseTableName(fullTableName) {
1161
- let cleanTableName = fullTableName;
1162
- if (cleanTableName.startsWith('"') && cleanTableName.endsWith('"')) {
1163
- cleanTableName = cleanTableName.slice(1, -1);
1164
- }
1165
- const parts = cleanTableName.split(TABLE_SEPARATOR);
1166
- if (parts.length !== 3) {
1167
- return null;
1168
- }
1169
- const [publicKey, extensionName, tableName] = parts;
1170
- if (!publicKey || !extensionName || !tableName) {
1171
- return null;
1172
- }
1173
- return {
1174
- publicKey,
1175
- extensionName,
1176
- tableName
1177
- };
1180
+ }
1181
+
1182
+ // src/commands.ts
1183
+ var TAURI_COMMANDS = {
1184
+ database: {
1185
+ query: "webview_extension_db_query",
1186
+ execute: "webview_extension_db_execute",
1187
+ registerMigrations: "webview_extension_db_register_migrations"
1188
+ },
1189
+ permissions: {
1190
+ checkWeb: "webview_extension_check_web_permission",
1191
+ checkDatabase: "webview_extension_check_database_permission",
1192
+ checkFilesystem: "webview_extension_check_filesystem_permission"
1193
+ },
1194
+ web: {
1195
+ open: "webview_extension_web_open",
1196
+ fetch: "webview_extension_web_request"
1197
+ },
1198
+ filesystem: {
1199
+ saveFile: "webview_extension_fs_save_file",
1200
+ openFile: "webview_extension_fs_open_file",
1201
+ showImage: "webview_extension_fs_show_image"
1202
+ },
1203
+ external: {
1204
+ // Response handling (called by extensions)
1205
+ respond: "external_respond"},
1206
+ filesync: {
1207
+ // Spaces
1208
+ listSpaces: "filesync_list_spaces",
1209
+ createSpace: "filesync_create_space",
1210
+ deleteSpace: "filesync_delete_space",
1211
+ // Files
1212
+ listFiles: "filesync_list_files",
1213
+ getFile: "filesync_get_file",
1214
+ uploadFile: "filesync_upload_file",
1215
+ downloadFile: "filesync_download_file",
1216
+ deleteFile: "filesync_delete_file",
1217
+ // Backends
1218
+ listBackends: "filesync_list_backends",
1219
+ addBackend: "filesync_add_backend",
1220
+ removeBackend: "filesync_remove_backend",
1221
+ testBackend: "filesync_test_backend",
1222
+ // Sync Rules
1223
+ listSyncRules: "filesync_list_sync_rules",
1224
+ addSyncRule: "filesync_add_sync_rule",
1225
+ removeSyncRule: "filesync_remove_sync_rule",
1226
+ // Sync Operations
1227
+ getSyncStatus: "filesync_get_sync_status",
1228
+ triggerSync: "filesync_trigger_sync",
1229
+ pauseSync: "filesync_pause_sync",
1230
+ resumeSync: "filesync_resume_sync",
1231
+ // Conflict Resolution
1232
+ resolveConflict: "filesync_resolve_conflict",
1233
+ // UI Helpers
1234
+ selectFolder: "filesync_select_folder"
1178
1235
  }
1179
- /**
1180
- * Execute a raw SQL query (SELECT)
1181
- * Returns rows as an array of objects
1182
- */
1183
- async query(sql, params = []) {
1184
- const result = await this.request(
1185
- HAEXTENSION_METHODS.database.query,
1186
- { query: sql, params }
1187
- );
1188
- if (this.config.debug) {
1189
- console.log("[SDK query()] Raw result:", JSON.stringify(result, null, 2));
1190
- }
1191
- return result.rows;
1236
+ };
1237
+
1238
+ // src/transport/handlers/database.ts
1239
+ var databaseHandlers = {
1240
+ [HAEXTENSION_METHODS.database.query]: {
1241
+ command: TAURI_COMMANDS.database.query,
1242
+ args: (p) => ({
1243
+ query: p.query,
1244
+ params: p.params || []
1245
+ })
1246
+ },
1247
+ [HAEXTENSION_METHODS.database.execute]: {
1248
+ command: TAURI_COMMANDS.database.execute,
1249
+ args: (p) => ({
1250
+ query: p.query,
1251
+ params: p.params || []
1252
+ })
1253
+ },
1254
+ [HAEXTENSION_METHODS.database.registerMigrations]: {
1255
+ command: TAURI_COMMANDS.database.registerMigrations,
1256
+ args: (p) => ({
1257
+ extensionVersion: p.extensionVersion,
1258
+ migrations: p.migrations
1259
+ })
1192
1260
  }
1193
- /**
1194
- * Alias for query() - more intuitive for SELECT statements
1195
- */
1196
- async select(sql, params = []) {
1197
- return this.query(sql, params);
1261
+ };
1262
+
1263
+ // src/transport/handlers/permissions.ts
1264
+ var permissionsHandlers = {
1265
+ "permissions.web.check": {
1266
+ command: TAURI_COMMANDS.permissions.checkWeb,
1267
+ args: (p) => ({
1268
+ url: p.url
1269
+ })
1270
+ },
1271
+ "permissions.database.check": {
1272
+ command: TAURI_COMMANDS.permissions.checkDatabase,
1273
+ args: (p) => ({
1274
+ resource: p.resource,
1275
+ operation: p.operation
1276
+ })
1277
+ },
1278
+ "permissions.filesystem.check": {
1279
+ command: TAURI_COMMANDS.permissions.checkFilesystem,
1280
+ args: (p) => ({
1281
+ path: p.path,
1282
+ actionStr: p.action
1283
+ })
1198
1284
  }
1199
- /**
1200
- * Execute a raw SQL statement (INSERT, UPDATE, DELETE, CREATE, etc.)
1201
- * Returns rowsAffected and lastInsertId
1202
- */
1203
- async execute(sql, params = []) {
1204
- const result = await this.request(
1205
- HAEXTENSION_METHODS.database.execute,
1206
- { query: sql, params }
1207
- );
1208
- return {
1209
- rowsAffected: result.rowsAffected,
1210
- lastInsertId: result.lastInsertId
1211
- };
1285
+ };
1286
+
1287
+ // src/transport/handlers/web.ts
1288
+ var webHandlers = {
1289
+ [HAEXTENSION_METHODS.application.open]: {
1290
+ command: TAURI_COMMANDS.web.open,
1291
+ args: (p) => ({
1292
+ url: p.url
1293
+ })
1294
+ },
1295
+ [HAEXTENSION_METHODS.web.fetch]: {
1296
+ command: TAURI_COMMANDS.web.fetch,
1297
+ args: (p) => ({
1298
+ url: p.url,
1299
+ method: p.method,
1300
+ headers: p.headers,
1301
+ body: p.body
1302
+ })
1212
1303
  }
1213
- /**
1214
- * Registers and applies extension migrations with HaexVault
1215
- *
1216
- * HaexVault will:
1217
- * 1. Validate all SQL statements (ensure only extension's own tables are accessed)
1218
- * 2. Store migrations with applied_at = NULL
1219
- * 3. Query pending migrations sorted by name
1220
- * 4. Apply pending migrations and set up CRDT triggers
1221
- * 5. Mark successful migrations with applied_at timestamp
1222
- *
1223
- * @param extensionVersion - The version of the extension
1224
- * @param migrations - Array of migration objects with name and SQL content
1225
- * @returns Promise with migration result (applied count, already applied count, applied migration names)
1226
- */
1227
- async registerMigrationsAsync(extensionVersion, migrations) {
1228
- return this.database.registerMigrationsAsync(extensionVersion, migrations);
1229
- }
1230
- async requestDatabasePermission(request) {
1231
- return this.request("permissions.database.request", {
1232
- resource: request.resource,
1233
- operation: request.operation,
1234
- reason: request.reason
1235
- });
1236
- }
1237
- async checkDatabasePermission(resource, operation) {
1238
- const response = await this.request(
1239
- "permissions.database.check",
1240
- {
1241
- resource,
1242
- operation
1243
- }
1244
- );
1245
- return response.status === "granted";
1246
- }
1247
- async respondToSearch(requestId, results) {
1248
- await this.request("search.respond", {
1249
- requestId,
1250
- results
1251
- });
1252
- }
1253
- /**
1254
- * Register a handler for external requests (from browser extensions, CLI, servers, etc.)
1255
- *
1256
- * @param action - The action/method name to handle (e.g., "get-logins", "get-totp")
1257
- * @param handler - Function that processes the request and returns a response
1258
- * @returns Unsubscribe function to remove the handler
1259
- *
1260
- * @example
1261
- * ```typescript
1262
- * client.onExternalRequest("get-logins", async (request) => {
1263
- * const entries = await getMatchingEntries(request.payload.url);
1264
- * return {
1265
- * requestId: request.requestId,
1266
- * success: true,
1267
- * data: { entries }
1268
- * };
1269
- * });
1270
- * ```
1271
- */
1272
- onExternalRequest(action, handler) {
1273
- this.externalRequestHandlers.set(action, handler);
1274
- this.log(`[ExternalRequest] Registered handler for action: ${action}`);
1275
- return () => {
1276
- this.externalRequestHandlers.delete(action);
1277
- this.log(`[ExternalRequest] Unregistered handler for action: ${action}`);
1278
- };
1279
- }
1280
- /**
1281
- * Send a response to an external request back to haex-vault
1282
- * This is called internally after a handler processes a request
1283
- */
1284
- async respondToExternalRequest(response) {
1285
- await this.request("external.respond", response);
1286
- }
1287
- async request(method, params) {
1288
- const resolvedParams = params ?? {};
1289
- if (this.isNativeWindow && typeof window.__TAURI__ !== "undefined") {
1290
- return this.invoke(method, resolvedParams);
1291
- }
1292
- return this.postMessage(method, resolvedParams);
1293
- }
1294
- async postMessage(method, params) {
1295
- const requestId = this.generateRequestId();
1296
- const request = {
1297
- method,
1298
- params,
1299
- timestamp: Date.now()
1300
- };
1301
- return new Promise((resolve, reject) => {
1302
- const timeout = setTimeout(() => {
1303
- this.pendingRequests.delete(requestId);
1304
- reject(
1305
- new HaexHubError("TIMEOUT" /* TIMEOUT */, "errors.timeout", {
1306
- timeout: this.config.timeout
1307
- })
1308
- );
1309
- }, this.config.timeout);
1310
- this.pendingRequests.set(requestId, { resolve, reject, timeout });
1311
- const targetOrigin = "*";
1312
- if (this.config.debug) {
1313
- console.log("[SDK Debug] ========== Sending Request ==========");
1314
- console.log("[SDK Debug] Request ID:", requestId);
1315
- console.log("[SDK Debug] Method:", request.method);
1316
- console.log("[SDK Debug] Params:", request.params);
1317
- console.log("[SDK Debug] Target origin:", targetOrigin);
1318
- console.log("[SDK Debug] Extension info:", this._extensionInfo);
1319
- console.log("[SDK Debug] ========================================");
1320
- }
1321
- window.parent.postMessage({ id: requestId, ...request }, targetOrigin);
1322
- });
1323
- }
1324
- async invoke(method, params) {
1325
- const { invoke } = window.__TAURI__.core;
1326
- if (this.config.debug) {
1327
- console.log("[SDK Debug] ========== Invoke Request ==========");
1328
- console.log("[SDK Debug] Method:", method);
1329
- console.log("[SDK Debug] Params:", params);
1330
- console.log("[SDK Debug] =======================================");
1331
- }
1332
- switch (method) {
1333
- case HAEXTENSION_METHODS.database.query:
1334
- return invoke("webview_extension_db_query", {
1335
- query: params.query,
1336
- params: params.params || []
1337
- });
1338
- case HAEXTENSION_METHODS.database.execute:
1339
- return invoke("webview_extension_db_execute", {
1340
- query: params.query,
1341
- params: params.params || []
1342
- });
1343
- case "permissions.web.check":
1344
- return invoke("webview_extension_check_web_permission", {
1345
- url: params.url
1346
- });
1347
- case "permissions.database.check":
1348
- return invoke("webview_extension_check_database_permission", {
1349
- resource: params.resource,
1350
- operation: params.operation
1351
- });
1352
- case "permissions.filesystem.check":
1353
- return invoke("webview_extension_check_filesystem_permission", {
1354
- path: params.path,
1355
- actionStr: params.action
1356
- });
1357
- case HAEXTENSION_METHODS.application.open:
1358
- return invoke("webview_extension_web_open", {
1359
- url: params.url
1360
- });
1361
- case HAEXTENSION_METHODS.web.fetch:
1362
- return invoke("webview_extension_web_request", {
1363
- url: params.url,
1364
- method: params.method,
1365
- headers: params.headers,
1366
- body: params.body
1367
- });
1368
- case HAEXTENSION_METHODS.filesystem.saveFile:
1369
- return invoke("webview_extension_fs_save_file", {
1370
- data: params.data,
1371
- defaultPath: params.defaultPath,
1372
- title: params.title,
1373
- filters: params.filters
1374
- });
1375
- case HAEXTENSION_METHODS.filesystem.openFile:
1376
- return invoke("webview_extension_fs_open_file", {
1377
- data: params.data,
1378
- fileName: params.fileName
1379
- });
1380
- case HAEXTENSION_METHODS.database.registerMigrations:
1381
- return invoke("webview_extension_db_register_migrations", {
1382
- extensionVersion: params.extensionVersion,
1383
- migrations: params.migrations
1384
- });
1385
- case "external.respond":
1386
- return invoke("webview_extension_external_respond", {
1387
- requestId: params.requestId,
1388
- success: params.success,
1389
- data: params.data,
1390
- error: params.error
1391
- });
1392
- default:
1393
- throw new HaexHubError(
1394
- "METHOD_NOT_FOUND" /* METHOD_NOT_FOUND */,
1395
- "errors.method_not_found",
1396
- { method }
1397
- );
1398
- }
1399
- }
1400
- on(eventType, callback) {
1401
- if (!this.eventListeners.has(eventType)) {
1402
- this.eventListeners.set(eventType, /* @__PURE__ */ new Set());
1403
- }
1404
- this.eventListeners.get(eventType).add(callback);
1304
+ };
1305
+
1306
+ // src/transport/handlers/filesystem.ts
1307
+ var filesystemHandlers = {
1308
+ [HAEXTENSION_METHODS.filesystem.saveFile]: {
1309
+ command: TAURI_COMMANDS.filesystem.saveFile,
1310
+ args: (p) => ({
1311
+ data: p.data,
1312
+ defaultPath: p.defaultPath,
1313
+ title: p.title,
1314
+ filters: p.filters
1315
+ })
1316
+ },
1317
+ [HAEXTENSION_METHODS.filesystem.openFile]: {
1318
+ command: TAURI_COMMANDS.filesystem.openFile,
1319
+ args: (p) => ({
1320
+ data: p.data,
1321
+ fileName: p.fileName
1322
+ })
1323
+ },
1324
+ [HAEXTENSION_METHODS.filesystem.showImage]: {
1325
+ command: TAURI_COMMANDS.filesystem.showImage,
1326
+ args: (p) => ({
1327
+ dataUrl: p.dataUrl
1328
+ })
1405
1329
  }
1406
- off(eventType, callback) {
1407
- const listeners = this.eventListeners.get(eventType);
1408
- if (listeners) {
1409
- listeners.delete(callback);
1410
- }
1330
+ };
1331
+
1332
+ // src/transport/handlers/external.ts
1333
+ var externalHandlers = {
1334
+ "external.respond": {
1335
+ command: TAURI_COMMANDS.external.respond,
1336
+ args: (p) => ({
1337
+ requestId: p.requestId,
1338
+ success: p.success,
1339
+ data: p.data,
1340
+ error: p.error
1341
+ })
1411
1342
  }
1412
- destroy() {
1413
- if (this.messageHandler) {
1414
- window.removeEventListener("message", this.messageHandler);
1415
- }
1416
- this.pendingRequests.forEach(({ timeout }) => clearTimeout(timeout));
1417
- this.pendingRequests.clear();
1418
- this.eventListeners.clear();
1419
- this.initialized = false;
1420
- this.log("HaexHub SDK destroyed");
1343
+ };
1344
+
1345
+ // src/transport/handlers/filesync.ts
1346
+ var filesyncHandlers = {
1347
+ // ==========================================================================
1348
+ // Spaces
1349
+ // ==========================================================================
1350
+ [HAEXTENSION_METHODS.filesystem.sync.listSpaces]: {
1351
+ command: TAURI_COMMANDS.filesync.listSpaces,
1352
+ args: () => ({})
1353
+ },
1354
+ [HAEXTENSION_METHODS.filesystem.sync.createSpace]: {
1355
+ command: TAURI_COMMANDS.filesync.createSpace,
1356
+ args: (p) => ({ request: p })
1357
+ },
1358
+ [HAEXTENSION_METHODS.filesystem.sync.deleteSpace]: {
1359
+ command: TAURI_COMMANDS.filesync.deleteSpace,
1360
+ args: (p) => ({ spaceId: p.spaceId })
1361
+ },
1362
+ // ==========================================================================
1363
+ // Files
1364
+ // ==========================================================================
1365
+ [HAEXTENSION_METHODS.filesystem.sync.listFiles]: {
1366
+ command: TAURI_COMMANDS.filesync.listFiles,
1367
+ args: (p) => ({ request: p })
1368
+ },
1369
+ [HAEXTENSION_METHODS.filesystem.sync.getFile]: {
1370
+ command: TAURI_COMMANDS.filesync.getFile,
1371
+ args: (p) => ({ fileId: p.fileId })
1372
+ },
1373
+ [HAEXTENSION_METHODS.filesystem.sync.uploadFile]: {
1374
+ command: TAURI_COMMANDS.filesync.uploadFile,
1375
+ args: (p) => ({ request: p })
1376
+ },
1377
+ [HAEXTENSION_METHODS.filesystem.sync.downloadFile]: {
1378
+ command: TAURI_COMMANDS.filesync.downloadFile,
1379
+ args: (p) => ({ request: p })
1380
+ },
1381
+ [HAEXTENSION_METHODS.filesystem.sync.deleteFile]: {
1382
+ command: TAURI_COMMANDS.filesync.deleteFile,
1383
+ args: (p) => ({ fileId: p.fileId })
1384
+ },
1385
+ // ==========================================================================
1386
+ // Backends
1387
+ // ==========================================================================
1388
+ [HAEXTENSION_METHODS.filesystem.sync.listBackends]: {
1389
+ command: TAURI_COMMANDS.filesync.listBackends,
1390
+ args: () => ({})
1391
+ },
1392
+ [HAEXTENSION_METHODS.filesystem.sync.addBackend]: {
1393
+ command: TAURI_COMMANDS.filesync.addBackend,
1394
+ args: (p) => ({ request: p })
1395
+ },
1396
+ [HAEXTENSION_METHODS.filesystem.sync.removeBackend]: {
1397
+ command: TAURI_COMMANDS.filesync.removeBackend,
1398
+ args: (p) => ({ backendId: p.backendId })
1399
+ },
1400
+ [HAEXTENSION_METHODS.filesystem.sync.testBackend]: {
1401
+ command: TAURI_COMMANDS.filesync.testBackend,
1402
+ args: (p) => ({ backendId: p.backendId })
1403
+ },
1404
+ // ==========================================================================
1405
+ // Sync Rules
1406
+ // ==========================================================================
1407
+ [HAEXTENSION_METHODS.filesystem.sync.listSyncRules]: {
1408
+ command: TAURI_COMMANDS.filesync.listSyncRules,
1409
+ args: () => ({})
1410
+ },
1411
+ [HAEXTENSION_METHODS.filesystem.sync.addSyncRule]: {
1412
+ command: TAURI_COMMANDS.filesync.addSyncRule,
1413
+ args: (p) => ({ request: p })
1414
+ },
1415
+ [HAEXTENSION_METHODS.filesystem.sync.removeSyncRule]: {
1416
+ command: TAURI_COMMANDS.filesync.removeSyncRule,
1417
+ args: (p) => ({ ruleId: p.ruleId })
1418
+ },
1419
+ // ==========================================================================
1420
+ // Sync Operations
1421
+ // ==========================================================================
1422
+ [HAEXTENSION_METHODS.filesystem.sync.getSyncStatus]: {
1423
+ command: TAURI_COMMANDS.filesync.getSyncStatus,
1424
+ args: () => ({})
1425
+ },
1426
+ [HAEXTENSION_METHODS.filesystem.sync.triggerSync]: {
1427
+ command: TAURI_COMMANDS.filesync.triggerSync,
1428
+ args: () => ({})
1429
+ },
1430
+ [HAEXTENSION_METHODS.filesystem.sync.pauseSync]: {
1431
+ command: TAURI_COMMANDS.filesync.pauseSync,
1432
+ args: () => ({})
1433
+ },
1434
+ [HAEXTENSION_METHODS.filesystem.sync.resumeSync]: {
1435
+ command: TAURI_COMMANDS.filesync.resumeSync,
1436
+ args: () => ({})
1437
+ },
1438
+ // ==========================================================================
1439
+ // Conflict Resolution
1440
+ // ==========================================================================
1441
+ [HAEXTENSION_METHODS.filesystem.sync.resolveConflict]: {
1442
+ command: TAURI_COMMANDS.filesync.resolveConflict,
1443
+ args: (p) => ({ request: p })
1444
+ },
1445
+ // ==========================================================================
1446
+ // UI Helpers
1447
+ // ==========================================================================
1448
+ [HAEXTENSION_METHODS.filesystem.sync.selectFolder]: {
1449
+ command: TAURI_COMMANDS.filesync.selectFolder,
1450
+ args: () => ({})
1421
1451
  }
1422
- async init() {
1423
- if (this.initialized) return;
1424
- const isInIframe = window.self !== window.top;
1425
- if (!isInIframe) {
1426
- try {
1427
- if (typeof window.__TAURI__ !== "undefined") {
1428
- const { invoke } = window.__TAURI__.core;
1429
- this._extensionInfo = await invoke("webview_extension_get_info");
1430
- this._context = await invoke("webview_extension_context_get");
1431
- this.isNativeWindow = true;
1432
- this.initialized = true;
1433
- this.log("HaexHub SDK initialized in native WebViewWindow mode");
1434
- this.log("Extension info:", this._extensionInfo);
1435
- this.log("Application context:", this._context);
1436
- this.notifySubscribers();
1437
- const { listen } = window.__TAURI__.event;
1438
- console.log("[HaexSpace SDK] Setting up Tauri event listener for:", HAEXTENSION_EVENTS.CONTEXT_CHANGED);
1439
- try {
1440
- await listen(HAEXTENSION_EVENTS.CONTEXT_CHANGED, (event) => {
1441
- console.log("[HaexSpace SDK] Received Tauri event:", HAEXTENSION_EVENTS.CONTEXT_CHANGED, event);
1442
- this.log("Received context change event:", event);
1443
- if (event.payload?.context) {
1444
- this._context = event.payload.context;
1445
- console.log("[HaexSpace SDK] Updated context to:", this._context);
1446
- this.handleEvent({
1447
- type: HAEXTENSION_EVENTS.CONTEXT_CHANGED,
1448
- data: { context: this._context },
1449
- timestamp: Date.now()
1450
- });
1451
- } else {
1452
- console.warn("[HaexSpace SDK] Event received but no context in payload:", event);
1453
- }
1454
- });
1455
- console.log("[HaexSpace SDK] Context change listener registered successfully");
1456
- } catch (error) {
1457
- console.error("[HaexSpace SDK] Failed to setup context change listener:", error);
1458
- this.log("Failed to setup context change listener:", error);
1459
- }
1460
- try {
1461
- await listen(HAEXTENSION_EVENTS.EXTERNAL_REQUEST, (event) => {
1462
- this.log("Received external request event:", event);
1463
- if (event.payload) {
1464
- this.handleEvent({
1465
- type: HAEXTENSION_EVENTS.EXTERNAL_REQUEST,
1466
- data: event.payload,
1467
- timestamp: Date.now()
1468
- });
1469
- }
1470
- });
1471
- console.log("[HaexSpace SDK] External request listener registered successfully");
1472
- } catch (error) {
1473
- console.error("[HaexSpace SDK] Failed to setup external request listener:", error);
1474
- this.log("Failed to setup external request listener:", error);
1475
- }
1476
- this.resolveReady();
1477
- return;
1478
- }
1479
- } catch (error) {
1480
- this.log("Tauri commands failed, falling back to iframe mode", error);
1481
- }
1482
- }
1483
- if (window.self === window.top) {
1484
- throw new HaexHubError("NOT_IN_IFRAME" /* NOT_IN_IFRAME */, "errors.not_in_iframe");
1485
- }
1486
- this.messageHandler = this.handleMessage.bind(this);
1487
- window.addEventListener("message", this.messageHandler);
1488
- this.isNativeWindow = false;
1489
- this.initialized = true;
1490
- this.log("HaexSpace SDK initialized in iframe mode");
1491
- try {
1492
- if (this.config.manifest) {
1493
- this._extensionInfo = {
1494
- publicKey: this.config.manifest.publicKey,
1495
- name: this.config.manifest.name,
1496
- version: this.config.manifest.version,
1497
- displayName: this.config.manifest.name
1498
- };
1499
- this.log("Extension info loaded from manifest:", this._extensionInfo);
1500
- this.notifySubscribers();
1501
- }
1502
- if (typeof window !== "undefined" && window.parent) {
1503
- const debugInfo = `SDK Debug:
1504
- window.parent exists: ${!!window.parent}
1505
- window.parent === window: ${window.parent === window}
1506
- window.self === window.top: ${window.self === window.top}`;
1507
- try {
1508
- window.parent.postMessage({
1509
- type: HAEXSPACE_MESSAGE_TYPES.DEBUG,
1510
- data: debugInfo
1511
- }, "*");
1512
- } catch (e) {
1513
- alert(debugInfo + `
1514
- postMessage error: ${e}`);
1515
- }
1516
- }
1517
- this._context = await this.request(HAEXTENSION_METHODS.context.get);
1518
- this.log("Application context received:", this._context);
1519
- this.notifySubscribers();
1520
- this.resolveReady();
1521
- } catch (error) {
1522
- this.log("Failed to load extension info or context:", error);
1523
- throw error;
1452
+ };
1453
+
1454
+ // src/transport/handlers/index.ts
1455
+ var allHandlers = {
1456
+ ...databaseHandlers,
1457
+ ...permissionsHandlers,
1458
+ ...webHandlers,
1459
+ ...filesystemHandlers,
1460
+ ...externalHandlers,
1461
+ ...filesyncHandlers
1462
+ };
1463
+
1464
+ // src/client/transport.ts
1465
+ function generateRequestId(counter) {
1466
+ return `req_${counter}`;
1467
+ }
1468
+ function sendPostMessage(method, params, requestId, config, extensionInfo2, pendingRequests) {
1469
+ const request = {
1470
+ method,
1471
+ params,
1472
+ timestamp: Date.now()
1473
+ };
1474
+ return new Promise((resolve, reject) => {
1475
+ const timeout = setTimeout(() => {
1476
+ pendingRequests.delete(requestId);
1477
+ reject(
1478
+ new HaexVaultSdkError("TIMEOUT" /* TIMEOUT */, "errors.timeout", {
1479
+ timeout: config.timeout
1480
+ })
1481
+ );
1482
+ }, config.timeout);
1483
+ pendingRequests.set(requestId, { resolve, reject, timeout });
1484
+ const targetOrigin = "*";
1485
+ if (config.debug) {
1486
+ console.log("[SDK Debug] ========== Sending Request ==========");
1487
+ console.log("[SDK Debug] Request ID:", requestId);
1488
+ console.log("[SDK Debug] Method:", request.method);
1489
+ console.log("[SDK Debug] Params:", request.params);
1490
+ console.log("[SDK Debug] Target origin:", targetOrigin);
1491
+ console.log("[SDK Debug] Extension info:", extensionInfo2);
1492
+ console.log("[SDK Debug] ========================================");
1524
1493
  }
1525
- }
1526
- handleMessage(event) {
1527
- if (this.config.debug) {
1494
+ window.parent.postMessage({ id: requestId, ...request }, targetOrigin);
1495
+ });
1496
+ }
1497
+ async function sendInvoke(method, params, config, log) {
1498
+ const { invoke } = window.__TAURI__.core;
1499
+ if (config.debug) {
1500
+ console.log("[SDK Debug] ========== Invoke Request ==========");
1501
+ console.log("[SDK Debug] Method:", method);
1502
+ console.log("[SDK Debug] Params:", params);
1503
+ console.log("[SDK Debug] =======================================");
1504
+ }
1505
+ const handler = allHandlers[method];
1506
+ if (handler) {
1507
+ const args = handler.args(params);
1508
+ return invoke(handler.command, args);
1509
+ }
1510
+ throw new HaexVaultSdkError(
1511
+ "METHOD_NOT_FOUND" /* METHOD_NOT_FOUND */,
1512
+ "errors.method_not_found",
1513
+ { method }
1514
+ );
1515
+ }
1516
+
1517
+ // src/client/events.ts
1518
+ function createMessageHandler(config, pendingRequests, extensionInfo2, onEvent) {
1519
+ return (event) => {
1520
+ if (config.debug) {
1528
1521
  console.log("[SDK Debug] ========== Message Received ==========");
1529
1522
  console.log("[SDK Debug] Event origin:", event.origin);
1530
1523
  console.log(
@@ -1532,160 +1525,534 @@ postMessage error: ${e}`);
1532
1525
  event.source === window.parent ? "parent window" : "unknown"
1533
1526
  );
1534
1527
  console.log("[SDK Debug] Event data:", event.data);
1535
- console.log("[SDK Debug] Extension info loaded:", !!this._extensionInfo);
1528
+ console.log("[SDK Debug] Extension info loaded:", !!extensionInfo2());
1536
1529
  console.log(
1537
1530
  "[SDK Debug] Pending requests count:",
1538
- this.pendingRequests.size
1531
+ pendingRequests.size
1539
1532
  );
1540
1533
  }
1541
1534
  if (event.source !== window.parent) {
1542
- if (this.config.debug) {
1535
+ if (config.debug) {
1543
1536
  console.error("[SDK Debug] \u274C REJECTED: Message not from parent window!");
1544
1537
  }
1545
1538
  return;
1546
1539
  }
1547
1540
  const data = event.data;
1548
- if ("id" in data && this.pendingRequests.has(data.id)) {
1549
- if (this.config.debug) {
1541
+ if ("id" in data && pendingRequests.has(data.id)) {
1542
+ if (config.debug) {
1550
1543
  console.log("[SDK Debug] \u2705 Found pending request for ID:", data.id);
1551
1544
  }
1552
- const pending = this.pendingRequests.get(data.id);
1545
+ const pending = pendingRequests.get(data.id);
1553
1546
  clearTimeout(pending.timeout);
1554
- this.pendingRequests.delete(data.id);
1547
+ pendingRequests.delete(data.id);
1555
1548
  if (data.error) {
1556
- if (this.config.debug) {
1549
+ if (config.debug) {
1557
1550
  console.error("[SDK Debug] \u274C Request failed:", data.error);
1558
1551
  }
1559
1552
  pending.reject(data.error);
1560
1553
  } else {
1561
- if (this.config.debug) {
1554
+ if (config.debug) {
1562
1555
  console.log("[SDK Debug] \u2705 Request succeeded:", data.result);
1563
1556
  }
1564
1557
  pending.resolve(data.result);
1565
1558
  }
1566
1559
  return;
1567
1560
  }
1568
- if ("id" in data && !this.pendingRequests.has(data.id)) {
1569
- if (this.config.debug) {
1561
+ if ("id" in data && !pendingRequests.has(data.id)) {
1562
+ if (config.debug) {
1570
1563
  console.warn(
1571
1564
  "[SDK Debug] \u26A0\uFE0F Received response for unknown request ID:",
1572
1565
  data.id
1573
1566
  );
1574
1567
  console.warn(
1575
1568
  "[SDK Debug] Known IDs:",
1576
- Array.from(this.pendingRequests.keys())
1569
+ Array.from(pendingRequests.keys())
1577
1570
  );
1578
1571
  }
1579
1572
  }
1580
1573
  if ("type" in data && data.type) {
1581
- if (this.config.debug) {
1574
+ if (config.debug) {
1582
1575
  console.log("[SDK Debug] Event received:", data.type);
1583
1576
  }
1584
- this.handleEvent(data);
1577
+ onEvent(data);
1585
1578
  }
1586
- if (this.config.debug) {
1579
+ if (config.debug) {
1587
1580
  console.log("[SDK Debug] ========== End Message ==========");
1588
1581
  }
1582
+ };
1583
+ }
1584
+ function processEvent(event, log, eventListeners, onContextChanged, onExternalRequest) {
1585
+ if (event.type === HAEXTENSION_EVENTS.CONTEXT_CHANGED) {
1586
+ const contextEvent = event;
1587
+ onContextChanged(contextEvent.data.context);
1588
+ log("Context updated:", contextEvent.data.context);
1589
+ }
1590
+ if (event.type === HAEXTENSION_EVENTS.EXTERNAL_REQUEST) {
1591
+ const externalEvent = event;
1592
+ onExternalRequest(externalEvent);
1593
+ return;
1589
1594
  }
1590
- handleEvent(event) {
1591
- if (event.type === HAEXTENSION_EVENTS.CONTEXT_CHANGED) {
1592
- const contextEvent = event;
1593
- this._context = contextEvent.data.context;
1594
- this.log("Context updated:", this._context);
1595
- this.notifySubscribers();
1595
+ emitEvent(event, log, eventListeners);
1596
+ }
1597
+ function emitEvent(event, log, eventListeners) {
1598
+ log("Event received:", event);
1599
+ const listeners = eventListeners.get(event.type);
1600
+ if (listeners) {
1601
+ listeners.forEach((callback) => callback(event));
1602
+ }
1603
+ }
1604
+ function addEventListener(eventType, callback, eventListeners) {
1605
+ if (!eventListeners.has(eventType)) {
1606
+ eventListeners.set(eventType, /* @__PURE__ */ new Set());
1607
+ }
1608
+ eventListeners.get(eventType).add(callback);
1609
+ }
1610
+ function removeEventListener(eventType, callback, eventListeners) {
1611
+ const listeners = eventListeners.get(eventType);
1612
+ if (listeners) {
1613
+ listeners.delete(callback);
1614
+ }
1615
+ }
1616
+ function notifySubscribers(subscribers) {
1617
+ subscribers.forEach((callback) => callback());
1618
+ }
1619
+ function createDrizzleInstance(schema, extensionInfo2, request, log) {
1620
+ if (!extensionInfo2) {
1621
+ throw new HaexVaultSdkError(
1622
+ "EXTENSION_INFO_UNAVAILABLE" /* EXTENSION_INFO_UNAVAILABLE */,
1623
+ "errors.client_not_ready"
1624
+ );
1625
+ }
1626
+ return drizzle(
1627
+ async (sql, params, method) => {
1628
+ try {
1629
+ if (method === "run" || method === "all") {
1630
+ const result2 = await request(
1631
+ HAEXTENSION_METHODS.database.execute,
1632
+ {
1633
+ query: sql,
1634
+ params
1635
+ }
1636
+ );
1637
+ if (method === "all") {
1638
+ return { rows: result2.rows || [] };
1639
+ }
1640
+ if (result2.rows && Array.isArray(result2.rows) && result2.rows.length > 0) {
1641
+ return { rows: result2.rows };
1642
+ }
1643
+ return result2;
1644
+ }
1645
+ const result = await request(HAEXTENSION_METHODS.database.query, {
1646
+ query: sql,
1647
+ params
1648
+ });
1649
+ const rows = result.rows;
1650
+ if (method === "get") {
1651
+ return { rows: rows.length > 0 ? rows.at(0) : void 0 };
1652
+ }
1653
+ return { rows };
1654
+ } catch (error) {
1655
+ log("Database operation failed:", error);
1656
+ throw error;
1657
+ }
1658
+ },
1659
+ {
1660
+ schema,
1661
+ logger: false
1596
1662
  }
1597
- if (event.type === HAEXTENSION_EVENTS.EXTERNAL_REQUEST) {
1598
- const externalEvent = event;
1599
- this.handleExternalRequest(externalEvent.data);
1600
- return;
1663
+ );
1664
+ }
1665
+ async function queryRaw(sql, params, request, debug) {
1666
+ const result = await request(
1667
+ HAEXTENSION_METHODS.database.query,
1668
+ { query: sql, params }
1669
+ );
1670
+ if (debug) {
1671
+ console.log("[SDK query()] Raw result:", JSON.stringify(result, null, 2));
1672
+ }
1673
+ return result.rows;
1674
+ }
1675
+ async function executeRaw(sql, params, request) {
1676
+ const result = await request(
1677
+ HAEXTENSION_METHODS.database.execute,
1678
+ { query: sql, params }
1679
+ );
1680
+ return {
1681
+ rowsAffected: result.rowsAffected,
1682
+ lastInsertId: result.lastInsertId
1683
+ };
1684
+ }
1685
+
1686
+ // src/client/external.ts
1687
+ function registerExternalHandler(action, handler, handlers, log) {
1688
+ handlers.set(action, handler);
1689
+ log(`[ExternalRequest] Registered handler for action: ${action}`);
1690
+ return () => {
1691
+ handlers.delete(action);
1692
+ log(`[ExternalRequest] Unregistered handler for action: ${action}`);
1693
+ };
1694
+ }
1695
+ async function handleExternalRequest(request, handlers, respond, log) {
1696
+ log(`[ExternalRequest] Received request: ${request.action} from ${request.publicKey.substring(0, 20)}...`);
1697
+ const handler = handlers.get(request.action);
1698
+ if (!handler) {
1699
+ log(`[ExternalRequest] No handler for action: ${request.action}`);
1700
+ await respond({
1701
+ requestId: request.requestId,
1702
+ success: false,
1703
+ error: `No handler registered for action: ${request.action}`
1704
+ });
1705
+ return;
1706
+ }
1707
+ try {
1708
+ const response = await handler(request);
1709
+ await respond(response);
1710
+ log(`[ExternalRequest] Response sent for: ${request.action}`);
1711
+ } catch (error) {
1712
+ log(`[ExternalRequest] Handler error:`, error);
1713
+ await respond({
1714
+ requestId: request.requestId,
1715
+ success: false,
1716
+ error: error instanceof Error ? error.message : String(error)
1717
+ });
1718
+ }
1719
+ }
1720
+ async function respondToExternalRequest(response, request) {
1721
+ await request("external.respond", response);
1722
+ }
1723
+
1724
+ // src/client.ts
1725
+ var HaexVaultClient = class {
1726
+ constructor(config = {}) {
1727
+ // State
1728
+ this.initialized = false;
1729
+ this.isNativeWindow = false;
1730
+ this.requestCounter = 0;
1731
+ this._extensionInfo = null;
1732
+ this._context = null;
1733
+ this._setupCompleted = false;
1734
+ // Collections
1735
+ this.pendingRequests = /* @__PURE__ */ new Map();
1736
+ this.eventListeners = /* @__PURE__ */ new Map();
1737
+ this.externalRequestHandlers = /* @__PURE__ */ new Map();
1738
+ this.reactiveSubscribers = /* @__PURE__ */ new Set();
1739
+ // Handlers
1740
+ this.messageHandler = null;
1741
+ this.setupPromise = null;
1742
+ this.setupHook = null;
1743
+ // Public APIs
1744
+ this.orm = null;
1745
+ this.config = {
1746
+ debug: config.debug ?? false,
1747
+ timeout: config.timeout ?? DEFAULT_TIMEOUT,
1748
+ manifest: config.manifest
1749
+ };
1750
+ this.storage = new StorageAPI(this);
1751
+ this.database = new DatabaseAPI(this);
1752
+ this.filesystem = new FilesystemAPI(this);
1753
+ this.web = new WebAPI(this);
1754
+ this.permissions = new PermissionsAPI(this);
1755
+ installConsoleForwarding(this.config.debug);
1756
+ this.readyPromise = new Promise((resolve) => {
1757
+ this.resolveReady = resolve;
1758
+ });
1759
+ this.init();
1760
+ }
1761
+ // ==========================================================================
1762
+ // Lifecycle
1763
+ // ==========================================================================
1764
+ async ready() {
1765
+ return this.readyPromise;
1766
+ }
1767
+ get setupCompleted() {
1768
+ return this._setupCompleted;
1769
+ }
1770
+ onSetup(setupFn) {
1771
+ if (this.setupHook) {
1772
+ throw new Error("Setup hook already registered");
1601
1773
  }
1602
- this.emitEvent(event);
1603
- }
1604
- async handleExternalRequest(request) {
1605
- this.log(`[ExternalRequest] Received request: ${request.action} from ${request.publicKey.substring(0, 20)}...`);
1606
- const handler = this.externalRequestHandlers.get(request.action);
1607
- if (!handler) {
1608
- this.log(`[ExternalRequest] No handler for action: ${request.action}`);
1609
- await this.respondToExternalRequest({
1610
- requestId: request.requestId,
1611
- success: false,
1612
- error: `No handler registered for action: ${request.action}`
1613
- });
1774
+ this.setupHook = setupFn;
1775
+ }
1776
+ async setupComplete() {
1777
+ await this.readyPromise;
1778
+ if (!this.setupHook || this.setupCompleted) {
1614
1779
  return;
1615
1780
  }
1616
- try {
1617
- const response = await handler(request);
1618
- await this.respondToExternalRequest(response);
1619
- this.log(`[ExternalRequest] Response sent for: ${request.action}`);
1620
- } catch (error) {
1621
- this.log(`[ExternalRequest] Handler error:`, error);
1622
- await this.respondToExternalRequest({
1623
- requestId: request.requestId,
1624
- success: false,
1625
- error: error instanceof Error ? error.message : String(error)
1626
- });
1781
+ if (!this.setupPromise) {
1782
+ this.setupPromise = this.runSetupAsync();
1627
1783
  }
1784
+ return this.setupPromise;
1628
1785
  }
1629
- emitEvent(event) {
1630
- this.log("Event received:", event);
1631
- const listeners = this.eventListeners.get(event.type);
1632
- if (listeners) {
1633
- listeners.forEach((callback) => callback(event));
1786
+ destroy() {
1787
+ if (this.messageHandler) {
1788
+ window.removeEventListener("message", this.messageHandler);
1634
1789
  }
1790
+ this.pendingRequests.forEach(({ timeout }) => clearTimeout(timeout));
1791
+ this.pendingRequests.clear();
1792
+ this.eventListeners.clear();
1793
+ this.initialized = false;
1794
+ this.log("HaexVault SDK destroyed");
1635
1795
  }
1636
- generateRequestId() {
1637
- return `req_${++this.requestCounter}`;
1796
+ // ==========================================================================
1797
+ // Properties
1798
+ // ==========================================================================
1799
+ get extensionInfo() {
1800
+ return this._extensionInfo;
1638
1801
  }
1639
- validatePublicKey(publicKey) {
1640
- if (!publicKey || typeof publicKey !== "string" || publicKey.trim() === "") {
1641
- throw new HaexHubError(
1642
- "INVALID_PUBLIC_KEY" /* INVALID_PUBLIC_KEY */,
1643
- "errors.invalid_public_key",
1644
- { publicKey }
1645
- );
1646
- }
1802
+ get context() {
1803
+ return this._context;
1647
1804
  }
1648
- validateExtensionName(extensionName) {
1649
- if (!extensionName || !/^[a-z][a-z0-9-]*$/i.test(extensionName)) {
1650
- throw new HaexHubError(
1651
- "INVALID_EXTENSION_NAME" /* INVALID_EXTENSION_NAME */,
1652
- "errors.invalid_extension_name",
1653
- { extensionName }
1654
- );
1655
- }
1656
- if (extensionName.includes(TABLE_SEPARATOR)) {
1657
- throw new HaexHubError(
1658
- "INVALID_EXTENSION_NAME" /* INVALID_EXTENSION_NAME */,
1659
- "errors.extension_name_contains_separator",
1660
- { extensionName, separator: TABLE_SEPARATOR }
1661
- );
1805
+ // ==========================================================================
1806
+ // Subscriptions
1807
+ // ==========================================================================
1808
+ subscribe(callback) {
1809
+ this.reactiveSubscribers.add(callback);
1810
+ return () => {
1811
+ this.reactiveSubscribers.delete(callback);
1812
+ };
1813
+ }
1814
+ // ==========================================================================
1815
+ // Table Name Utilities
1816
+ // ==========================================================================
1817
+ getTableName(tableName) {
1818
+ return getExtensionTableName(this._extensionInfo, tableName);
1819
+ }
1820
+ getDependencyTableName(publicKey, extensionName, tableName) {
1821
+ return getDependencyTableName(publicKey, extensionName, tableName);
1822
+ }
1823
+ parseTableName(fullTableName) {
1824
+ return parseTableName(fullTableName);
1825
+ }
1826
+ // ==========================================================================
1827
+ // Database
1828
+ // ==========================================================================
1829
+ initializeDatabase(schema) {
1830
+ const db = createDrizzleInstance(schema, this._extensionInfo, this.request.bind(this), this.log.bind(this));
1831
+ this.orm = db;
1832
+ return db;
1833
+ }
1834
+ async query(sql, params = []) {
1835
+ return queryRaw(sql, params, this.request.bind(this), this.config.debug);
1836
+ }
1837
+ async select(sql, params = []) {
1838
+ return this.query(sql, params);
1839
+ }
1840
+ async execute(sql, params = []) {
1841
+ return executeRaw(sql, params, this.request.bind(this));
1842
+ }
1843
+ async registerMigrationsAsync(extensionVersion, migrations) {
1844
+ return this.database.registerMigrationsAsync(extensionVersion, migrations);
1845
+ }
1846
+ // ==========================================================================
1847
+ // Dependencies
1848
+ // ==========================================================================
1849
+ async getDependencies() {
1850
+ return this.request("extensions.getDependencies");
1851
+ }
1852
+ // ==========================================================================
1853
+ // Permissions
1854
+ // ==========================================================================
1855
+ async requestDatabasePermission(request) {
1856
+ return this.request("permissions.database.request", {
1857
+ resource: request.resource,
1858
+ operation: request.operation,
1859
+ reason: request.reason
1860
+ });
1861
+ }
1862
+ async checkDatabasePermission(resource, operation) {
1863
+ const response = await this.request("permissions.database.check", { resource, operation });
1864
+ return response.status === "granted";
1865
+ }
1866
+ // ==========================================================================
1867
+ // Search
1868
+ // ==========================================================================
1869
+ async respondToSearch(requestId, results) {
1870
+ await this.request("search.respond", { requestId, results });
1871
+ }
1872
+ // ==========================================================================
1873
+ // External Requests
1874
+ // ==========================================================================
1875
+ onExternalRequest(action, handler) {
1876
+ return registerExternalHandler(action, handler, this.externalRequestHandlers, this.log.bind(this));
1877
+ }
1878
+ async respondToExternalRequest(response) {
1879
+ await respondToExternalRequest(response, this.request.bind(this));
1880
+ }
1881
+ // ==========================================================================
1882
+ // Events
1883
+ // ==========================================================================
1884
+ on(eventType, callback) {
1885
+ addEventListener(eventType, callback, this.eventListeners);
1886
+ }
1887
+ off(eventType, callback) {
1888
+ removeEventListener(eventType, callback, this.eventListeners);
1889
+ }
1890
+ // ==========================================================================
1891
+ // Communication
1892
+ // ==========================================================================
1893
+ async request(method, params) {
1894
+ const resolvedParams = params ?? {};
1895
+ if (this.isNativeWindow && hasTauri()) {
1896
+ return sendInvoke(method, resolvedParams, this.config, this.log.bind(this));
1662
1897
  }
1898
+ const requestId = generateRequestId(++this.requestCounter);
1899
+ return sendPostMessage(method, resolvedParams, requestId, this.config, this._extensionInfo, this.pendingRequests);
1663
1900
  }
1664
- validateTableName(tableName) {
1665
- if (!tableName || typeof tableName !== "string") {
1666
- throw new HaexHubError(
1667
- "INVALID_TABLE_NAME" /* INVALID_TABLE_NAME */,
1668
- "errors.table_name_empty"
1669
- );
1901
+ // ==========================================================================
1902
+ // Private: Initialization
1903
+ // ==========================================================================
1904
+ async init() {
1905
+ if (this.initialized) return;
1906
+ if (!isInIframe() && hasTauri()) {
1907
+ try {
1908
+ await this.initNative();
1909
+ return;
1910
+ } catch (error) {
1911
+ this.log("Tauri commands failed, falling back to iframe mode", error);
1912
+ }
1670
1913
  }
1671
- if (tableName.includes(TABLE_SEPARATOR)) {
1672
- throw new HaexHubError(
1673
- "INVALID_TABLE_NAME" /* INVALID_TABLE_NAME */,
1674
- "errors.table_name_contains_separator",
1675
- { tableName, separator: TABLE_SEPARATOR }
1676
- );
1914
+ await this.initIframe();
1915
+ }
1916
+ async initNative() {
1917
+ const { extensionInfo: extensionInfo2, context: context2 } = await initNativeMode(
1918
+ {
1919
+ config: this.config,
1920
+ state: {
1921
+ initialized: this.initialized,
1922
+ isNativeWindow: this.isNativeWindow,
1923
+ requestCounter: this.requestCounter,
1924
+ setupCompleted: this._setupCompleted,
1925
+ extensionInfo: this._extensionInfo,
1926
+ context: this._context,
1927
+ orm: this.orm
1928
+ },
1929
+ collections: {
1930
+ pendingRequests: this.pendingRequests,
1931
+ eventListeners: this.eventListeners,
1932
+ externalRequestHandlers: this.externalRequestHandlers,
1933
+ reactiveSubscribers: this.reactiveSubscribers
1934
+ },
1935
+ promises: {
1936
+ readyPromise: this.readyPromise,
1937
+ resolveReady: this.resolveReady,
1938
+ setupPromise: this.setupPromise,
1939
+ setupHook: this.setupHook
1940
+ },
1941
+ handlers: {
1942
+ messageHandler: this.messageHandler
1943
+ }
1944
+ },
1945
+ this.log.bind(this),
1946
+ this.handleEvent.bind(this),
1947
+ (ctx) => {
1948
+ this._context = ctx;
1949
+ this.notifySubscribersInternal();
1950
+ }
1951
+ );
1952
+ this._extensionInfo = extensionInfo2;
1953
+ this._context = context2;
1954
+ this.isNativeWindow = true;
1955
+ this.initialized = true;
1956
+ this.notifySubscribersInternal();
1957
+ this.resolveReady();
1958
+ }
1959
+ async initIframe() {
1960
+ this.messageHandler = createMessageHandler(
1961
+ this.config,
1962
+ this.pendingRequests,
1963
+ () => this._extensionInfo,
1964
+ this.handleEvent.bind(this)
1965
+ );
1966
+ const { context: context2 } = await initIframeMode(
1967
+ {
1968
+ config: this.config,
1969
+ state: {
1970
+ initialized: this.initialized,
1971
+ isNativeWindow: this.isNativeWindow,
1972
+ requestCounter: this.requestCounter,
1973
+ setupCompleted: this._setupCompleted,
1974
+ extensionInfo: this._extensionInfo,
1975
+ context: this._context,
1976
+ orm: this.orm
1977
+ },
1978
+ collections: {
1979
+ pendingRequests: this.pendingRequests,
1980
+ eventListeners: this.eventListeners,
1981
+ externalRequestHandlers: this.externalRequestHandlers,
1982
+ reactiveSubscribers: this.reactiveSubscribers
1983
+ },
1984
+ promises: {
1985
+ readyPromise: this.readyPromise,
1986
+ resolveReady: this.resolveReady,
1987
+ setupPromise: this.setupPromise,
1988
+ setupHook: this.setupHook
1989
+ },
1990
+ handlers: {
1991
+ messageHandler: this.messageHandler
1992
+ }
1993
+ },
1994
+ this.log.bind(this),
1995
+ this.messageHandler,
1996
+ this.request.bind(this)
1997
+ );
1998
+ if (this.config.manifest) {
1999
+ this._extensionInfo = {
2000
+ publicKey: this.config.manifest.publicKey,
2001
+ name: this.config.manifest.name,
2002
+ version: this.config.manifest.version,
2003
+ displayName: this.config.manifest.name
2004
+ };
2005
+ this.notifySubscribersInternal();
1677
2006
  }
1678
- if (!/^[a-z][a-z0-9-_]*$/i.test(tableName)) {
1679
- throw new HaexHubError(
1680
- "INVALID_TABLE_NAME" /* INVALID_TABLE_NAME */,
1681
- "errors.table_name_format",
1682
- { tableName }
1683
- );
2007
+ this._context = context2;
2008
+ this.isNativeWindow = false;
2009
+ this.initialized = true;
2010
+ this.notifySubscribersInternal();
2011
+ this.resolveReady();
2012
+ }
2013
+ // ==========================================================================
2014
+ // Private: Event Handling
2015
+ // ==========================================================================
2016
+ handleEvent(event) {
2017
+ processEvent(
2018
+ event,
2019
+ this.log.bind(this),
2020
+ this.eventListeners,
2021
+ (ctx) => {
2022
+ this._context = ctx;
2023
+ this.notifySubscribersInternal();
2024
+ },
2025
+ (extEvent) => this.handleExternalRequestInternal(extEvent.data)
2026
+ );
2027
+ }
2028
+ async handleExternalRequestInternal(request) {
2029
+ await handleExternalRequest(request, this.externalRequestHandlers, this.respondToExternalRequest.bind(this), this.log.bind(this));
2030
+ }
2031
+ // ==========================================================================
2032
+ // Private: Setup
2033
+ // ==========================================================================
2034
+ async runSetupAsync() {
2035
+ if (!this.setupHook) return;
2036
+ try {
2037
+ this.log("[HaexVault] Running setup hook...");
2038
+ await this.setupHook();
2039
+ this._setupCompleted = true;
2040
+ this.log("[HaexVault] Setup completed successfully");
2041
+ this.notifySubscribersInternal();
2042
+ } catch (error) {
2043
+ this.log("[HaexVault] Setup failed:", error);
2044
+ throw error;
1684
2045
  }
1685
2046
  }
2047
+ // ==========================================================================
2048
+ // Private: Utilities
2049
+ // ==========================================================================
2050
+ notifySubscribersInternal() {
2051
+ notifySubscribers(this.reactiveSubscribers);
2052
+ }
1686
2053
  log(...args) {
1687
2054
  if (this.config.debug) {
1688
- console.log("[HaexSpace SDK]", ...args);
2055
+ console.log("[HaexVault SDK]", ...args);
1689
2056
  }
1690
2057
  }
1691
2058
  };