@fluxstack/live-client 0.1.0 → 0.2.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.
@@ -31,8 +31,11 @@ var FluxstackLive = (() => {
31
31
  StateValidator: () => StateValidator,
32
32
  clearPersistedState: () => clearPersistedState,
33
33
  createBinaryChunkMessage: () => createBinaryChunkMessage,
34
+ getConnection: () => getConnection,
34
35
  getPersistedState: () => getPersistedState,
35
- persistState: () => persistState
36
+ onConnectionChange: () => onConnectionChange,
37
+ persistState: () => persistState,
38
+ useLive: () => useLive
36
39
  });
37
40
 
38
41
  // src/connection.ts
@@ -44,6 +47,7 @@ var FluxstackLive = (() => {
44
47
  __publicField(this, "reconnectTimeout", null);
45
48
  __publicField(this, "heartbeatInterval", null);
46
49
  __publicField(this, "componentCallbacks", /* @__PURE__ */ new Map());
50
+ __publicField(this, "binaryCallbacks", /* @__PURE__ */ new Map());
47
51
  __publicField(this, "pendingRequests", /* @__PURE__ */ new Map());
48
52
  __publicField(this, "stateListeners", /* @__PURE__ */ new Set());
49
53
  __publicField(this, "_state", {
@@ -123,6 +127,7 @@ var FluxstackLive = (() => {
123
127
  this.log("Connecting...", { url });
124
128
  try {
125
129
  const ws = new WebSocket(url);
130
+ ws.binaryType = "arraybuffer";
126
131
  this.ws = ws;
127
132
  ws.onopen = () => {
128
133
  this.log("Connected");
@@ -131,19 +136,34 @@ var FluxstackLive = (() => {
131
136
  this.startHeartbeat();
132
137
  };
133
138
  ws.onmessage = (event) => {
139
+ if (event.data instanceof ArrayBuffer) {
140
+ this.handleBinaryMessage(new Uint8Array(event.data));
141
+ return;
142
+ }
134
143
  try {
135
- const response = JSON.parse(event.data);
136
- this.log("Received", { type: response.type, componentId: response.componentId });
137
- this.handleMessage(response);
144
+ const parsed = JSON.parse(event.data);
145
+ if (Array.isArray(parsed)) {
146
+ for (const msg of parsed) {
147
+ this.log("Received", { type: msg.type, componentId: msg.componentId });
148
+ this.handleMessage(msg);
149
+ }
150
+ } else {
151
+ this.log("Received", { type: parsed.type, componentId: parsed.componentId });
152
+ this.handleMessage(parsed);
153
+ }
138
154
  } catch {
139
155
  this.log("Failed to parse message");
140
156
  this.setState({ error: "Failed to parse message" });
141
157
  }
142
158
  };
143
- ws.onclose = () => {
144
- this.log("Disconnected");
159
+ ws.onclose = (event) => {
160
+ this.log("Disconnected", { code: event.code, reason: event.reason });
145
161
  this.setState({ connected: false, connecting: false, connectionId: null });
146
162
  this.stopHeartbeat();
163
+ if (event.code === 4003) {
164
+ this.setState({ error: "Connection rejected: origin not allowed" });
165
+ return;
166
+ }
147
167
  this.attemptReconnect();
148
168
  };
149
169
  ws.onerror = () => {
@@ -318,6 +338,25 @@ var FluxstackLive = (() => {
318
338
  }
319
339
  });
320
340
  }
341
+ /** Parse and route a binary BINARY_STATE_DELTA frame */
342
+ handleBinaryMessage(buffer) {
343
+ if (buffer.length < 3 || buffer[0] !== 1) return;
344
+ const idLen = buffer[1];
345
+ if (buffer.length < 2 + idLen) return;
346
+ const componentId = new TextDecoder().decode(buffer.subarray(2, 2 + idLen));
347
+ const payload = buffer.subarray(2 + idLen);
348
+ const callback = this.binaryCallbacks.get(componentId);
349
+ if (callback) {
350
+ callback(payload);
351
+ }
352
+ }
353
+ /** Register a binary message handler for a component */
354
+ registerBinaryHandler(componentId, callback) {
355
+ this.binaryCallbacks.set(componentId, callback);
356
+ return () => {
357
+ this.binaryCallbacks.delete(componentId);
358
+ };
359
+ }
321
360
  /** Register a component message callback */
322
361
  registerComponent(componentId, callback) {
323
362
  this.log("Registering component", componentId);
@@ -353,6 +392,7 @@ var FluxstackLive = (() => {
353
392
  destroy() {
354
393
  this.disconnect();
355
394
  this.componentCallbacks.clear();
395
+ this.binaryCallbacks.clear();
356
396
  for (const [, req] of this.pendingRequests) {
357
397
  clearTimeout(req.timeout);
358
398
  req.reject(new Error("Connection destroyed"));
@@ -511,6 +551,21 @@ var FluxstackLive = (() => {
511
551
  }
512
552
  return response.result;
513
553
  }
554
+ /**
555
+ * Fire an action without waiting for a response (fire-and-forget).
556
+ * Useful for high-frequency operations like game input where the
557
+ * server doesn't need to send back a result.
558
+ */
559
+ fire(action, payload = {}) {
560
+ if (!this._mounted || !this._componentId) return;
561
+ this.connection.sendMessage({
562
+ type: "CALL_ACTION",
563
+ componentId: this._componentId,
564
+ action,
565
+ payload,
566
+ expectResponse: false
567
+ });
568
+ }
514
569
  // ── State ──
515
570
  /**
516
571
  * Subscribe to state changes.
@@ -523,6 +578,26 @@ var FluxstackLive = (() => {
523
578
  this.stateListeners.delete(callback);
524
579
  };
525
580
  }
581
+ /**
582
+ * Register a binary decoder for this component.
583
+ * When the server sends a BINARY_STATE_DELTA frame targeting this component,
584
+ * the decoder converts the raw payload into a delta object which is merged into state.
585
+ * Returns an unsubscribe function.
586
+ */
587
+ setBinaryDecoder(decoder) {
588
+ if (!this._componentId) {
589
+ throw new Error("Component must be mounted before setting binary decoder");
590
+ }
591
+ return this.connection.registerBinaryHandler(this._componentId, (payload) => {
592
+ try {
593
+ const delta = decoder(payload);
594
+ this._state = { ...this._state, ...delta };
595
+ this.notifyStateChange(this._state, delta);
596
+ } catch (e) {
597
+ console.error("Binary decode error:", e);
598
+ }
599
+ });
600
+ }
526
601
  /**
527
602
  * Subscribe to errors.
528
603
  * Returns an unsubscribe function.
@@ -1170,6 +1245,70 @@ var FluxstackLive = (() => {
1170
1245
  };
1171
1246
  }
1172
1247
  };
1248
+
1249
+ // src/index.ts
1250
+ var _sharedConnection = null;
1251
+ var _sharedConnectionUrl = null;
1252
+ var _statusListeners = /* @__PURE__ */ new Set();
1253
+ function getOrCreateConnection(url) {
1254
+ const resolvedUrl = url ?? `ws://${typeof location !== "undefined" ? location.host : "localhost:3000"}/api/live/ws`;
1255
+ if (_sharedConnection && _sharedConnectionUrl === resolvedUrl) {
1256
+ return _sharedConnection;
1257
+ }
1258
+ if (_sharedConnection) {
1259
+ _sharedConnection.destroy();
1260
+ }
1261
+ _sharedConnection = new LiveConnection({ url: resolvedUrl });
1262
+ _sharedConnectionUrl = resolvedUrl;
1263
+ _sharedConnection.onStateChange((state) => {
1264
+ for (const cb of _statusListeners) {
1265
+ cb(state.connected);
1266
+ }
1267
+ });
1268
+ return _sharedConnection;
1269
+ }
1270
+ function useLive(componentName, initialState, options = {}) {
1271
+ const { url, room, userId, autoMount = true, debug = false } = options;
1272
+ const connection = getOrCreateConnection(url);
1273
+ const handle = new LiveComponentHandle(connection, componentName, {
1274
+ initialState,
1275
+ room,
1276
+ userId,
1277
+ autoMount,
1278
+ debug
1279
+ });
1280
+ return {
1281
+ call: (action, payload) => handle.call(action, payload ?? {}),
1282
+ on: (callback) => handle.onStateChange(callback),
1283
+ onError: (callback) => handle.onError(callback),
1284
+ get state() {
1285
+ return handle.state;
1286
+ },
1287
+ get mounted() {
1288
+ return handle.mounted;
1289
+ },
1290
+ get componentId() {
1291
+ return handle.componentId;
1292
+ },
1293
+ get error() {
1294
+ return handle.error;
1295
+ },
1296
+ destroy: () => handle.destroy(),
1297
+ handle
1298
+ };
1299
+ }
1300
+ function onConnectionChange(callback) {
1301
+ _statusListeners.add(callback);
1302
+ if (_sharedConnection) {
1303
+ callback(_sharedConnection.state.connected);
1304
+ }
1305
+ return () => {
1306
+ _statusListeners.delete(callback);
1307
+ };
1308
+ }
1309
+ function getConnection(url) {
1310
+ return getOrCreateConnection(url);
1311
+ }
1173
1312
  return __toCommonJS(src_exports);
1174
1313
  })();
1175
1314
  //# sourceMappingURL=live-client.browser.global.js.map