mindcache 2.2.0 → 2.3.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/index.mjs CHANGED
@@ -15,16 +15,17 @@ var CloudAdapter_exports = {};
15
15
  __export(CloudAdapter_exports, {
16
16
  CloudAdapter: () => CloudAdapter
17
17
  });
18
- var DEFAULT_BASE_URL, RECONNECT_DELAY, MAX_RECONNECT_DELAY, CloudAdapter;
18
+ var RECONNECT_DELAY, MAX_RECONNECT_DELAY, CloudAdapter;
19
19
  var init_CloudAdapter = __esm({
20
20
  "src/cloud/CloudAdapter.ts"() {
21
- DEFAULT_BASE_URL = "wss://api.mindcache.io";
22
21
  RECONNECT_DELAY = 1e3;
23
22
  MAX_RECONNECT_DELAY = 3e4;
24
23
  CloudAdapter = class {
25
24
  constructor(config) {
26
25
  this.config = config;
27
- this.config.baseUrl = config.baseUrl || DEFAULT_BASE_URL;
26
+ if (!config.baseUrl) {
27
+ throw new Error("MindCache Cloud: baseUrl is required. Please provide the cloud API URL in your configuration.");
28
+ }
28
29
  }
29
30
  ws = null;
30
31
  queue = [];
@@ -85,6 +86,40 @@ var init_CloudAdapter = __esm({
85
86
  }
86
87
  this.mindcache = null;
87
88
  }
89
+ /**
90
+ * Fetch a short-lived WebSocket token from the API using the API key.
91
+ * This keeps the API key secure by only using it for a single HTTPS request,
92
+ * then using the short-lived token for the WebSocket connection.
93
+ *
94
+ * Supports two key formats:
95
+ * - API keys: mc_live_xxx or mc_test_xxx → Bearer token
96
+ * - Delegate keys: del_xxx:sec_xxx → ApiKey format
97
+ */
98
+ async fetchTokenWithApiKey() {
99
+ if (!this.config.apiKey) {
100
+ throw new Error("API key is required to fetch token");
101
+ }
102
+ const httpBaseUrl = this.config.baseUrl.replace("wss://", "https://").replace("ws://", "http://");
103
+ const isDelegate = this.config.apiKey.startsWith("del_") && this.config.apiKey.includes(":");
104
+ const authHeader = isDelegate ? `ApiKey ${this.config.apiKey}` : `Bearer ${this.config.apiKey}`;
105
+ const response = await fetch(`${httpBaseUrl}/api/ws-token`, {
106
+ method: "POST",
107
+ headers: {
108
+ "Content-Type": "application/json",
109
+ "Authorization": authHeader
110
+ },
111
+ body: JSON.stringify({
112
+ instanceId: this.config.instanceId,
113
+ permission: "write"
114
+ })
115
+ });
116
+ if (!response.ok) {
117
+ const error = await response.json().catch(() => ({ error: "Failed to get token" }));
118
+ throw new Error(error.error || `Failed to get WebSocket token: ${response.status}`);
119
+ }
120
+ const data = await response.json();
121
+ return data.token;
122
+ }
88
123
  /**
89
124
  * Connect to the cloud service
90
125
  */
@@ -94,13 +129,19 @@ var init_CloudAdapter = __esm({
94
129
  }
95
130
  this._state = "connecting";
96
131
  try {
97
- if (this.config.tokenProvider && !this.token) {
98
- this.token = await this.config.tokenProvider();
132
+ if (!this.token) {
133
+ if (this.config.tokenProvider) {
134
+ this.token = await this.config.tokenProvider();
135
+ } else if (this.config.apiKey) {
136
+ this.token = await this.fetchTokenWithApiKey();
137
+ }
99
138
  }
100
139
  let url = `${this.config.baseUrl}/sync/${this.config.instanceId}`;
101
140
  if (this.token) {
102
141
  url += `?token=${encodeURIComponent(this.token)}`;
103
142
  this.token = null;
143
+ } else {
144
+ throw new Error("MindCache Cloud: No authentication method available. Provide apiKey or tokenProvider.");
104
145
  }
105
146
  this.ws = new WebSocket(url);
106
147
  this.setupWebSocket();
@@ -162,12 +203,6 @@ var init_CloudAdapter = __esm({
162
203
  return;
163
204
  }
164
205
  this.ws.onopen = () => {
165
- if (this.config.apiKey) {
166
- this.ws.send(JSON.stringify({
167
- type: "auth",
168
- apiKey: this.config.apiKey
169
- }));
170
- }
171
206
  };
172
207
  this.ws.onmessage = (event) => {
173
208
  try {
@@ -175,6 +210,7 @@ var init_CloudAdapter = __esm({
175
210
  this.handleMessage(msg);
176
211
  } catch (error) {
177
212
  console.error("MindCache Cloud: Failed to parse message:", error);
213
+ console.error("Raw message:", typeof event.data === "string" ? event.data.slice(0, 200) : event.data);
178
214
  }
179
215
  };
180
216
  this.ws.onclose = () => {
@@ -182,10 +218,12 @@ var init_CloudAdapter = __esm({
182
218
  this.emit("disconnected");
183
219
  this.scheduleReconnect();
184
220
  };
185
- this.ws.onerror = (error) => {
221
+ this.ws.onerror = () => {
186
222
  this._state = "error";
187
- this.emit("error", new Error("WebSocket error"));
188
- console.error("MindCache Cloud: WebSocket error:", error);
223
+ const url = `${this.config.baseUrl}/sync/${this.config.instanceId}`;
224
+ console.error(`MindCache Cloud: WebSocket error connecting to ${url}`);
225
+ console.error("Check that the instance ID and API key are correct, and that the server is reachable.");
226
+ this.emit("error", new Error(`WebSocket connection failed to ${url}`));
189
227
  };
190
228
  }
191
229
  handleMessage(msg) {
@@ -262,15 +300,6 @@ var init_CloudAdapter = __esm({
262
300
  this.reconnectTimeout = setTimeout(async () => {
263
301
  this.reconnectTimeout = null;
264
302
  this.reconnectAttempts++;
265
- if (this.config.tokenProvider) {
266
- try {
267
- this.token = await this.config.tokenProvider();
268
- } catch (error) {
269
- console.error("MindCache Cloud: Failed to get token for reconnect:", error);
270
- this.emit("error", error);
271
- return;
272
- }
273
- }
274
303
  this.connect();
275
304
  }, delay);
276
305
  }
@@ -325,6 +354,7 @@ var MindCache = class {
325
354
  _cloudConfig = null;
326
355
  // Access level for system operations
327
356
  _accessLevel = "user";
357
+ _initPromise = null;
328
358
  constructor(options) {
329
359
  if (options?.accessLevel) {
330
360
  this._accessLevel = options.accessLevel;
@@ -333,7 +363,7 @@ var MindCache = class {
333
363
  this._cloudConfig = options.cloud;
334
364
  this._isLoaded = false;
335
365
  this._connectionState = "disconnected";
336
- this._initCloud();
366
+ this._initPromise = this._initCloud();
337
367
  }
338
368
  }
339
369
  /**
@@ -353,9 +383,11 @@ var MindCache = class {
353
383
  return;
354
384
  }
355
385
  try {
356
- const { CloudAdapter: CloudAdapter2 } = await Promise.resolve().then(() => (init_CloudAdapter(), CloudAdapter_exports));
357
- const configUrl = this._cloudConfig.baseUrl || "https://api.mindcache.io";
358
- const baseUrl = configUrl.replace("https://", "wss://").replace("http://", "ws://");
386
+ const CloudAdapter2 = await this._getCloudAdapterClass();
387
+ if (!this._cloudConfig.baseUrl) {
388
+ throw new Error("MindCache Cloud: baseUrl is required. Please provide the cloud API URL in your configuration.");
389
+ }
390
+ const baseUrl = this._cloudConfig.baseUrl.replace("https://", "wss://").replace("http://", "ws://");
359
391
  const adapter = new CloudAdapter2({
360
392
  instanceId: this._cloudConfig.instanceId,
361
393
  projectId: this._cloudConfig.projectId || "default",
@@ -423,12 +455,46 @@ var MindCache = class {
423
455
  get isLoaded() {
424
456
  return this._isLoaded;
425
457
  }
458
+ /**
459
+ * Protected method to load CloudAdapter class.
460
+ * Can be overridden/mocked for testing.
461
+ */
462
+ async _getCloudAdapterClass() {
463
+ const { CloudAdapter: CloudAdapter2 } = await Promise.resolve().then(() => (init_CloudAdapter(), CloudAdapter_exports));
464
+ return CloudAdapter2;
465
+ }
426
466
  /**
427
467
  * Check if this instance is connected to cloud
428
468
  */
429
469
  get isCloud() {
430
470
  return this._cloudConfig !== null;
431
471
  }
472
+ /**
473
+ * Wait for initial sync to complete (or resolve immediately if already synced/local).
474
+ * Useful for scripts or linear execution flows.
475
+ */
476
+ async waitForSync() {
477
+ if (this._isLoaded) {
478
+ return;
479
+ }
480
+ if (this._initPromise) {
481
+ await this._initPromise;
482
+ }
483
+ if (this._isLoaded) {
484
+ return;
485
+ }
486
+ return new Promise((resolve) => {
487
+ if (!this._cloudAdapter) {
488
+ resolve();
489
+ return;
490
+ }
491
+ const handler = () => {
492
+ this._cloudAdapter?.off("synced", handler);
493
+ resolve();
494
+ };
495
+ this._cloudAdapter.on("synced", handler);
496
+ });
497
+ }
432
498
  /**
433
499
  * Disconnect from cloud (if connected)
434
500
  */