@surelle-ha/dead-fuse 1.0.5 → 1.0.9

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.js CHANGED
@@ -29,7 +29,7 @@ module.exports = __toCommonJS(index_exports);
29
29
  var import_supabase_js = require("@supabase/supabase-js");
30
30
 
31
31
  // src/constants.ts
32
- var DEFAULT_MASTER = "https://dead-fuse-dashboard.vercel.app";
32
+ var DEFAULT_MASTER = "https://dead-fuse.surelle.xyz";
33
33
 
34
34
  // src/stateManager.ts
35
35
  var MUTATION_METHODS = /* @__PURE__ */ new Set(["POST", "PUT", "PATCH", "DELETE"]);
@@ -186,13 +186,16 @@ function dispatchStateEvent(state, message, config) {
186
186
  // src/connection.ts
187
187
  var MAX_RECONNECT_ATTEMPTS = 10;
188
188
  var BASE_RECONNECT_DELAY = 1e3;
189
+ var CLIENT_HEARTBEAT_INTERVAL = 25e3;
189
190
  var DeadFuseConnection = class {
190
191
  constructor(config) {
191
192
  this.supabase = null;
192
193
  this.channel = null;
193
194
  this.reconnectAttempts = 0;
194
195
  this.reconnectTimer = null;
196
+ this.heartbeatTimer = null;
195
197
  this.destroyed = false;
198
+ this.clientId = typeof crypto !== "undefined" && "randomUUID" in crypto ? crypto.randomUUID() : `df-client-${Math.random().toString(36).slice(2)}`;
196
199
  this.config = config;
197
200
  }
198
201
  connect() {
@@ -225,7 +228,12 @@ var DeadFuseConnection = class {
225
228
  supabaseUrl = json.supabaseUrl;
226
229
  supabaseAnonKey = json.supabaseAnonKey;
227
230
  } catch (err) {
228
- console.warn("[DeadFuse] Could not fetch config from dashboard:", err);
231
+ console.warn("[DeadFuse] Could not fetch config from dashboard:", err, "master:", masterUrl);
232
+ if (masterUrl.startsWith("wss://")) {
233
+ console.warn(
234
+ "[DeadFuse] The `master` value appears to be a websocket URL. `master` must be the dashboard base URL like https://your-dashboard.example.com"
235
+ );
236
+ }
229
237
  this._applyFallback();
230
238
  this._scheduleReconnect();
231
239
  return;
@@ -238,6 +246,48 @@ var DeadFuseConnection = class {
238
246
  this._subscribe();
239
247
  this._fetchInitialState();
240
248
  }
249
+ async _registerClient() {
250
+ const masterUrl = this.config.master ?? DEFAULT_MASTER;
251
+ if (!masterUrl || !this.config.token) return;
252
+ try {
253
+ const res = await fetch(
254
+ `${masterUrl.replace(/\/$/, "")}/api/project/${encodeURIComponent(
255
+ this.config.projectId
256
+ )}/clients/connect`,
257
+ {
258
+ method: "POST",
259
+ headers: { "Content-Type": "application/json" },
260
+ body: JSON.stringify({ token: this.config.token, clientId: this.clientId })
261
+ }
262
+ );
263
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
264
+ if (!this.heartbeatTimer) {
265
+ this.heartbeatTimer = setInterval(() => {
266
+ if (this.destroyed) return;
267
+ void this._registerClient();
268
+ }, CLIENT_HEARTBEAT_INTERVAL);
269
+ }
270
+ } catch (err) {
271
+ console.warn("[DeadFuse] Could not register client with dashboard:", err);
272
+ }
273
+ }
274
+ async _unregisterClient() {
275
+ const masterUrl = this.config.master ?? DEFAULT_MASTER;
276
+ if (!masterUrl || !this.config.token) return;
277
+ try {
278
+ await fetch(
279
+ `${masterUrl.replace(/\/$/, "")}/api/project/${encodeURIComponent(
280
+ this.config.projectId
281
+ )}/clients/disconnect`,
282
+ {
283
+ method: "POST",
284
+ headers: { "Content-Type": "application/json" },
285
+ body: JSON.stringify({ token: this.config.token, clientId: this.clientId })
286
+ }
287
+ );
288
+ } catch {
289
+ }
290
+ }
241
291
  // ── Realtime subscription ──────────────────────────────────────────────────
242
292
  _subscribe() {
243
293
  if (!this.supabase) return;
@@ -254,6 +304,7 @@ var DeadFuseConnection = class {
254
304
  if (this.reconnectAttempts > 0) this.config.onReconnect?.();
255
305
  this.reconnectAttempts = 0;
256
306
  this.channel?.track({ projectId: this.config.projectId, ts: Date.now() });
307
+ void this._registerClient();
257
308
  }
258
309
  if (status === "CHANNEL_ERROR" || status === "TIMED_OUT") {
259
310
  console.warn(`[DeadFuse] Realtime channel error: ${status}`);
@@ -346,6 +397,11 @@ var DeadFuseConnection = class {
346
397
  clearTimeout(this.reconnectTimer);
347
398
  this.reconnectTimer = null;
348
399
  }
400
+ if (this.heartbeatTimer) {
401
+ clearInterval(this.heartbeatTimer);
402
+ this.heartbeatTimer = null;
403
+ }
404
+ void this._unregisterClient();
349
405
  if (this.channel && this.supabase) {
350
406
  this.supabase.removeChannel(this.channel);
351
407
  this.channel = null;
package/dist/index.mjs CHANGED
@@ -2,7 +2,7 @@
2
2
  import { createClient } from "@supabase/supabase-js";
3
3
 
4
4
  // src/constants.ts
5
- var DEFAULT_MASTER = "https://dead-fuse-dashboard.vercel.app";
5
+ var DEFAULT_MASTER = "https://dead-fuse.surelle.xyz";
6
6
 
7
7
  // src/stateManager.ts
8
8
  var MUTATION_METHODS = /* @__PURE__ */ new Set(["POST", "PUT", "PATCH", "DELETE"]);
@@ -159,13 +159,16 @@ function dispatchStateEvent(state, message, config) {
159
159
  // src/connection.ts
160
160
  var MAX_RECONNECT_ATTEMPTS = 10;
161
161
  var BASE_RECONNECT_DELAY = 1e3;
162
+ var CLIENT_HEARTBEAT_INTERVAL = 25e3;
162
163
  var DeadFuseConnection = class {
163
164
  constructor(config) {
164
165
  this.supabase = null;
165
166
  this.channel = null;
166
167
  this.reconnectAttempts = 0;
167
168
  this.reconnectTimer = null;
169
+ this.heartbeatTimer = null;
168
170
  this.destroyed = false;
171
+ this.clientId = typeof crypto !== "undefined" && "randomUUID" in crypto ? crypto.randomUUID() : `df-client-${Math.random().toString(36).slice(2)}`;
169
172
  this.config = config;
170
173
  }
171
174
  connect() {
@@ -198,7 +201,12 @@ var DeadFuseConnection = class {
198
201
  supabaseUrl = json.supabaseUrl;
199
202
  supabaseAnonKey = json.supabaseAnonKey;
200
203
  } catch (err) {
201
- console.warn("[DeadFuse] Could not fetch config from dashboard:", err);
204
+ console.warn("[DeadFuse] Could not fetch config from dashboard:", err, "master:", masterUrl);
205
+ if (masterUrl.startsWith("wss://")) {
206
+ console.warn(
207
+ "[DeadFuse] The `master` value appears to be a websocket URL. `master` must be the dashboard base URL like https://your-dashboard.example.com"
208
+ );
209
+ }
202
210
  this._applyFallback();
203
211
  this._scheduleReconnect();
204
212
  return;
@@ -211,6 +219,48 @@ var DeadFuseConnection = class {
211
219
  this._subscribe();
212
220
  this._fetchInitialState();
213
221
  }
222
+ async _registerClient() {
223
+ const masterUrl = this.config.master ?? DEFAULT_MASTER;
224
+ if (!masterUrl || !this.config.token) return;
225
+ try {
226
+ const res = await fetch(
227
+ `${masterUrl.replace(/\/$/, "")}/api/project/${encodeURIComponent(
228
+ this.config.projectId
229
+ )}/clients/connect`,
230
+ {
231
+ method: "POST",
232
+ headers: { "Content-Type": "application/json" },
233
+ body: JSON.stringify({ token: this.config.token, clientId: this.clientId })
234
+ }
235
+ );
236
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
237
+ if (!this.heartbeatTimer) {
238
+ this.heartbeatTimer = setInterval(() => {
239
+ if (this.destroyed) return;
240
+ void this._registerClient();
241
+ }, CLIENT_HEARTBEAT_INTERVAL);
242
+ }
243
+ } catch (err) {
244
+ console.warn("[DeadFuse] Could not register client with dashboard:", err);
245
+ }
246
+ }
247
+ async _unregisterClient() {
248
+ const masterUrl = this.config.master ?? DEFAULT_MASTER;
249
+ if (!masterUrl || !this.config.token) return;
250
+ try {
251
+ await fetch(
252
+ `${masterUrl.replace(/\/$/, "")}/api/project/${encodeURIComponent(
253
+ this.config.projectId
254
+ )}/clients/disconnect`,
255
+ {
256
+ method: "POST",
257
+ headers: { "Content-Type": "application/json" },
258
+ body: JSON.stringify({ token: this.config.token, clientId: this.clientId })
259
+ }
260
+ );
261
+ } catch {
262
+ }
263
+ }
214
264
  // ── Realtime subscription ──────────────────────────────────────────────────
215
265
  _subscribe() {
216
266
  if (!this.supabase) return;
@@ -227,6 +277,7 @@ var DeadFuseConnection = class {
227
277
  if (this.reconnectAttempts > 0) this.config.onReconnect?.();
228
278
  this.reconnectAttempts = 0;
229
279
  this.channel?.track({ projectId: this.config.projectId, ts: Date.now() });
280
+ void this._registerClient();
230
281
  }
231
282
  if (status === "CHANNEL_ERROR" || status === "TIMED_OUT") {
232
283
  console.warn(`[DeadFuse] Realtime channel error: ${status}`);
@@ -319,6 +370,11 @@ var DeadFuseConnection = class {
319
370
  clearTimeout(this.reconnectTimer);
320
371
  this.reconnectTimer = null;
321
372
  }
373
+ if (this.heartbeatTimer) {
374
+ clearInterval(this.heartbeatTimer);
375
+ this.heartbeatTimer = null;
376
+ }
377
+ void this._unregisterClient();
322
378
  if (this.channel && this.supabase) {
323
379
  this.supabase.removeChannel(this.channel);
324
380
  this.channel = null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@surelle-ha/dead-fuse",
3
- "version": "1.0.5",
3
+ "version": "1.0.9",
4
4
  "description": "Lightweight license enforcement and remote control layer for deployed web applications",
5
5
  "publishConfig": {
6
6
  "access": "public"