ogi-addon 1.2.2 → 1.3.1

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/src/main.ts CHANGED
@@ -1,15 +1,49 @@
1
1
  import ws, { WebSocket } from 'ws';
2
2
  import events from 'node:events';
3
- import { ConfigurationBuilder, ConfigurationFile } from './config/ConfigurationBuilder';
3
+ import {
4
+ ConfigurationBuilder,
5
+ ConfigurationFile,
6
+ } from './config/ConfigurationBuilder';
4
7
  import { Configuration } from './config/Configuration';
5
8
  import EventResponse from './EventResponse';
6
9
  import { SearchResult } from './SearchEngine';
7
10
  import Fuse, { IFuseOptions } from 'fuse.js';
8
11
 
9
- export type OGIAddonEvent = 'connect' | 'disconnect' | 'configure' | 'authenticate' | 'search' | 'setup' | 'library-search' | 'game-details' | 'exit' | 'request-dl';
10
- export type OGIAddonClientSentEvent = 'response' | 'authenticate' | 'configure' | 'defer-update' | 'notification' | 'input-asked' | 'steam-search' | 'task-update';
11
-
12
- export type OGIAddonServerSentEvent = 'authenticate' | 'configure' | 'config-update' | 'search' | 'setup' | 'response' | 'library-search' | 'game-details' | 'request-dl';
12
+ export type OGIAddonEvent =
13
+ | 'connect'
14
+ | 'disconnect'
15
+ | 'configure'
16
+ | 'authenticate'
17
+ | 'search'
18
+ | 'setup'
19
+ | 'library-search'
20
+ | 'game-details'
21
+ | 'exit'
22
+ | 'request-dl'
23
+ | 'catalog';
24
+
25
+ export type OGIAddonClientSentEvent =
26
+ | 'response'
27
+ | 'authenticate'
28
+ | 'configure'
29
+ | 'defer-update'
30
+ | 'notification'
31
+ | 'input-asked'
32
+ | 'get-app-details'
33
+ | 'flag'
34
+ | 'task-update';
35
+
36
+ export type OGIAddonServerSentEvent =
37
+ | 'authenticate'
38
+ | 'configure'
39
+ | 'config-update'
40
+ | 'search'
41
+ | 'setup'
42
+ | 'response'
43
+ | 'library-search'
44
+ | 'game-details'
45
+ | 'request-dl'
46
+ | 'catalog';
13
47
  export { ConfigurationBuilder, Configuration, EventResponse, SearchResult };
14
48
  const defaultPort = 7654;
15
49
  import pjson from '../package.json';
@@ -26,15 +60,11 @@ export interface ClientSentEventTypes {
26
60
  };
27
61
  configure: ConfigurationFile;
28
62
  'defer-update': {
29
- logs: string[],
30
- progress: number
63
+ logs: string[];
64
+ progress: number;
31
65
  };
32
66
  notification: Notification;
33
67
  'input-asked': ConfigurationBuilder;
34
- 'steam-search': {
35
- query: string;
36
- strict: boolean;
37
- };
38
68
  'task-update': {
39
69
  id: string;
40
70
  progress: number;
@@ -42,85 +72,153 @@ export interface ClientSentEventTypes {
42
72
  finished: boolean;
43
73
  failed: string | undefined;
44
74
  };
75
+ 'get-app-details': {
76
+ appID: number;
77
+ storefront: string;
78
+ };
79
+ flag: {
80
+ flag: string;
81
+ value: string | string[];
82
+ };
45
83
  }
46
84
 
47
85
  export type BasicLibraryInfo = {
48
86
  name: string;
49
87
  capsuleImage: string;
50
88
  appID: number;
51
- }
89
+ storefront: string;
90
+ };
52
91
 
53
92
  export interface EventListenerTypes {
54
93
  /**
55
94
  * This event is emitted when the addon connects to the OGI Addon Server. Addon does not need to resolve anything.
56
- * @param socket
57
- * @returns
95
+ * @param socket
96
+ * @returns
58
97
  */
59
98
  connect: (socket: ws) => void;
60
99
 
61
100
  /**
62
101
  * This event is emitted when the client requests for the addon to disconnect. Addon does not need to resolve this event, but we recommend `process.exit(0)` so the addon can exit gracefully instead of by force by the addon server.
63
- * @param reason
64
- * @returns
102
+ * @param reason
103
+ * @returns
65
104
  */
66
105
  disconnect: (reason: string) => void;
67
106
  /**
68
- * This event is emitted when the client requests for the addon to configure itself. Addon should resolve the event with the internal configuration. (See ConfigurationBuilder)
69
- * @param config
70
- * @returns
107
+ * This event is emitted when the client requests for the addon to configure itself. Addon should resolve the event with the internal configuration. (See ConfigurationBuilder)
108
+ * @param config
109
+ * @returns
71
110
  */
72
111
  configure: (config: ConfigurationBuilder) => ConfigurationBuilder;
73
112
  /**
74
- * This event is called when the client provides a response to any event. This should be treated as middleware.
75
- * @param response
76
- * @returns
113
+ * This event is called when the client provides a response to any event. This should be treated as middleware.
114
+ * @param response
115
+ * @returns
77
116
  */
78
117
  response: (response: any) => void;
79
118
 
80
119
  /**
81
- * This event is called when the client requests for the addon to authenticate itself. You don't need to provide any info.
82
- * @param config
83
- * @returns
120
+ * This event is called when the client requests for the addon to authenticate itself. You don't need to provide any info.
121
+ * @param config
122
+ * @returns
84
123
  */
85
124
  authenticate: (config: any) => void;
86
125
  /**
87
- * This event is emitted when the client requests for a torrent/direct download search to be performed. Addon is given the gameID (could be a steam appID or custom store appID), along with the storefront type. Addon should resolve the event with the search results. (See SearchResult)
88
- * @param query
89
- * @param event
90
- * @returns
126
+ * This event is emitted when the client requests for a torrent/direct download search to be performed. Addon is given the gameID (could be a steam appID or custom store appID), along with the storefront type. Addon should resolve the event with the search results. (See SearchResult)
127
+ * @param query
128
+ * @param event
129
+ * @returns
91
130
  */
92
- search: (query: { type: 'steamapp' | 'internal', text: string }, event: EventResponse<SearchResult[]>) => void;
131
+ search: (
132
+ query: { storefront: string; appID: number },
133
+ event: EventResponse<SearchResult[]>
134
+ ) => void;
93
135
  /**
94
136
  * This event is emitted when the client requests for app setup to be performed. Addon should resolve the event with the metadata for the library entry. (See LibraryInfo)
95
- * @param data
96
- * @param event
97
- * @returns
137
+ * @param data
138
+ * @param event
139
+ * @returns
98
140
  */
99
141
  setup: (
100
142
  data: {
101
- path: string,
102
- type: 'direct' | 'torrent' | 'magnet',
103
- name: string,
104
- usedRealDebrid: boolean,
143
+ path: string;
144
+ type: 'direct' | 'torrent' | 'magnet';
145
+ name: string;
146
+ usedRealDebrid: boolean;
105
147
  multiPartFiles?: {
106
- name: string,
107
- downloadURL: string
108
- }[],
109
- appID: number,
110
- storefront: 'steam' | 'internal'
111
- }, event: EventResponse<LibraryInfo>
148
+ name: string;
149
+ downloadURL: string;
150
+ }[];
151
+ appID: number;
152
+ storefront: string;
153
+ },
154
+ event: EventResponse<
155
+ Omit<
156
+ LibraryInfo,
157
+ | 'capsuleImage'
158
+ | 'coverImage'
159
+ | 'name'
160
+ | 'appID'
161
+ | 'storefront'
162
+ | 'addonsource'
163
+ >
164
+ >
112
165
  ) => void;
113
166
 
114
167
  /**
115
- * This event is emitted when the client requires for a search to be performed. Input is the search query.
116
- * @param query
117
- * @param event
118
- * @returns
168
+ * This event is emitted when the client requires for a search to be performed. Input is the search query.
169
+ * @param query
170
+ * @param event
171
+ * @returns
172
+ */
173
+ 'library-search': (
174
+ query: string,
175
+ event: EventResponse<BasicLibraryInfo[]>
176
+ ) => void;
177
+
178
+ /**
179
+ * This event is emitted when the client requests for a game details to be fetched. Addon should resolve the event with the game details. This is used to generate a store page for the game.
180
+ * @param appID
181
+ * @param event
182
+ * @returns
119
183
  */
120
- 'library-search': (query: string, event: EventResponse<BasicLibraryInfo[]>) => void;
121
184
  'game-details': (appID: number, event: EventResponse<StoreData>) => void;
185
+
186
+ /**
187
+ * This event is emitted when the client requests for the addon to exit. Use this to perform any cleanup tasks, ending with a `process.exit(0)`.
188
+ * @returns
189
+ */
122
190
  exit: () => void;
123
- 'request-dl': (appID: number, info: SearchResult, event: EventResponse<SearchResult>) => void;
191
+
192
+ /**
193
+ * This event is emitted when the client requests for a download to be performed with the 'request' type. Addon should resolve the event with a SearchResult containing the actual download info.
194
+ * @param appID
195
+ * @param info
196
+ * @param event
197
+ * @returns
198
+ */
199
+ 'request-dl': (
200
+ appID: number,
201
+ info: SearchResult,
202
+ event: EventResponse<SearchResult>
203
+ ) => void;
204
+
205
+ /**
206
+ * This event is emitted when the client requests for a catalog to be fetched. Addon should resolve the event with the catalog.
207
+ * @param event
208
+ * @returns
209
+ */
210
+ catalog: (
211
+ event: Omit<
212
+ EventResponse<{
213
+ [key: string]: {
214
+ name: string;
215
+ description: string;
216
+ listings: BasicLibraryInfo[];
217
+ };
218
+ }>,
219
+ 'askForInput'
220
+ >
221
+ ) => void;
124
222
  }
125
223
 
126
224
  export interface StoreData {
@@ -145,6 +243,11 @@ export interface WebsocketMessageServer {
145
243
  id?: string;
146
244
  args: any;
147
245
  }
246
+
247
+ /**
248
+ * The configuration for the addon. This is used to identify the addon and provide information about it.
249
+ * Storefronts is an array of names of stores that the addon supports.
250
+ */
148
251
  export interface OGIAddonConfiguration {
149
252
  name: string;
150
253
  id: string;
@@ -153,28 +256,31 @@ export interface OGIAddonConfiguration {
153
256
 
154
257
  author: string;
155
258
  repository: string;
259
+ storefronts: string[];
156
260
  }
157
261
 
158
262
  /**
159
263
  * The main class for the OGI Addon. This class is used to interact with the OGI Addon Server. The OGI Addon Server provides a `--addonSecret` to the addon so it can securely connect.
160
- * @example
264
+ * @example
161
265
  * ```typescript
162
266
  * const addon = new OGIAddon({
163
267
  * name: 'Test Addon',
164
- * id: 'test-addon',
268
+ * id: 'test-addon',
165
269
  * description: 'A test addon',
166
270
  * version: '1.0.0',
167
271
  * author: 'OGI Developers',
168
272
  * repository: ''
169
273
  * });
170
274
  * ```
171
- *
275
+ *
172
276
  */
173
277
  export default class OGIAddon {
174
278
  public eventEmitter = new events.EventEmitter();
175
279
  public addonWSListener: OGIAddonWSListener;
176
280
  public addonInfo: OGIAddonConfiguration;
177
281
  public config: Configuration = new Configuration({});
282
+ private eventsAvailable: OGIAddonEvent[] = [];
283
+ private registeredConnectEvent: boolean = false;
178
284
 
179
285
  constructor(addonInfo: OGIAddonConfiguration) {
180
286
  this.addonInfo = addonInfo;
@@ -182,20 +288,37 @@ export default class OGIAddon {
182
288
  }
183
289
 
184
290
  /**
185
- * Register an event listener for the addon. (See EventListenerTypes)
291
+ * Register an event listener for the addon. (See EventListenerTypes)
186
292
  * @param event {OGIAddonEvent}
187
- * @param listener {EventListenerTypes[OGIAddonEvent]}
293
+ * @param listener {EventListenerTypes[OGIAddonEvent]}
188
294
  */
189
- public on<T extends OGIAddonEvent>(event: T, listener: EventListenerTypes[T]) {
295
+ public on<T extends OGIAddonEvent>(
296
+ event: T,
297
+ listener: EventListenerTypes[T]
298
+ ) {
190
299
  this.eventEmitter.on(event, listener);
300
+ this.eventsAvailable.push(event);
301
+ // wait for the addon to be connected
302
+ if (!this.registeredConnectEvent) {
303
+ this.addonWSListener.eventEmitter.once('connect', () => {
304
+ this.addonWSListener.send('flag', {
305
+ flag: 'events-available',
306
+ value: this.eventsAvailable,
307
+ });
308
+ });
309
+ this.registeredConnectEvent = true;
310
+ }
191
311
  }
192
312
 
193
- public emit<T extends OGIAddonEvent>(event: T, ...args: Parameters<EventListenerTypes[T]>) {
313
+ public emit<T extends OGIAddonEvent>(
314
+ event: T,
315
+ ...args: Parameters<EventListenerTypes[T]>
316
+ ) {
194
317
  this.eventEmitter.emit(event, ...args);
195
318
  }
196
319
 
197
320
  /**
198
- * Notify the client using a notification. Provide the type of notification, the message, and an ID.
321
+ * Notify the client using a notification. Provide the type of notification, the message, and an ID.
199
322
  * @param notification {Notification}
200
323
  */
201
324
  public notify(notification: Notification) {
@@ -203,13 +326,19 @@ export default class OGIAddon {
203
326
  }
204
327
 
205
328
  /**
206
- * Search for items in the OGI Steam-Synced Library. Query can either be a Steam AppID or a Steam Game Name.
207
- * @param query {string}
208
- * @param event {EventResponse<BasicLibraryInfo[]>}
329
+ * Get the app details for a given appID and storefront.
330
+ * @param appID {number}
331
+ * @param storefront {string}
332
+ * @returns {Promise<StoreData>}
209
333
  */
210
- public async steamSearch(query: string, strict: boolean = false) {
211
- const id = this.addonWSListener.send('steam-search', { query, strict });
212
- return await this.addonWSListener.waitForResponseFromServer<Omit<BasicLibraryInfo, 'capsuleImage'>[]>(id);
334
+ public async getAppDetails(appID: number, storefront: string) {
335
+ const id = this.addonWSListener.send('get-app-details', {
336
+ appID,
337
+ storefront,
338
+ });
339
+ return await this.addonWSListener.waitForResponseFromServer<
340
+ StoreData | undefined
341
+ >(id);
213
342
  }
214
343
 
215
344
  /**
@@ -223,7 +352,13 @@ export default class OGIAddon {
223
352
  const progress = 0;
224
353
  const logs: string[] = [];
225
354
  const task = new CustomTask(this.addonWSListener, id, progress, logs);
226
- this.addonWSListener.send('task-update', { id, progress, logs, finished: false, failed: undefined });
355
+ this.addonWSListener.send('task-update', {
356
+ id,
357
+ progress,
358
+ logs,
359
+ finished: false,
360
+ failed: undefined,
361
+ });
227
362
  return task;
228
363
  }
229
364
  }
@@ -235,7 +370,12 @@ export class CustomTask {
235
370
  public finished: boolean = false;
236
371
  public ws: OGIAddonWSListener;
237
372
  public failed: string | undefined = undefined;
238
- constructor(ws: OGIAddonWSListener, id: string, progress: number, logs: string[]) {
373
+ constructor(
374
+ ws: OGIAddonWSListener,
375
+ id: string,
376
+ progress: number,
377
+ logs: string[]
378
+ ) {
239
379
  this.id = id;
240
380
  this.progress = progress;
241
381
  this.logs = logs;
@@ -258,7 +398,13 @@ export class CustomTask {
258
398
  this.update();
259
399
  }
260
400
  public update() {
261
- this.ws.send('task-update', { id: this.id, progress: this.progress, logs: this.logs, finished: this.finished, failed: this.failed });
401
+ this.ws.send('task-update', {
402
+ id: this.id,
403
+ progress: this.progress,
404
+ logs: this.logs,
405
+ finished: this.finished,
406
+ failed: this.failed,
407
+ });
262
408
  }
263
409
  }
264
410
  /**
@@ -271,17 +417,27 @@ export class CustomTask {
271
417
  */
272
418
  export class SearchTool<T> {
273
419
  private fuse: Fuse<T>;
274
- constructor(items: T[], keys: string[], options: Omit<IFuseOptions<T>, 'keys'> = { threshold: 0.3, includeScore: true }) {
420
+ constructor(
421
+ items: T[],
422
+ keys: string[],
423
+ options: Omit<IFuseOptions<T>, 'keys'> = {
424
+ threshold: 0.3,
425
+ includeScore: true,
426
+ }
427
+ ) {
275
428
  this.fuse = new Fuse(items, {
276
429
  keys,
277
- ...options
430
+ ...options,
278
431
  });
279
432
  }
280
433
  public search(query: string, limit: number = 10): T[] {
281
- return this.fuse.search(query).slice(0, limit).map(result => result.item);
434
+ return this.fuse
435
+ .search(query)
436
+ .slice(0, limit)
437
+ .map((result) => result.item);
282
438
  }
283
439
  public addItems(items: T[]) {
284
- items.map(item => this.fuse.add(item));
440
+ items.map((item) => this.fuse.add(item));
285
441
  }
286
442
  }
287
443
  /**
@@ -295,7 +451,7 @@ export interface LibraryInfo {
295
451
  launchExecutable: string;
296
452
  launchArguments?: string;
297
453
  capsuleImage: string;
298
- storefront: 'steam' | 'internal';
454
+ storefront: string;
299
455
  addonsource: string;
300
456
  coverImage: string;
301
457
  titleImage?: string;
@@ -303,7 +459,7 @@ export interface LibraryInfo {
303
459
  interface Notification {
304
460
  type: 'warning' | 'error' | 'info' | 'success';
305
461
  message: string;
306
- id: string
462
+ id: string;
307
463
  }
308
464
  class OGIAddonWSListener {
309
465
  private socket: WebSocket;
@@ -311,8 +467,12 @@ class OGIAddonWSListener {
311
467
  public addon: OGIAddon;
312
468
 
313
469
  constructor(ogiAddon: OGIAddon, eventEmitter: events.EventEmitter) {
314
- if (process.argv[process.argv.length - 1].split('=')[0] !== '--addonSecret') {
315
- throw new Error('No secret provided. This usually happens because the addon was not started by the OGI Addon Server.');
470
+ if (
471
+ process.argv[process.argv.length - 1].split('=')[0] !== '--addonSecret'
472
+ ) {
473
+ throw new Error(
474
+ 'No secret provided. This usually happens because the addon was not started by the OGI Addon Server.'
475
+ );
316
476
  }
317
477
  this.addon = ogiAddon;
318
478
  this.eventEmitter = eventEmitter;
@@ -325,7 +485,7 @@ class OGIAddonWSListener {
325
485
  this.send('authenticate', {
326
486
  ...this.addon.addonInfo,
327
487
  secret: process.argv[process.argv.length - 1].split('=')[1],
328
- ogiVersion: VERSION
488
+ ogiVersion: VERSION,
329
489
  });
330
490
 
331
491
  this.eventEmitter.emit('connect');
@@ -339,10 +499,12 @@ class OGIAddonWSListener {
339
499
 
340
500
  this.socket.on('error', (error) => {
341
501
  if (error.message.includes('Failed to connect')) {
342
- throw new Error('OGI Addon Server is not running/is unreachable. Please start the server and try again.');
502
+ throw new Error(
503
+ 'OGI Addon Server is not running/is unreachable. Please start the server and try again.'
504
+ );
343
505
  }
344
506
  console.error('An error occurred:', error);
345
- })
507
+ });
346
508
 
347
509
  this.socket.on('close', (code, reason) => {
348
510
  if (code === 1008) {
@@ -350,8 +512,8 @@ class OGIAddonWSListener {
350
512
  return;
351
513
  }
352
514
  this.eventEmitter.emit('disconnect', reason);
353
- console.log("Disconnected from OGI Addon Server")
354
- console.error(reason.toString())
515
+ console.log('Disconnected from OGI Addon Server');
516
+ console.error(reason.toString());
355
517
  this.eventEmitter.emit('exit');
356
518
  this.socket.close();
357
519
  });
@@ -359,21 +521,28 @@ class OGIAddonWSListener {
359
521
  this.registerMessageReceiver();
360
522
  }
361
523
 
362
- private async userInputAsked(configBuilt: ConfigurationBuilder, name: string, description: string, socket: WebSocket): Promise<{ [key: string]: number | boolean | string }> {
524
+ private async userInputAsked(
525
+ configBuilt: ConfigurationBuilder,
526
+ name: string,
527
+ description: string,
528
+ socket: WebSocket
529
+ ): Promise<{ [key: string]: number | boolean | string }> {
363
530
  const config = configBuilt.build(false);
364
531
  const id = Math.random().toString(36).substring(7);
365
532
  if (!socket) {
366
533
  return {};
367
534
  }
368
- socket.send(JSON.stringify({
369
- event: 'input-asked',
370
- args: {
371
- config,
372
- name,
373
- description
374
- },
375
- id: id
376
- }));
535
+ socket.send(
536
+ JSON.stringify({
537
+ event: 'input-asked',
538
+ args: {
539
+ config,
540
+ name,
541
+ description,
542
+ },
543
+ id: id,
544
+ })
545
+ );
377
546
  return await this.waitForResponseFromServer(id);
378
547
  }
379
548
 
@@ -384,21 +553,42 @@ class OGIAddonWSListener {
384
553
  case 'config-update':
385
554
  const result = this.addon.config.updateConfig(message.args);
386
555
  if (!result[0]) {
387
- this.respondToMessage(message.id!!, { success: false, error: result[1] });
388
- }
389
- else {
556
+ this.respondToMessage(message.id!!, {
557
+ success: false,
558
+ error: result[1],
559
+ });
560
+ } else {
390
561
  this.respondToMessage(message.id!!, { success: true });
391
562
  }
392
- break
563
+ break;
393
564
  case 'search':
394
- let searchResultEvent = new EventResponse<SearchResult[]>((screen, name, description) => this.userInputAsked(screen, name, description, this.socket));
565
+ let searchResultEvent = new EventResponse<SearchResult[]>(
566
+ (screen, name, description) =>
567
+ this.userInputAsked(screen, name, description, this.socket)
568
+ );
395
569
  this.eventEmitter.emit('search', message.args, searchResultEvent);
396
- const searchResult = await this.waitForEventToRespond(searchResultEvent);
570
+ const searchResult =
571
+ await this.waitForEventToRespond(searchResultEvent);
397
572
  this.respondToMessage(message.id!!, searchResult.data);
398
- break
573
+ break;
399
574
  case 'setup':
400
- let setupEvent = new EventResponse<LibraryInfo>((screen, name, description) => this.userInputAsked(screen, name, description, this.socket));
401
- this.eventEmitter.emit('setup', { path: message.args.path, appID: message.args.appID, storefront: message.args.storefront, type: message.args.type, name: message.args.name, usedRealDebrid: message.args.usedRealDebrid, multiPartFiles: message.args.multiPartFiles }, setupEvent);
575
+ let setupEvent = new EventResponse<LibraryInfo>(
576
+ (screen, name, description) =>
577
+ this.userInputAsked(screen, name, description, this.socket)
578
+ );
579
+ this.eventEmitter.emit(
580
+ 'setup',
581
+ {
582
+ path: message.args.path,
583
+ appID: message.args.appID,
584
+ storefront: message.args.storefront,
585
+ type: message.args.type,
586
+ name: message.args.name,
587
+ usedRealDebrid: message.args.usedRealDebrid,
588
+ multiPartFiles: message.args.multiPartFiles,
589
+ },
590
+ setupEvent
591
+ );
402
592
  const interval = setInterval(() => {
403
593
  if (setupEvent.resolved) {
404
594
  clearInterval(interval);
@@ -408,54 +598,104 @@ class OGIAddonWSListener {
408
598
  logs: setupEvent.logs,
409
599
  deferID: message.args.deferID,
410
600
  progress: setupEvent.progress,
411
- failed: setupEvent.failed
601
+ failed: setupEvent.failed,
412
602
  } as ClientSentEventTypes['defer-update']);
413
603
  }, 100);
414
604
  const setupResult = await this.waitForEventToRespond(setupEvent);
415
605
  this.respondToMessage(message.id!!, setupResult.data);
416
- break
606
+ break;
417
607
  case 'library-search':
418
- let librarySearchEvent = new EventResponse<BasicLibraryInfo[]>((screen, name, description) => this.userInputAsked(screen, name, description, this.socket));
608
+ let librarySearchEvent = new EventResponse<BasicLibraryInfo[]>(
609
+ (screen, name, description) =>
610
+ this.userInputAsked(screen, name, description, this.socket)
611
+ );
419
612
  if (this.eventEmitter.listenerCount('game-details') === 0) {
420
613
  this.respondToMessage(message.id!!, []);
421
614
  break;
422
615
  }
423
- this.eventEmitter.emit('library-search', message.args, librarySearchEvent);
424
- const librarySearchResult = await this.waitForEventToRespond(librarySearchEvent);
616
+ this.eventEmitter.emit(
617
+ 'library-search',
618
+ message.args,
619
+ librarySearchEvent
620
+ );
621
+ const librarySearchResult =
622
+ await this.waitForEventToRespond(librarySearchEvent);
425
623
  this.respondToMessage(message.id!!, librarySearchResult.data);
426
- break
624
+ break;
427
625
  case 'game-details':
428
- let gameDetailsEvent = new EventResponse<StoreData>((screen, name, description) => this.userInputAsked(screen, name, description, this.socket));
626
+ let gameDetailsEvent = new EventResponse<StoreData | undefined>(
627
+ (screen, name, description) =>
628
+ this.userInputAsked(screen, name, description, this.socket)
629
+ );
429
630
  if (this.eventEmitter.listenerCount('game-details') === 0) {
430
- this.respondToMessage(message.id!!, { error: 'No event listener for game-details' });
631
+ this.respondToMessage(message.id!!, {
632
+ error: 'No event listener for game-details',
633
+ });
431
634
  break;
432
635
  }
433
- this.eventEmitter.emit('game-details', message.args, gameDetailsEvent);
434
- const gameDetailsResult = await this.waitForEventToRespond(gameDetailsEvent);
636
+ this.eventEmitter.emit(
637
+ 'game-details',
638
+ message.args,
639
+ gameDetailsEvent
640
+ );
641
+ const gameDetailsResult =
642
+ await this.waitForEventToRespond(gameDetailsEvent);
435
643
  this.respondToMessage(message.id!!, gameDetailsResult.data);
436
- break
644
+ break;
437
645
  case 'request-dl':
438
- let requestDLEvent = new EventResponse<SearchResult>((screen, name, description) => this.userInputAsked(screen, name, description, this.socket));
646
+ let requestDLEvent = new EventResponse<SearchResult>(
647
+ (screen, name, description) =>
648
+ this.userInputAsked(screen, name, description, this.socket)
649
+ );
439
650
  if (this.eventEmitter.listenerCount('request-dl') === 0) {
440
- this.respondToMessage(message.id!!, { error: 'No event listener for request-dl' });
651
+ this.respondToMessage(message.id!!, {
652
+ error: 'No event listener for request-dl',
653
+ });
441
654
  break;
442
655
  }
443
- this.eventEmitter.emit('request-dl', message.args.appID, message.args.info, requestDLEvent);
444
- const requestDLResult = await this.waitForEventToRespond(requestDLEvent);
656
+ this.eventEmitter.emit(
657
+ 'request-dl',
658
+ message.args.appID,
659
+ message.args.info,
660
+ requestDLEvent
661
+ );
662
+ const requestDLResult =
663
+ await this.waitForEventToRespond(requestDLEvent);
445
664
  if (requestDLEvent.failed) {
446
- this.respondToMessage(message.id!!, { statusError: requestDLEvent.failed });
665
+ this.respondToMessage(message.id!!, {
666
+ statusError: requestDLEvent.failed,
667
+ });
447
668
  break;
448
669
  }
449
- if (requestDLEvent.data === undefined || requestDLEvent.data?.downloadType === 'request') {
450
- throw new Error('Request DL event did not return a valid result. Please ensure that the event does not resolve with another `request` download type.');
670
+ if (
671
+ requestDLEvent.data === undefined ||
672
+ requestDLEvent.data?.downloadType === 'request'
673
+ ) {
674
+ throw new Error(
675
+ 'Request DL event did not return a valid result. Please ensure that the event does not resolve with another `request` download type.'
676
+ );
451
677
  }
452
678
  this.respondToMessage(message.id!!, requestDLResult.data);
453
- break
679
+ break;
680
+ case 'catalog':
681
+ let catalogEvent = new EventResponse<{
682
+ [key: string]: {
683
+ name: string;
684
+ description: string;
685
+ listings: BasicLibraryInfo[];
686
+ };
687
+ }>();
688
+ this.eventEmitter.emit('catalog', catalogEvent);
689
+ const catalogResult = await this.waitForEventToRespond(catalogEvent);
690
+ this.respondToMessage(message.id!!, catalogResult.data);
691
+ break;
454
692
  }
455
693
  });
456
694
  }
457
695
 
458
- private waitForEventToRespond<T>(event: EventResponse<T>): Promise<EventResponse<T>> {
696
+ private waitForEventToRespond<T>(
697
+ event: EventResponse<T>
698
+ ): Promise<EventResponse<T>> {
459
699
  // check the handlers to see if there even is any
460
700
  return new Promise((resolve, reject) => {
461
701
  const dataGet = setInterval(() => {
@@ -474,21 +714,22 @@ class OGIAddonWSListener {
474
714
  resolve(event);
475
715
  }
476
716
  }, 100);
477
- }
478
- else {
717
+ } else {
479
718
  reject('Event did not respond in time');
480
719
  }
481
- }, 5000)
720
+ }, 5000);
482
721
  });
483
722
  }
484
723
 
485
724
  public respondToMessage(messageID: string, response: any) {
486
- this.socket.send(JSON.stringify({
487
- event: 'response',
488
- id: messageID,
489
- args: response
490
- }));
491
- console.log("dispatched response to " + messageID)
725
+ this.socket.send(
726
+ JSON.stringify({
727
+ event: 'response',
728
+ id: messageID,
729
+ args: response,
730
+ })
731
+ );
732
+ console.log('dispatched response to ' + messageID);
492
733
  }
493
734
 
494
735
  public waitForResponseFromServer<T>(messageID: string): Promise<T> {
@@ -499,33 +740,35 @@ class OGIAddonWSListener {
499
740
  this.socket.once('message', waiter);
500
741
  return;
501
742
  }
502
- console.log("received response from " + messageID)
743
+ console.log('received response from ' + messageID);
503
744
 
504
745
  if (message.id === messageID) {
505
746
  resolve(message.args);
506
- }
507
- else {
747
+ } else {
508
748
  this.socket.once('message', waiter);
509
749
  }
510
- }
750
+ };
511
751
  this.socket.once('message', waiter);
512
752
  });
513
753
  }
514
754
 
515
- public send(event: OGIAddonClientSentEvent, args: ClientSentEventTypes[OGIAddonClientSentEvent]): string {
755
+ public send(
756
+ event: OGIAddonClientSentEvent,
757
+ args: ClientSentEventTypes[OGIAddonClientSentEvent]
758
+ ): string {
516
759
  // generate a random id
517
760
  const id = Math.random().toString(36).substring(7);
518
- this.socket.send(JSON.stringify({
519
- event,
520
- args,
521
- id
522
- }));
761
+ this.socket.send(
762
+ JSON.stringify({
763
+ event,
764
+ args,
765
+ id,
766
+ })
767
+ );
523
768
  return id;
524
769
  }
525
770
 
526
771
  public close() {
527
772
  this.socket.close();
528
773
  }
529
-
530
-
531
774
  }