@clockworkdog/cogs-client 2.3.0 → 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/README.md CHANGED
@@ -112,7 +112,7 @@ Initialize a [CogsConnection](https://clockwork-dog.github.io/cogs-client-lib/in
112
112
  ```ts
113
113
  let connected = false;
114
114
 
115
- import manifest from './cogs-plugin-manifest.js'; // Requires `"allowJs": true` in `tsconfig.json`
115
+ import * as manifest from './cogs-plugin-manifest.js'; // Requires `"allowJs": true` in `tsconfig.json`
116
116
 
117
117
  const cogsConnection = new CogsConnection(manifest);
118
118
  cogsConnection.addEventListener('open', () => {
@@ -147,6 +147,29 @@ function sendPortUpdateToCogs() {
147
147
  }
148
148
  ```
149
149
 
150
+ You can save arbitrary data to COGS which will be restored when reconnecting with COGS:
151
+
152
+ ```ts
153
+ const cogsConnection = new CogsConnection(manifest, {
154
+ // Initial items in the store
155
+ 'my-key': { foo: 0, bar: '' }
156
+ });
157
+
158
+ // Update the store
159
+ cogsConnection.store.setItems({ 'my-key': { foo: 1, bar: 'two' } });
160
+
161
+ // Get item from data store
162
+ cogsConnection.store.items.getItem('my-key')
163
+
164
+ // Listen for data changes
165
+ cogsConnection.store.addEventListener('item', ({ key, value }) => {
166
+ console.log(key, 'item changed:', value);
167
+ });
168
+ cogsConnection.store.addEventListener('items', ({ items }) => {
169
+ console.log('items changed:', items);
170
+ });
171
+ ```
172
+
150
173
  ### Support audio actions
151
174
 
152
175
  Add `audio` to `cogs-plugin-manifest.js`:
@@ -5,7 +5,10 @@ import AllMediaClipStatesMessage from './types/AllMediaClipStatesMessage';
5
5
  import { CogsPluginManifest, PluginManifestEventJson } from './types/CogsPluginManifest';
6
6
  import * as ManifestTypes from './types/ManifestTypes';
7
7
  import { DeepReadonly } from './types/utils';
8
- export default class CogsConnection<Manifest extends CogsPluginManifest> {
8
+ import DataStore from './DataStore';
9
+ export default class CogsConnection<Manifest extends CogsPluginManifest, DataT extends {
10
+ [key: string]: unknown;
11
+ } = Record<never, never>> {
9
12
  readonly manifest: Manifest;
10
13
  private websocket;
11
14
  private eventTarget;
@@ -17,14 +20,6 @@ export default class CogsConnection<Manifest extends CogsPluginManifest> {
17
20
  get showPhase(): ShowPhase;
18
21
  private _timerState;
19
22
  get timerState(): TimerState | null;
20
- /**
21
- * Track the support for HTTP/2 assets on the client and server side
22
- * Client side is dictated by the environment we're connecting from (and the media cogs-av-box version)
23
- * Server side is dictated by the COGS version which added the HTTP/2 assets server itself
24
- */
25
- private clientSupportsHttp2Assets;
26
- private serverSupportsHttp2Assets;
27
- get supportsHttp2Assets(): boolean;
28
23
  /**
29
24
  * Return asset URLs using the information about the client and server support for HTTP/2
30
25
  */
@@ -35,6 +30,10 @@ export default class CogsConnection<Manifest extends CogsPluginManifest> {
35
30
  private audioOutputs;
36
31
  private _selectedAudioOutput;
37
32
  get selectedAudioOutput(): string;
33
+ /**
34
+ * Stores data in COGS
35
+ */
36
+ store: DataStore<DataT>;
38
37
  /**
39
38
  * URL parameters use for the websocket connection and asset URLs
40
39
  */
@@ -44,7 +43,7 @@ export default class CogsConnection<Manifest extends CogsPluginManifest> {
44
43
  port?: number;
45
44
  }, initialClientState?: Partial<ManifestTypes.StateAsObject<Manifest, {
46
45
  writableFromClient: true;
47
- }>> | undefined);
46
+ }>>, initialDataStoreData?: DataT);
48
47
  get isConnected(): boolean;
49
48
  close(): void;
50
49
  sendEvent<EventName extends ManifestTypes.EventNameToCogs<Manifest>>(eventName: EventName, ...[eventValue]: ManifestTypes.EventToCogsAsObject<Manifest>[EventName] extends undefined ? [] : [ManifestTypes.EventToCogsAsObject<Manifest>[EventName]]): void;
@@ -55,6 +54,7 @@ export default class CogsConnection<Manifest extends CogsPluginManifest> {
55
54
  sendInitialMediaClipStates(allMediaClipStates: AllMediaClipStatesMessage): void;
56
55
  sendMediaClipState(mediaClipState: MediaClipStateMessage): void;
57
56
  sendAudioOutputs(audioOutputs: MediaDeviceInfo[]): void;
57
+ private sendDataStoreItems;
58
58
  /**
59
59
  * Show or hide the plugin window.
60
60
  * @param visible Whether to show or hide the window
@@ -4,10 +4,10 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.CogsIncomingEvent = exports.CogsStateChangedEvent = exports.CogsConfigChangedEvent = exports.CogsMessageEvent = exports.CogsConnectionCloseEvent = exports.CogsConnectionOpenEvent = void 0;
7
- const compare_versions_1 = require("compare-versions");
8
7
  const ShowPhase_1 = __importDefault(require("./types/ShowPhase"));
9
8
  const reconnecting_websocket_1 = __importDefault(require("reconnecting-websocket"));
10
9
  const urls_1 = require("./helpers/urls");
10
+ const DataStore_1 = __importDefault(require("./DataStore"));
11
11
  class CogsConnection {
12
12
  get config() {
13
13
  return { ...this.currentConfig };
@@ -21,20 +21,17 @@ class CogsConnection {
21
21
  get timerState() {
22
22
  return this._timerState ? { ...this._timerState } : null;
23
23
  }
24
- get supportsHttp2Assets() {
25
- return this.clientSupportsHttp2Assets && this.serverSupportsHttp2Assets;
26
- }
27
24
  /**
28
25
  * Return asset URLs using the information about the client and server support for HTTP/2
29
26
  */
30
27
  getAssetUrl(path) {
31
28
  var _a, _b;
32
- return `${(0, urls_1.assetUrl)(path, this.supportsHttp2Assets)}?${(_b = (_a = this.urlParams) === null || _a === void 0 ? void 0 : _a.toString()) !== null && _b !== void 0 ? _b : ''}`;
29
+ return `${(0, urls_1.assetUrl)(path)}?${(_b = (_a = this.urlParams) === null || _a === void 0 ? void 0 : _a.toString()) !== null && _b !== void 0 ? _b : ''}`;
33
30
  }
34
31
  get selectedAudioOutput() {
35
32
  return this._selectedAudioOutput;
36
33
  }
37
- constructor(manifest, { hostname = document.location.hostname, port = urls_1.COGS_SERVER_PORT } = {}, initialClientState = undefined) {
34
+ constructor(manifest, { hostname = document.location.hostname, port = urls_1.COGS_SERVER_PORT } = {}, initialClientState, initialDataStoreData) {
38
35
  var _a;
39
36
  this.manifest = manifest;
40
37
  this.eventTarget = new EventTarget();
@@ -42,20 +39,14 @@ class CogsConnection {
42
39
  this.currentState = {}; // Received on open connection - TODO: set initial state from manifest?
43
40
  this._showPhase = ShowPhase_1.default.Setup;
44
41
  this._timerState = null;
45
- /**
46
- * Track the support for HTTP/2 assets on the client and server side
47
- * Client side is dictated by the environment we're connecting from (and the media cogs-av-box version)
48
- * Server side is dictated by the COGS version which added the HTTP/2 assets server itself
49
- */
50
- this.clientSupportsHttp2Assets = false;
51
- this.serverSupportsHttp2Assets = false;
52
42
  /**
53
43
  * Cached audio outputs use to look up the device/sink ID when a different device label is requested
54
44
  */
55
45
  this.audioOutputs = undefined;
56
46
  this._selectedAudioOutput = '';
57
47
  this.currentState = { ...initialClientState };
58
- const { useReconnectingWebsocket, path, pathParams, supportsHttp2Assets } = websocketParametersFromUrl(document.location.href);
48
+ this.store = new DataStore_1.default(initialDataStoreData !== null && initialDataStoreData !== void 0 ? initialDataStoreData : {});
49
+ const { useReconnectingWebsocket, path, pathParams } = websocketParametersFromUrl(document.location.href);
59
50
  // Store the URL parameters for use in asset URLs
60
51
  // and add screen dimensions which COGS can use to determine the best asset quality to serve
61
52
  //
@@ -67,13 +58,9 @@ class CogsConnection {
67
58
  this.urlParams.set('screenPixelRatio', window.devicePixelRatio.toString());
68
59
  const socketUrl = `ws://${hostname}:${port}${path}?${this.urlParams}`;
69
60
  this.websocket = useReconnectingWebsocket ? new reconnecting_websocket_1.default(socketUrl) : new WebSocket(socketUrl);
70
- this.clientSupportsHttp2Assets = !!supportsHttp2Assets;
71
61
  this.websocket.onopen = () => {
72
62
  this.currentConfig = {}; // Received on open connection
73
63
  this.currentState = {}; // Received on open connection
74
- // Reset this flag as we might have just connected to an old COGS version which doesn't support HTTP/2
75
- // The flag will be set when COGS sends a "cogs_environment" message
76
- this.serverSupportsHttp2Assets = false;
77
64
  this.dispatchEvent(new CogsConnectionOpenEvent());
78
65
  this.setState(this.currentState); // TODO: Remove this because you should set it manually...??
79
66
  };
@@ -108,9 +95,6 @@ class CogsConnection {
108
95
  case 'show_phase':
109
96
  this._showPhase = message.phase;
110
97
  break;
111
- case 'cogs_environment':
112
- this.serverSupportsHttp2Assets = message.http2AssetsServer;
113
- break;
114
98
  case 'media_config_update':
115
99
  for (const optionName of ['preferOptimizedAudio', 'preferOptimizedVideo', 'preferOptimizedImages']) {
116
100
  const optionEnabled = message[optionName];
@@ -122,6 +106,9 @@ class CogsConnection {
122
106
  }
123
107
  }
124
108
  break;
109
+ case 'data_store_items':
110
+ this.store.handleDataStoreItemsMessage(message);
111
+ break;
125
112
  }
126
113
  this.dispatchEvent(new CogsMessageEvent(message));
127
114
  }
@@ -134,6 +121,10 @@ class CogsConnection {
134
121
  console.error('Unable to parse incoming data from server', data, e);
135
122
  }
136
123
  };
124
+ // Tell COGS when any data store items change
125
+ this.store.addEventListener('items', (event) => {
126
+ this.sendDataStoreItems(event.items);
127
+ });
137
128
  // Send a list of audio outputs to COGS and keep it up to date
138
129
  {
139
130
  const refreshAudioOutputs = async () => {
@@ -190,6 +181,11 @@ class CogsConnection {
190
181
  this.websocket.send(JSON.stringify({ audioOutputs }));
191
182
  }
192
183
  }
184
+ sendDataStoreItems(partialItems) {
185
+ if (this.isConnected) {
186
+ this.websocket.send(JSON.stringify({ dataStoreItems: partialItems }));
187
+ }
188
+ }
193
189
  /**
194
190
  * Show or hide the plugin window.
195
191
  * @param visible Whether to show or hide the window
@@ -213,66 +209,52 @@ class CogsConnection {
213
209
  }
214
210
  exports.default = CogsConnection;
215
211
  function websocketParametersFromUrl(url) {
216
- var _a, _b, _c, _d, _e, _f, _g, _h;
212
+ var _a, _b, _c, _d, _e;
217
213
  const parsedUrl = new URL(url);
218
214
  const pathParams = new URLSearchParams(parsedUrl.searchParams);
219
215
  const localClientId = pathParams.get('local_id');
220
216
  const isSimulator = pathParams.get('simulator') === 'true';
221
217
  const display = (_a = pathParams.get('display')) !== null && _a !== void 0 ? _a : '';
222
218
  const pluginId = parsedUrl.pathname.startsWith('/plugin/') ? decodeURIComponent(parsedUrl.pathname.split('/')[2]) : undefined;
223
- // Allow explicitly disabling HTTP/2 assets. This is useful in situations where we know the self-signed certificate cannot be
224
- // supported such as the native mobile app
225
- const disableHttp2Assets = ((_b = pathParams.get('http2Assets')) !== null && _b !== void 0 ? _b : '') === 'false';
226
219
  if (localClientId) {
227
- const type = (_c = pathParams.get('t')) !== null && _c !== void 0 ? _c : '';
220
+ const type = (_b = pathParams.get('t')) !== null && _b !== void 0 ? _b : '';
228
221
  pathParams.delete('local_id');
229
222
  return {
230
223
  path: `/local/${encodeURIComponent(localClientId)}`,
231
224
  pathParams: new URLSearchParams({ t: type }),
232
225
  useReconnectingWebsocket: true,
233
- supportsHttp2Assets: !disableHttp2Assets,
234
226
  };
235
227
  }
236
228
  else if (isSimulator) {
237
- const supportsHttp2Assets = ((_d = pathParams.get('http2Assets')) !== null && _d !== void 0 ? _d : '') === 'true';
238
- pathParams.delete('http2Assets');
239
- const name = (_e = pathParams.get('name')) !== null && _e !== void 0 ? _e : '';
229
+ const name = (_c = pathParams.get('name')) !== null && _c !== void 0 ? _c : '';
240
230
  pathParams.delete('simulator');
241
231
  pathParams.delete('name');
242
232
  return {
243
233
  path: `/simulator/${encodeURIComponent(name)}`,
244
234
  pathParams,
245
235
  useReconnectingWebsocket: true,
246
- supportsHttp2Assets: !disableHttp2Assets && supportsHttp2Assets,
247
236
  };
248
237
  }
249
238
  else if (display) {
250
- const displayIdIndex = (_f = pathParams.get('displayIdIndex')) !== null && _f !== void 0 ? _f : '';
239
+ const displayIdIndex = (_d = pathParams.get('displayIdIndex')) !== null && _d !== void 0 ? _d : '';
251
240
  pathParams.delete('display');
252
241
  pathParams.delete('displayIdIndex');
253
242
  return {
254
243
  path: `/display/${encodeURIComponent(display)}/${encodeURIComponent(displayIdIndex)}`,
255
- supportsHttp2Assets: !disableHttp2Assets,
256
244
  };
257
245
  }
258
246
  else if (pluginId) {
259
247
  return {
260
248
  path: `/plugin/${encodeURIComponent(pluginId)}`,
261
249
  useReconnectingWebsocket: true,
262
- supportsHttp2Assets: !disableHttp2Assets,
263
250
  };
264
251
  }
265
252
  else {
266
- const serial = (_g = pathParams.get('serial')) !== null && _g !== void 0 ? _g : '';
253
+ const serial = (_e = pathParams.get('serial')) !== null && _e !== void 0 ? _e : '';
267
254
  pathParams.delete('serial');
268
- // Check if cogs-box-av is a version which added support for ignoring HTTP/2 self-signed certificates
269
- const firmwareVersion = ((_h = pathParams.get('f')) !== null && _h !== void 0 ? _h : '').replace(/^v/, '');
270
- const isCogsBoxAvDevBuild = firmwareVersion === '0.0.0'; // We assume dev firmware builds have HTTP/2 assets support - Added in 2024-03
271
- const supportsHttp2Assets = isCogsBoxAvDevBuild || ((0, compare_versions_1.validate)(firmwareVersion) && (0, compare_versions_1.satisfies)(firmwareVersion, '>=4.9.0'));
272
255
  return {
273
256
  path: `/client/${encodeURIComponent(serial)}`,
274
257
  pathParams,
275
- supportsHttp2Assets: !disableHttp2Assets && supportsHttp2Assets,
276
258
  };
277
259
  }
278
260
  }
@@ -0,0 +1,41 @@
1
+ import { DataStoreItemsClientMessage } from './types/CogsClientMessage';
2
+ /**
3
+ * A simple key-value store for storing data in COGS
4
+ *
5
+ * When reconnected the data will be restored.
6
+ */
7
+ export default class DataStore<ItemsT extends {
8
+ [key: string]: unknown;
9
+ } = {
10
+ [key: string]: unknown;
11
+ }> {
12
+ #private;
13
+ private _items;
14
+ constructor(defaultItems: ItemsT);
15
+ handleDataStoreItemsMessage(message: DataStoreItemsClientMessage): void;
16
+ get items(): Readonly<typeof this._items>;
17
+ getItem(key: string): unknown | undefined;
18
+ setItems(partialItems: Partial<ItemsT>): this;
19
+ addEventListener<K extends keyof DataStoreEventMap>(type: K, listener: (event: DataStoreEventMap[K]) => void, options?: boolean | AddEventListenerOptions): void;
20
+ removeEventListener<K extends keyof DataStoreEventMap>(type: K, listener: (event: DataStoreEventMap[K]) => void, options?: boolean | EventListenerOptions): void;
21
+ private dispatchEvent;
22
+ }
23
+ export declare class DataStoreItemEvent extends Event {
24
+ readonly key: string;
25
+ readonly value: unknown;
26
+ readonly _cogsConnectionEventType = "item";
27
+ constructor(key: string, value: unknown);
28
+ }
29
+ export declare class DataStoreItemsEvent extends Event {
30
+ readonly items: {
31
+ [key: string]: unknown;
32
+ };
33
+ readonly _cogsConnectionEventType = "item";
34
+ constructor(items: {
35
+ [key: string]: unknown;
36
+ });
37
+ }
38
+ export interface DataStoreEventMap {
39
+ item: DataStoreItemEvent;
40
+ items: DataStoreItemsEvent;
41
+ }
@@ -0,0 +1,70 @@
1
+ "use strict";
2
+ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
3
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
4
+ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
5
+ return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
6
+ };
7
+ var _DataStore_eventTarget;
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.DataStoreItemsEvent = exports.DataStoreItemEvent = void 0;
10
+ /**
11
+ * A simple key-value store for storing data in COGS
12
+ *
13
+ * When reconnected the data will be restored.
14
+ */
15
+ class DataStore {
16
+ constructor(defaultItems) {
17
+ _DataStore_eventTarget.set(this, new EventTarget());
18
+ this._items = { ...defaultItems };
19
+ }
20
+ handleDataStoreItemsMessage(message) {
21
+ this._items = { ...this._items, ...message.items };
22
+ Object.entries(message.items).forEach(([key, value]) => {
23
+ this.dispatchEvent(new DataStoreItemEvent(key, value));
24
+ });
25
+ this.dispatchEvent(new DataStoreItemsEvent(message.items));
26
+ }
27
+ get items() {
28
+ return this._items;
29
+ }
30
+ getItem(key) {
31
+ return this._items[key];
32
+ }
33
+ setItems(partialItems) {
34
+ this._items = { ...this._items, ...partialItems };
35
+ Object.entries(partialItems).forEach(([key, value]) => {
36
+ this.dispatchEvent(new DataStoreItemEvent(key, value));
37
+ });
38
+ this.dispatchEvent(new DataStoreItemsEvent(partialItems));
39
+ return this;
40
+ }
41
+ // Type-safe listeners
42
+ addEventListener(type, listener, options) {
43
+ __classPrivateFieldGet(this, _DataStore_eventTarget, "f").addEventListener(type, listener, options);
44
+ }
45
+ removeEventListener(type, listener, options) {
46
+ __classPrivateFieldGet(this, _DataStore_eventTarget, "f").removeEventListener(type, listener, options);
47
+ }
48
+ dispatchEvent(event) {
49
+ __classPrivateFieldGet(this, _DataStore_eventTarget, "f").dispatchEvent(event);
50
+ }
51
+ }
52
+ _DataStore_eventTarget = new WeakMap();
53
+ exports.default = DataStore;
54
+ class DataStoreItemEvent extends Event {
55
+ constructor(key, value) {
56
+ super('item');
57
+ this.key = key;
58
+ this.value = value;
59
+ this._cogsConnectionEventType = 'item';
60
+ }
61
+ }
62
+ exports.DataStoreItemEvent = DataStoreItemEvent;
63
+ class DataStoreItemsEvent extends Event {
64
+ constructor(items) {
65
+ super('items');
66
+ this.items = items;
67
+ this._cogsConnectionEventType = 'item';
68
+ }
69
+ }
70
+ exports.DataStoreItemsEvent = DataStoreItemsEvent;