@surelle-ha/dead-fuse 1.0.6 → 1.0.10

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
@@ -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,49 @@ 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 clientHost = typeof window !== "undefined" ? window.location.origin : void 0;
254
+ const res = await fetch(
255
+ `${masterUrl.replace(/\/$/, "")}/api/project/${encodeURIComponent(
256
+ this.config.projectId
257
+ )}/clients/connect`,
258
+ {
259
+ method: "POST",
260
+ headers: { "Content-Type": "application/json" },
261
+ body: JSON.stringify({ token: this.config.token, clientId: this.clientId, host: clientHost })
262
+ }
263
+ );
264
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
265
+ if (!this.heartbeatTimer) {
266
+ this.heartbeatTimer = setInterval(() => {
267
+ if (this.destroyed) return;
268
+ void this._registerClient();
269
+ }, CLIENT_HEARTBEAT_INTERVAL);
270
+ }
271
+ } catch (err) {
272
+ console.warn("[DeadFuse] Could not register client with dashboard:", err);
273
+ }
274
+ }
275
+ async _unregisterClient() {
276
+ const masterUrl = this.config.master ?? DEFAULT_MASTER;
277
+ if (!masterUrl || !this.config.token) return;
278
+ try {
279
+ await fetch(
280
+ `${masterUrl.replace(/\/$/, "")}/api/project/${encodeURIComponent(
281
+ this.config.projectId
282
+ )}/clients/disconnect`,
283
+ {
284
+ method: "POST",
285
+ headers: { "Content-Type": "application/json" },
286
+ body: JSON.stringify({ token: this.config.token, clientId: this.clientId })
287
+ }
288
+ );
289
+ } catch {
290
+ }
291
+ }
241
292
  // ── Realtime subscription ──────────────────────────────────────────────────
242
293
  _subscribe() {
243
294
  if (!this.supabase) return;
@@ -254,6 +305,7 @@ var DeadFuseConnection = class {
254
305
  if (this.reconnectAttempts > 0) this.config.onReconnect?.();
255
306
  this.reconnectAttempts = 0;
256
307
  this.channel?.track({ projectId: this.config.projectId, ts: Date.now() });
308
+ void this._registerClient();
257
309
  }
258
310
  if (status === "CHANNEL_ERROR" || status === "TIMED_OUT") {
259
311
  console.warn(`[DeadFuse] Realtime channel error: ${status}`);
@@ -346,6 +398,11 @@ var DeadFuseConnection = class {
346
398
  clearTimeout(this.reconnectTimer);
347
399
  this.reconnectTimer = null;
348
400
  }
401
+ if (this.heartbeatTimer) {
402
+ clearInterval(this.heartbeatTimer);
403
+ this.heartbeatTimer = null;
404
+ }
405
+ void this._unregisterClient();
349
406
  if (this.channel && this.supabase) {
350
407
  this.supabase.removeChannel(this.channel);
351
408
  this.channel = null;
package/dist/index.mjs CHANGED
@@ -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,49 @@ 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 clientHost = typeof window !== "undefined" ? window.location.origin : void 0;
227
+ const res = await fetch(
228
+ `${masterUrl.replace(/\/$/, "")}/api/project/${encodeURIComponent(
229
+ this.config.projectId
230
+ )}/clients/connect`,
231
+ {
232
+ method: "POST",
233
+ headers: { "Content-Type": "application/json" },
234
+ body: JSON.stringify({ token: this.config.token, clientId: this.clientId, host: clientHost })
235
+ }
236
+ );
237
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
238
+ if (!this.heartbeatTimer) {
239
+ this.heartbeatTimer = setInterval(() => {
240
+ if (this.destroyed) return;
241
+ void this._registerClient();
242
+ }, CLIENT_HEARTBEAT_INTERVAL);
243
+ }
244
+ } catch (err) {
245
+ console.warn("[DeadFuse] Could not register client with dashboard:", err);
246
+ }
247
+ }
248
+ async _unregisterClient() {
249
+ const masterUrl = this.config.master ?? DEFAULT_MASTER;
250
+ if (!masterUrl || !this.config.token) return;
251
+ try {
252
+ await fetch(
253
+ `${masterUrl.replace(/\/$/, "")}/api/project/${encodeURIComponent(
254
+ this.config.projectId
255
+ )}/clients/disconnect`,
256
+ {
257
+ method: "POST",
258
+ headers: { "Content-Type": "application/json" },
259
+ body: JSON.stringify({ token: this.config.token, clientId: this.clientId })
260
+ }
261
+ );
262
+ } catch {
263
+ }
264
+ }
214
265
  // ── Realtime subscription ──────────────────────────────────────────────────
215
266
  _subscribe() {
216
267
  if (!this.supabase) return;
@@ -227,6 +278,7 @@ var DeadFuseConnection = class {
227
278
  if (this.reconnectAttempts > 0) this.config.onReconnect?.();
228
279
  this.reconnectAttempts = 0;
229
280
  this.channel?.track({ projectId: this.config.projectId, ts: Date.now() });
281
+ void this._registerClient();
230
282
  }
231
283
  if (status === "CHANNEL_ERROR" || status === "TIMED_OUT") {
232
284
  console.warn(`[DeadFuse] Realtime channel error: ${status}`);
@@ -319,6 +371,11 @@ var DeadFuseConnection = class {
319
371
  clearTimeout(this.reconnectTimer);
320
372
  this.reconnectTimer = null;
321
373
  }
374
+ if (this.heartbeatTimer) {
375
+ clearInterval(this.heartbeatTimer);
376
+ this.heartbeatTimer = null;
377
+ }
378
+ void this._unregisterClient();
322
379
  if (this.channel && this.supabase) {
323
380
  this.supabase.removeChannel(this.channel);
324
381
  this.channel = null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@surelle-ha/dead-fuse",
3
- "version": "1.0.6",
3
+ "version": "1.0.10",
4
4
  "description": "Lightweight license enforcement and remote control layer for deployed web applications",
5
5
  "publishConfig": {
6
6
  "access": "public"