ogi-addon 1.2.2 → 1.3.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/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: boolean;
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,22 +256,24 @@ export interface OGIAddonConfiguration {
153
256
 
154
257
  author: string;
155
258
  repository: string;
259
+ storefronts: string[];
260
+ storeFrontServerCapable: boolean;
156
261
  }
157
262
 
158
263
  /**
159
264
  * 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
265
+ * @example
161
266
  * ```typescript
162
267
  * const addon = new OGIAddon({
163
268
  * name: 'Test Addon',
164
- * id: 'test-addon',
269
+ * id: 'test-addon',
165
270
  * description: 'A test addon',
166
271
  * version: '1.0.0',
167
272
  * author: 'OGI Developers',
168
273
  * repository: ''
169
274
  * });
170
275
  * ```
171
- *
276
+ *
172
277
  */
173
278
  export default class OGIAddon {
174
279
  public eventEmitter = new events.EventEmitter();
@@ -176,26 +281,41 @@ export default class OGIAddon {
176
281
  public addonInfo: OGIAddonConfiguration;
177
282
  public config: Configuration = new Configuration({});
178
283
 
179
- constructor(addonInfo: OGIAddonConfiguration) {
180
- this.addonInfo = addonInfo;
284
+ constructor(
285
+ addonInfo: Omit<OGIAddonConfiguration, 'storeFrontServerCapable'>
286
+ ) {
287
+ this.addonInfo = { storeFrontServerCapable: false, ...addonInfo };
181
288
  this.addonWSListener = new OGIAddonWSListener(this, this.eventEmitter);
182
289
  }
183
290
 
184
291
  /**
185
- * Register an event listener for the addon. (See EventListenerTypes)
292
+ * Register an event listener for the addon. (See EventListenerTypes)
186
293
  * @param event {OGIAddonEvent}
187
- * @param listener {EventListenerTypes[OGIAddonEvent]}
294
+ * @param listener {EventListenerTypes[OGIAddonEvent]}
188
295
  */
189
- public on<T extends OGIAddonEvent>(event: T, listener: EventListenerTypes[T]) {
296
+ public on<T extends OGIAddonEvent>(
297
+ event: T,
298
+ listener: EventListenerTypes[T]
299
+ ) {
190
300
  this.eventEmitter.on(event, listener);
301
+ if (event === 'game-details') {
302
+ this.addonInfo.storeFrontServerCapable = true;
303
+ this.addonWSListener.send('flag', {
304
+ flag: 'storeFrontServerCapable',
305
+ value: this.addonInfo.storeFrontServerCapable,
306
+ });
307
+ }
191
308
  }
192
309
 
193
- public emit<T extends OGIAddonEvent>(event: T, ...args: Parameters<EventListenerTypes[T]>) {
310
+ public emit<T extends OGIAddonEvent>(
311
+ event: T,
312
+ ...args: Parameters<EventListenerTypes[T]>
313
+ ) {
194
314
  this.eventEmitter.emit(event, ...args);
195
315
  }
196
316
 
197
317
  /**
198
- * Notify the client using a notification. Provide the type of notification, the message, and an ID.
318
+ * Notify the client using a notification. Provide the type of notification, the message, and an ID.
199
319
  * @param notification {Notification}
200
320
  */
201
321
  public notify(notification: Notification) {
@@ -203,13 +323,19 @@ export default class OGIAddon {
203
323
  }
204
324
 
205
325
  /**
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[]>}
326
+ * Get the app details for a given appID and storefront.
327
+ * @param appID {number}
328
+ * @param storefront {string}
329
+ * @returns {Promise<StoreData>}
209
330
  */
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);
331
+ public async getAppDetails(appID: number, storefront: string) {
332
+ const id = this.addonWSListener.send('get-app-details', {
333
+ appID,
334
+ storefront,
335
+ });
336
+ return await this.addonWSListener.waitForResponseFromServer<
337
+ StoreData | undefined
338
+ >(id);
213
339
  }
214
340
 
215
341
  /**
@@ -223,7 +349,13 @@ export default class OGIAddon {
223
349
  const progress = 0;
224
350
  const logs: string[] = [];
225
351
  const task = new CustomTask(this.addonWSListener, id, progress, logs);
226
- this.addonWSListener.send('task-update', { id, progress, logs, finished: false, failed: undefined });
352
+ this.addonWSListener.send('task-update', {
353
+ id,
354
+ progress,
355
+ logs,
356
+ finished: false,
357
+ failed: undefined,
358
+ });
227
359
  return task;
228
360
  }
229
361
  }
@@ -235,7 +367,12 @@ export class CustomTask {
235
367
  public finished: boolean = false;
236
368
  public ws: OGIAddonWSListener;
237
369
  public failed: string | undefined = undefined;
238
- constructor(ws: OGIAddonWSListener, id: string, progress: number, logs: string[]) {
370
+ constructor(
371
+ ws: OGIAddonWSListener,
372
+ id: string,
373
+ progress: number,
374
+ logs: string[]
375
+ ) {
239
376
  this.id = id;
240
377
  this.progress = progress;
241
378
  this.logs = logs;
@@ -258,7 +395,13 @@ export class CustomTask {
258
395
  this.update();
259
396
  }
260
397
  public update() {
261
- this.ws.send('task-update', { id: this.id, progress: this.progress, logs: this.logs, finished: this.finished, failed: this.failed });
398
+ this.ws.send('task-update', {
399
+ id: this.id,
400
+ progress: this.progress,
401
+ logs: this.logs,
402
+ finished: this.finished,
403
+ failed: this.failed,
404
+ });
262
405
  }
263
406
  }
264
407
  /**
@@ -271,17 +414,27 @@ export class CustomTask {
271
414
  */
272
415
  export class SearchTool<T> {
273
416
  private fuse: Fuse<T>;
274
- constructor(items: T[], keys: string[], options: Omit<IFuseOptions<T>, 'keys'> = { threshold: 0.3, includeScore: true }) {
417
+ constructor(
418
+ items: T[],
419
+ keys: string[],
420
+ options: Omit<IFuseOptions<T>, 'keys'> = {
421
+ threshold: 0.3,
422
+ includeScore: true,
423
+ }
424
+ ) {
275
425
  this.fuse = new Fuse(items, {
276
426
  keys,
277
- ...options
427
+ ...options,
278
428
  });
279
429
  }
280
430
  public search(query: string, limit: number = 10): T[] {
281
- return this.fuse.search(query).slice(0, limit).map(result => result.item);
431
+ return this.fuse
432
+ .search(query)
433
+ .slice(0, limit)
434
+ .map((result) => result.item);
282
435
  }
283
436
  public addItems(items: T[]) {
284
- items.map(item => this.fuse.add(item));
437
+ items.map((item) => this.fuse.add(item));
285
438
  }
286
439
  }
287
440
  /**
@@ -295,7 +448,7 @@ export interface LibraryInfo {
295
448
  launchExecutable: string;
296
449
  launchArguments?: string;
297
450
  capsuleImage: string;
298
- storefront: 'steam' | 'internal';
451
+ storefront: string;
299
452
  addonsource: string;
300
453
  coverImage: string;
301
454
  titleImage?: string;
@@ -303,7 +456,7 @@ export interface LibraryInfo {
303
456
  interface Notification {
304
457
  type: 'warning' | 'error' | 'info' | 'success';
305
458
  message: string;
306
- id: string
459
+ id: string;
307
460
  }
308
461
  class OGIAddonWSListener {
309
462
  private socket: WebSocket;
@@ -311,8 +464,12 @@ class OGIAddonWSListener {
311
464
  public addon: OGIAddon;
312
465
 
313
466
  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.');
467
+ if (
468
+ process.argv[process.argv.length - 1].split('=')[0] !== '--addonSecret'
469
+ ) {
470
+ throw new Error(
471
+ 'No secret provided. This usually happens because the addon was not started by the OGI Addon Server.'
472
+ );
316
473
  }
317
474
  this.addon = ogiAddon;
318
475
  this.eventEmitter = eventEmitter;
@@ -325,7 +482,7 @@ class OGIAddonWSListener {
325
482
  this.send('authenticate', {
326
483
  ...this.addon.addonInfo,
327
484
  secret: process.argv[process.argv.length - 1].split('=')[1],
328
- ogiVersion: VERSION
485
+ ogiVersion: VERSION,
329
486
  });
330
487
 
331
488
  this.eventEmitter.emit('connect');
@@ -339,10 +496,12 @@ class OGIAddonWSListener {
339
496
 
340
497
  this.socket.on('error', (error) => {
341
498
  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.');
499
+ throw new Error(
500
+ 'OGI Addon Server is not running/is unreachable. Please start the server and try again.'
501
+ );
343
502
  }
344
503
  console.error('An error occurred:', error);
345
- })
504
+ });
346
505
 
347
506
  this.socket.on('close', (code, reason) => {
348
507
  if (code === 1008) {
@@ -350,8 +509,8 @@ class OGIAddonWSListener {
350
509
  return;
351
510
  }
352
511
  this.eventEmitter.emit('disconnect', reason);
353
- console.log("Disconnected from OGI Addon Server")
354
- console.error(reason.toString())
512
+ console.log('Disconnected from OGI Addon Server');
513
+ console.error(reason.toString());
355
514
  this.eventEmitter.emit('exit');
356
515
  this.socket.close();
357
516
  });
@@ -359,21 +518,28 @@ class OGIAddonWSListener {
359
518
  this.registerMessageReceiver();
360
519
  }
361
520
 
362
- private async userInputAsked(configBuilt: ConfigurationBuilder, name: string, description: string, socket: WebSocket): Promise<{ [key: string]: number | boolean | string }> {
521
+ private async userInputAsked(
522
+ configBuilt: ConfigurationBuilder,
523
+ name: string,
524
+ description: string,
525
+ socket: WebSocket
526
+ ): Promise<{ [key: string]: number | boolean | string }> {
363
527
  const config = configBuilt.build(false);
364
528
  const id = Math.random().toString(36).substring(7);
365
529
  if (!socket) {
366
530
  return {};
367
531
  }
368
- socket.send(JSON.stringify({
369
- event: 'input-asked',
370
- args: {
371
- config,
372
- name,
373
- description
374
- },
375
- id: id
376
- }));
532
+ socket.send(
533
+ JSON.stringify({
534
+ event: 'input-asked',
535
+ args: {
536
+ config,
537
+ name,
538
+ description,
539
+ },
540
+ id: id,
541
+ })
542
+ );
377
543
  return await this.waitForResponseFromServer(id);
378
544
  }
379
545
 
@@ -384,21 +550,42 @@ class OGIAddonWSListener {
384
550
  case 'config-update':
385
551
  const result = this.addon.config.updateConfig(message.args);
386
552
  if (!result[0]) {
387
- this.respondToMessage(message.id!!, { success: false, error: result[1] });
388
- }
389
- else {
553
+ this.respondToMessage(message.id!!, {
554
+ success: false,
555
+ error: result[1],
556
+ });
557
+ } else {
390
558
  this.respondToMessage(message.id!!, { success: true });
391
559
  }
392
- break
560
+ break;
393
561
  case 'search':
394
- let searchResultEvent = new EventResponse<SearchResult[]>((screen, name, description) => this.userInputAsked(screen, name, description, this.socket));
562
+ let searchResultEvent = new EventResponse<SearchResult[]>(
563
+ (screen, name, description) =>
564
+ this.userInputAsked(screen, name, description, this.socket)
565
+ );
395
566
  this.eventEmitter.emit('search', message.args, searchResultEvent);
396
- const searchResult = await this.waitForEventToRespond(searchResultEvent);
567
+ const searchResult =
568
+ await this.waitForEventToRespond(searchResultEvent);
397
569
  this.respondToMessage(message.id!!, searchResult.data);
398
- break
570
+ break;
399
571
  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);
572
+ let setupEvent = new EventResponse<LibraryInfo>(
573
+ (screen, name, description) =>
574
+ this.userInputAsked(screen, name, description, this.socket)
575
+ );
576
+ this.eventEmitter.emit(
577
+ 'setup',
578
+ {
579
+ path: message.args.path,
580
+ appID: message.args.appID,
581
+ storefront: message.args.storefront,
582
+ type: message.args.type,
583
+ name: message.args.name,
584
+ usedRealDebrid: message.args.usedRealDebrid,
585
+ multiPartFiles: message.args.multiPartFiles,
586
+ },
587
+ setupEvent
588
+ );
402
589
  const interval = setInterval(() => {
403
590
  if (setupEvent.resolved) {
404
591
  clearInterval(interval);
@@ -408,54 +595,104 @@ class OGIAddonWSListener {
408
595
  logs: setupEvent.logs,
409
596
  deferID: message.args.deferID,
410
597
  progress: setupEvent.progress,
411
- failed: setupEvent.failed
598
+ failed: setupEvent.failed,
412
599
  } as ClientSentEventTypes['defer-update']);
413
600
  }, 100);
414
601
  const setupResult = await this.waitForEventToRespond(setupEvent);
415
602
  this.respondToMessage(message.id!!, setupResult.data);
416
- break
603
+ break;
417
604
  case 'library-search':
418
- let librarySearchEvent = new EventResponse<BasicLibraryInfo[]>((screen, name, description) => this.userInputAsked(screen, name, description, this.socket));
605
+ let librarySearchEvent = new EventResponse<BasicLibraryInfo[]>(
606
+ (screen, name, description) =>
607
+ this.userInputAsked(screen, name, description, this.socket)
608
+ );
419
609
  if (this.eventEmitter.listenerCount('game-details') === 0) {
420
610
  this.respondToMessage(message.id!!, []);
421
611
  break;
422
612
  }
423
- this.eventEmitter.emit('library-search', message.args, librarySearchEvent);
424
- const librarySearchResult = await this.waitForEventToRespond(librarySearchEvent);
613
+ this.eventEmitter.emit(
614
+ 'library-search',
615
+ message.args,
616
+ librarySearchEvent
617
+ );
618
+ const librarySearchResult =
619
+ await this.waitForEventToRespond(librarySearchEvent);
425
620
  this.respondToMessage(message.id!!, librarySearchResult.data);
426
- break
621
+ break;
427
622
  case 'game-details':
428
- let gameDetailsEvent = new EventResponse<StoreData>((screen, name, description) => this.userInputAsked(screen, name, description, this.socket));
623
+ let gameDetailsEvent = new EventResponse<StoreData | undefined>(
624
+ (screen, name, description) =>
625
+ this.userInputAsked(screen, name, description, this.socket)
626
+ );
429
627
  if (this.eventEmitter.listenerCount('game-details') === 0) {
430
- this.respondToMessage(message.id!!, { error: 'No event listener for game-details' });
628
+ this.respondToMessage(message.id!!, {
629
+ error: 'No event listener for game-details',
630
+ });
431
631
  break;
432
632
  }
433
- this.eventEmitter.emit('game-details', message.args, gameDetailsEvent);
434
- const gameDetailsResult = await this.waitForEventToRespond(gameDetailsEvent);
633
+ this.eventEmitter.emit(
634
+ 'game-details',
635
+ message.args,
636
+ gameDetailsEvent
637
+ );
638
+ const gameDetailsResult =
639
+ await this.waitForEventToRespond(gameDetailsEvent);
435
640
  this.respondToMessage(message.id!!, gameDetailsResult.data);
436
- break
641
+ break;
437
642
  case 'request-dl':
438
- let requestDLEvent = new EventResponse<SearchResult>((screen, name, description) => this.userInputAsked(screen, name, description, this.socket));
643
+ let requestDLEvent = new EventResponse<SearchResult>(
644
+ (screen, name, description) =>
645
+ this.userInputAsked(screen, name, description, this.socket)
646
+ );
439
647
  if (this.eventEmitter.listenerCount('request-dl') === 0) {
440
- this.respondToMessage(message.id!!, { error: 'No event listener for request-dl' });
648
+ this.respondToMessage(message.id!!, {
649
+ error: 'No event listener for request-dl',
650
+ });
441
651
  break;
442
652
  }
443
- this.eventEmitter.emit('request-dl', message.args.appID, message.args.info, requestDLEvent);
444
- const requestDLResult = await this.waitForEventToRespond(requestDLEvent);
653
+ this.eventEmitter.emit(
654
+ 'request-dl',
655
+ message.args.appID,
656
+ message.args.info,
657
+ requestDLEvent
658
+ );
659
+ const requestDLResult =
660
+ await this.waitForEventToRespond(requestDLEvent);
445
661
  if (requestDLEvent.failed) {
446
- this.respondToMessage(message.id!!, { statusError: requestDLEvent.failed });
662
+ this.respondToMessage(message.id!!, {
663
+ statusError: requestDLEvent.failed,
664
+ });
447
665
  break;
448
666
  }
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.');
667
+ if (
668
+ requestDLEvent.data === undefined ||
669
+ requestDLEvent.data?.downloadType === 'request'
670
+ ) {
671
+ throw new Error(
672
+ 'Request DL event did not return a valid result. Please ensure that the event does not resolve with another `request` download type.'
673
+ );
451
674
  }
452
675
  this.respondToMessage(message.id!!, requestDLResult.data);
453
- break
676
+ break;
677
+ case 'catalog':
678
+ let catalogEvent = new EventResponse<{
679
+ [key: string]: {
680
+ name: string;
681
+ description: string;
682
+ listings: BasicLibraryInfo[];
683
+ };
684
+ }>();
685
+ this.eventEmitter.emit('catalog', catalogEvent);
686
+ const catalogResult = await this.waitForEventToRespond(catalogEvent);
687
+ this.respondToMessage(message.id!!, catalogResult.data);
688
+ break;
454
689
  }
455
690
  });
456
691
  }
457
692
 
458
- private waitForEventToRespond<T>(event: EventResponse<T>): Promise<EventResponse<T>> {
693
+ private waitForEventToRespond<T>(
694
+ event: EventResponse<T>
695
+ ): Promise<EventResponse<T>> {
459
696
  // check the handlers to see if there even is any
460
697
  return new Promise((resolve, reject) => {
461
698
  const dataGet = setInterval(() => {
@@ -474,21 +711,22 @@ class OGIAddonWSListener {
474
711
  resolve(event);
475
712
  }
476
713
  }, 100);
477
- }
478
- else {
714
+ } else {
479
715
  reject('Event did not respond in time');
480
716
  }
481
- }, 5000)
717
+ }, 5000);
482
718
  });
483
719
  }
484
720
 
485
721
  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)
722
+ this.socket.send(
723
+ JSON.stringify({
724
+ event: 'response',
725
+ id: messageID,
726
+ args: response,
727
+ })
728
+ );
729
+ console.log('dispatched response to ' + messageID);
492
730
  }
493
731
 
494
732
  public waitForResponseFromServer<T>(messageID: string): Promise<T> {
@@ -499,33 +737,35 @@ class OGIAddonWSListener {
499
737
  this.socket.once('message', waiter);
500
738
  return;
501
739
  }
502
- console.log("received response from " + messageID)
740
+ console.log('received response from ' + messageID);
503
741
 
504
742
  if (message.id === messageID) {
505
743
  resolve(message.args);
506
- }
507
- else {
744
+ } else {
508
745
  this.socket.once('message', waiter);
509
746
  }
510
- }
747
+ };
511
748
  this.socket.once('message', waiter);
512
749
  });
513
750
  }
514
751
 
515
- public send(event: OGIAddonClientSentEvent, args: ClientSentEventTypes[OGIAddonClientSentEvent]): string {
752
+ public send(
753
+ event: OGIAddonClientSentEvent,
754
+ args: ClientSentEventTypes[OGIAddonClientSentEvent]
755
+ ): string {
516
756
  // generate a random id
517
757
  const id = Math.random().toString(36).substring(7);
518
- this.socket.send(JSON.stringify({
519
- event,
520
- args,
521
- id
522
- }));
758
+ this.socket.send(
759
+ JSON.stringify({
760
+ event,
761
+ args,
762
+ id,
763
+ })
764
+ );
523
765
  return id;
524
766
  }
525
767
 
526
768
  public close() {
527
769
  this.socket.close();
528
770
  }
529
-
530
-
531
771
  }