ogi-addon 1.2.0 → 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,20 +60,25 @@ 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;
41
71
  logs: string[];
42
72
  finished: boolean;
73
+ failed: string | undefined;
74
+ };
75
+ 'get-app-details': {
76
+ appID: number;
77
+ storefront: string;
78
+ };
79
+ flag: {
80
+ flag: string;
81
+ value: boolean;
43
82
  };
44
83
  }
45
84
 
@@ -47,79 +86,139 @@ export type BasicLibraryInfo = {
47
86
  name: string;
48
87
  capsuleImage: string;
49
88
  appID: number;
50
- }
89
+ storefront: string;
90
+ };
51
91
 
52
92
  export interface EventListenerTypes {
53
93
  /**
54
94
  * This event is emitted when the addon connects to the OGI Addon Server. Addon does not need to resolve anything.
55
- * @param socket
56
- * @returns
95
+ * @param socket
96
+ * @returns
57
97
  */
58
98
  connect: (socket: ws) => void;
59
99
 
60
100
  /**
61
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.
62
- * @param reason
63
- * @returns
102
+ * @param reason
103
+ * @returns
64
104
  */
65
105
  disconnect: (reason: string) => void;
66
106
  /**
67
- * 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)
68
- * @param config
69
- * @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
70
110
  */
71
111
  configure: (config: ConfigurationBuilder) => ConfigurationBuilder;
72
112
  /**
73
- * This event is called when the client provides a response to any event. This should be treated as middleware.
74
- * @param response
75
- * @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
76
116
  */
77
117
  response: (response: any) => void;
78
118
 
79
119
  /**
80
- * This event is called when the client requests for the addon to authenticate itself. You don't need to provide any info.
81
- * @param config
82
- * @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
83
123
  */
84
124
  authenticate: (config: any) => void;
85
125
  /**
86
- * 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)
87
- * @param query
88
- * @param event
89
- * @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
90
130
  */
91
- 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;
92
135
  /**
93
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)
94
- * @param data
95
- * @param event
96
- * @returns
137
+ * @param data
138
+ * @param event
139
+ * @returns
97
140
  */
98
141
  setup: (
99
142
  data: {
100
- path: string,
101
- type: 'direct' | 'torrent' | 'magnet',
102
- name: string,
103
- usedRealDebrid: boolean,
143
+ path: string;
144
+ type: 'direct' | 'torrent' | 'magnet';
145
+ name: string;
146
+ usedRealDebrid: boolean;
104
147
  multiPartFiles?: {
105
- name: string,
106
- downloadURL: string
107
- }[],
108
- appID: number,
109
- storefront: 'steam' | 'internal'
110
- }, 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
+ >
165
+ ) => void;
166
+
167
+ /**
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[]>
111
176
  ) => void;
112
177
 
113
178
  /**
114
- * This event is emitted when the client requires for a search to be performed. Input is the search query.
115
- * @param query
116
- * @param event
117
- * @returns
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
118
183
  */
119
- 'library-search': (query: string, event: EventResponse<BasicLibraryInfo[]>) => void;
120
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
+ */
121
190
  exit: () => void;
122
- '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;
123
222
  }
124
223
 
125
224
  export interface StoreData {
@@ -144,6 +243,11 @@ export interface WebsocketMessageServer {
144
243
  id?: string;
145
244
  args: any;
146
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
+ */
147
251
  export interface OGIAddonConfiguration {
148
252
  name: string;
149
253
  id: string;
@@ -152,22 +256,24 @@ export interface OGIAddonConfiguration {
152
256
 
153
257
  author: string;
154
258
  repository: string;
259
+ storefronts: string[];
260
+ storeFrontServerCapable: boolean;
155
261
  }
156
262
 
157
263
  /**
158
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.
159
- * @example
265
+ * @example
160
266
  * ```typescript
161
267
  * const addon = new OGIAddon({
162
268
  * name: 'Test Addon',
163
- * id: 'test-addon',
269
+ * id: 'test-addon',
164
270
  * description: 'A test addon',
165
271
  * version: '1.0.0',
166
272
  * author: 'OGI Developers',
167
273
  * repository: ''
168
274
  * });
169
275
  * ```
170
- *
276
+ *
171
277
  */
172
278
  export default class OGIAddon {
173
279
  public eventEmitter = new events.EventEmitter();
@@ -175,26 +281,41 @@ export default class OGIAddon {
175
281
  public addonInfo: OGIAddonConfiguration;
176
282
  public config: Configuration = new Configuration({});
177
283
 
178
- constructor(addonInfo: OGIAddonConfiguration) {
179
- this.addonInfo = addonInfo;
284
+ constructor(
285
+ addonInfo: Omit<OGIAddonConfiguration, 'storeFrontServerCapable'>
286
+ ) {
287
+ this.addonInfo = { storeFrontServerCapable: false, ...addonInfo };
180
288
  this.addonWSListener = new OGIAddonWSListener(this, this.eventEmitter);
181
289
  }
182
290
 
183
291
  /**
184
- * Register an event listener for the addon. (See EventListenerTypes)
292
+ * Register an event listener for the addon. (See EventListenerTypes)
185
293
  * @param event {OGIAddonEvent}
186
- * @param listener {EventListenerTypes[OGIAddonEvent]}
294
+ * @param listener {EventListenerTypes[OGIAddonEvent]}
187
295
  */
188
- public on<T extends OGIAddonEvent>(event: T, listener: EventListenerTypes[T]) {
296
+ public on<T extends OGIAddonEvent>(
297
+ event: T,
298
+ listener: EventListenerTypes[T]
299
+ ) {
189
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
+ }
190
308
  }
191
309
 
192
- 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
+ ) {
193
314
  this.eventEmitter.emit(event, ...args);
194
315
  }
195
316
 
196
317
  /**
197
- * 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.
198
319
  * @param notification {Notification}
199
320
  */
200
321
  public notify(notification: Notification) {
@@ -202,13 +323,19 @@ export default class OGIAddon {
202
323
  }
203
324
 
204
325
  /**
205
- * Search for items in the OGI Steam-Synced Library. Query can either be a Steam AppID or a Steam Game Name.
206
- * @param query {string}
207
- * @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>}
208
330
  */
209
- public async steamSearch(query: string, strict: boolean = false) {
210
- const id = this.addonWSListener.send('steam-search', { query, strict });
211
- 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);
212
339
  }
213
340
 
214
341
  /**
@@ -222,7 +349,13 @@ export default class OGIAddon {
222
349
  const progress = 0;
223
350
  const logs: string[] = [];
224
351
  const task = new CustomTask(this.addonWSListener, id, progress, logs);
225
- this.addonWSListener.send('task-update', { id, progress, logs, finished: false });
352
+ this.addonWSListener.send('task-update', {
353
+ id,
354
+ progress,
355
+ logs,
356
+ finished: false,
357
+ failed: undefined,
358
+ });
226
359
  return task;
227
360
  }
228
361
  }
@@ -233,7 +366,13 @@ export class CustomTask {
233
366
  public logs: string[];
234
367
  public finished: boolean = false;
235
368
  public ws: OGIAddonWSListener;
236
- constructor(ws: OGIAddonWSListener, id: string, progress: number, logs: string[]) {
369
+ public failed: string | undefined = undefined;
370
+ constructor(
371
+ ws: OGIAddonWSListener,
372
+ id: string,
373
+ progress: number,
374
+ logs: string[]
375
+ ) {
237
376
  this.id = id;
238
377
  this.progress = progress;
239
378
  this.logs = logs;
@@ -247,12 +386,22 @@ export class CustomTask {
247
386
  this.finished = true;
248
387
  this.update();
249
388
  }
389
+ public fail(message: string) {
390
+ this.failed = message;
391
+ this.update();
392
+ }
250
393
  public setProgress(progress: number) {
251
394
  this.progress = progress;
252
395
  this.update();
253
396
  }
254
397
  public update() {
255
- this.ws.send('task-update', { id: this.id, progress: this.progress, logs: this.logs, finished: this.finished });
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
+ });
256
405
  }
257
406
  }
258
407
  /**
@@ -265,17 +414,27 @@ export class CustomTask {
265
414
  */
266
415
  export class SearchTool<T> {
267
416
  private fuse: Fuse<T>;
268
- 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
+ ) {
269
425
  this.fuse = new Fuse(items, {
270
426
  keys,
271
- ...options
427
+ ...options,
272
428
  });
273
429
  }
274
430
  public search(query: string, limit: number = 10): T[] {
275
- 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);
276
435
  }
277
436
  public addItems(items: T[]) {
278
- items.map(item => this.fuse.add(item));
437
+ items.map((item) => this.fuse.add(item));
279
438
  }
280
439
  }
281
440
  /**
@@ -289,7 +448,7 @@ export interface LibraryInfo {
289
448
  launchExecutable: string;
290
449
  launchArguments?: string;
291
450
  capsuleImage: string;
292
- storefront: 'steam' | 'internal';
451
+ storefront: string;
293
452
  addonsource: string;
294
453
  coverImage: string;
295
454
  titleImage?: string;
@@ -297,7 +456,7 @@ export interface LibraryInfo {
297
456
  interface Notification {
298
457
  type: 'warning' | 'error' | 'info' | 'success';
299
458
  message: string;
300
- id: string
459
+ id: string;
301
460
  }
302
461
  class OGIAddonWSListener {
303
462
  private socket: WebSocket;
@@ -305,8 +464,12 @@ class OGIAddonWSListener {
305
464
  public addon: OGIAddon;
306
465
 
307
466
  constructor(ogiAddon: OGIAddon, eventEmitter: events.EventEmitter) {
308
- if (process.argv[process.argv.length - 1].split('=')[0] !== '--addonSecret') {
309
- 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
+ );
310
473
  }
311
474
  this.addon = ogiAddon;
312
475
  this.eventEmitter = eventEmitter;
@@ -319,7 +482,7 @@ class OGIAddonWSListener {
319
482
  this.send('authenticate', {
320
483
  ...this.addon.addonInfo,
321
484
  secret: process.argv[process.argv.length - 1].split('=')[1],
322
- ogiVersion: VERSION
485
+ ogiVersion: VERSION,
323
486
  });
324
487
 
325
488
  this.eventEmitter.emit('connect');
@@ -333,10 +496,12 @@ class OGIAddonWSListener {
333
496
 
334
497
  this.socket.on('error', (error) => {
335
498
  if (error.message.includes('Failed to connect')) {
336
- 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
+ );
337
502
  }
338
503
  console.error('An error occurred:', error);
339
- })
504
+ });
340
505
 
341
506
  this.socket.on('close', (code, reason) => {
342
507
  if (code === 1008) {
@@ -344,8 +509,8 @@ class OGIAddonWSListener {
344
509
  return;
345
510
  }
346
511
  this.eventEmitter.emit('disconnect', reason);
347
- console.log("Disconnected from OGI Addon Server")
348
- console.error(reason.toString())
512
+ console.log('Disconnected from OGI Addon Server');
513
+ console.error(reason.toString());
349
514
  this.eventEmitter.emit('exit');
350
515
  this.socket.close();
351
516
  });
@@ -353,21 +518,28 @@ class OGIAddonWSListener {
353
518
  this.registerMessageReceiver();
354
519
  }
355
520
 
356
- 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 }> {
357
527
  const config = configBuilt.build(false);
358
528
  const id = Math.random().toString(36).substring(7);
359
529
  if (!socket) {
360
530
  return {};
361
531
  }
362
- socket.send(JSON.stringify({
363
- event: 'input-asked',
364
- args: {
365
- config,
366
- name,
367
- description
368
- },
369
- id: id
370
- }));
532
+ socket.send(
533
+ JSON.stringify({
534
+ event: 'input-asked',
535
+ args: {
536
+ config,
537
+ name,
538
+ description,
539
+ },
540
+ id: id,
541
+ })
542
+ );
371
543
  return await this.waitForResponseFromServer(id);
372
544
  }
373
545
 
@@ -378,21 +550,42 @@ class OGIAddonWSListener {
378
550
  case 'config-update':
379
551
  const result = this.addon.config.updateConfig(message.args);
380
552
  if (!result[0]) {
381
- this.respondToMessage(message.id!!, { success: false, error: result[1] });
382
- }
383
- else {
553
+ this.respondToMessage(message.id!!, {
554
+ success: false,
555
+ error: result[1],
556
+ });
557
+ } else {
384
558
  this.respondToMessage(message.id!!, { success: true });
385
559
  }
386
- break
560
+ break;
387
561
  case 'search':
388
- 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
+ );
389
566
  this.eventEmitter.emit('search', message.args, searchResultEvent);
390
- const searchResult = await this.waitForEventToRespond(searchResultEvent);
567
+ const searchResult =
568
+ await this.waitForEventToRespond(searchResultEvent);
391
569
  this.respondToMessage(message.id!!, searchResult.data);
392
- break
570
+ break;
393
571
  case 'setup':
394
- let setupEvent = new EventResponse<LibraryInfo>((screen, name, description) => this.userInputAsked(screen, name, description, this.socket));
395
- 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
+ );
396
589
  const interval = setInterval(() => {
397
590
  if (setupEvent.resolved) {
398
591
  clearInterval(interval);
@@ -401,50 +594,105 @@ class OGIAddonWSListener {
401
594
  this.send('defer-update', {
402
595
  logs: setupEvent.logs,
403
596
  deferID: message.args.deferID,
404
- progress: setupEvent.progress
405
- } as any);
597
+ progress: setupEvent.progress,
598
+ failed: setupEvent.failed,
599
+ } as ClientSentEventTypes['defer-update']);
406
600
  }, 100);
407
601
  const setupResult = await this.waitForEventToRespond(setupEvent);
408
602
  this.respondToMessage(message.id!!, setupResult.data);
409
- break
603
+ break;
410
604
  case 'library-search':
411
- 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
+ );
412
609
  if (this.eventEmitter.listenerCount('game-details') === 0) {
413
610
  this.respondToMessage(message.id!!, []);
414
611
  break;
415
612
  }
416
- this.eventEmitter.emit('library-search', message.args, librarySearchEvent);
417
- 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);
418
620
  this.respondToMessage(message.id!!, librarySearchResult.data);
419
- break
621
+ break;
420
622
  case 'game-details':
421
- 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
+ );
422
627
  if (this.eventEmitter.listenerCount('game-details') === 0) {
423
- 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
+ });
424
631
  break;
425
632
  }
426
- this.eventEmitter.emit('game-details', message.args, gameDetailsEvent);
427
- 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);
428
640
  this.respondToMessage(message.id!!, gameDetailsResult.data);
429
- break
641
+ break;
430
642
  case 'request-dl':
431
- 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
+ );
432
647
  if (this.eventEmitter.listenerCount('request-dl') === 0) {
433
- 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
+ });
651
+ break;
652
+ }
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);
661
+ if (requestDLEvent.failed) {
662
+ this.respondToMessage(message.id!!, {
663
+ statusError: requestDLEvent.failed,
664
+ });
434
665
  break;
435
666
  }
436
- this.eventEmitter.emit('request-dl', message.args.appID, message.args.info, requestDLEvent);
437
- const requestDLResult = await this.waitForEventToRespond(requestDLEvent);
438
- if (requestDLEvent.data === null || requestDLEvent.data?.downloadType === 'request') {
439
- 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
+ );
440
674
  }
441
675
  this.respondToMessage(message.id!!, requestDLResult.data);
442
- 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;
443
689
  }
444
690
  });
445
691
  }
446
692
 
447
- private waitForEventToRespond<T>(event: EventResponse<T>): Promise<EventResponse<T>> {
693
+ private waitForEventToRespond<T>(
694
+ event: EventResponse<T>
695
+ ): Promise<EventResponse<T>> {
448
696
  // check the handlers to see if there even is any
449
697
  return new Promise((resolve, reject) => {
450
698
  const dataGet = setInterval(() => {
@@ -463,21 +711,22 @@ class OGIAddonWSListener {
463
711
  resolve(event);
464
712
  }
465
713
  }, 100);
466
- }
467
- else {
714
+ } else {
468
715
  reject('Event did not respond in time');
469
716
  }
470
- }, 5000)
717
+ }, 5000);
471
718
  });
472
719
  }
473
720
 
474
721
  public respondToMessage(messageID: string, response: any) {
475
- this.socket.send(JSON.stringify({
476
- event: 'response',
477
- id: messageID,
478
- args: response
479
- }));
480
- 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);
481
730
  }
482
731
 
483
732
  public waitForResponseFromServer<T>(messageID: string): Promise<T> {
@@ -488,33 +737,35 @@ class OGIAddonWSListener {
488
737
  this.socket.once('message', waiter);
489
738
  return;
490
739
  }
491
- console.log("received response from " + messageID)
740
+ console.log('received response from ' + messageID);
492
741
 
493
742
  if (message.id === messageID) {
494
743
  resolve(message.args);
495
- }
496
- else {
744
+ } else {
497
745
  this.socket.once('message', waiter);
498
746
  }
499
- }
747
+ };
500
748
  this.socket.once('message', waiter);
501
749
  });
502
750
  }
503
751
 
504
- public send(event: OGIAddonClientSentEvent, args: ClientSentEventTypes[OGIAddonClientSentEvent]): string {
752
+ public send(
753
+ event: OGIAddonClientSentEvent,
754
+ args: ClientSentEventTypes[OGIAddonClientSentEvent]
755
+ ): string {
505
756
  // generate a random id
506
757
  const id = Math.random().toString(36).substring(7);
507
- this.socket.send(JSON.stringify({
508
- event,
509
- args,
510
- id
511
- }));
758
+ this.socket.send(
759
+ JSON.stringify({
760
+ event,
761
+ args,
762
+ id,
763
+ })
764
+ );
512
765
  return id;
513
766
  }
514
767
 
515
768
  public close() {
516
769
  this.socket.close();
517
770
  }
518
-
519
-
520
771
  }