ogi-addon 3.1.0 → 4.0.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/build/main.mjs CHANGED
@@ -2,13 +2,13 @@ import EventResponse from "./EventResponse.mjs";
2
2
  import { extraction } from "./extraction.mjs";
3
3
  import { ConfigurationBuilder } from "./config/ConfigurationBuilder.mjs";
4
4
  import { Configuration } from "./config/Configuration.mjs";
5
- import ws from "ws";
5
+ import { EventResponseSocket, randomMessageId } from "@ogi-sdk/connect";
6
6
  import events from "node:events";
7
7
  import { z as z$1 } from "zod";
8
8
  import Fuse from "fuse.js";
9
9
 
10
10
  //#region package.json
11
- var version = "3.1.0";
11
+ var version = "4.0.0";
12
12
 
13
13
  //#endregion
14
14
  //#region src/main.ts
@@ -43,8 +43,8 @@ var OGIAddon = class {
43
43
  }
44
44
  /**
45
45
  * Register an event listener for the addon. (See EventListenerTypes)
46
- * @param event {OGIAddonEvent}
47
- * @param listener {EventListenerTypes[OGIAddonEvent]}
46
+ * @param event {OGIAddonSDKEventListener}
47
+ * @param listener {EventListenerTypes[OGIAddonSDKEventListener]}
48
48
  */
49
49
  on(event, listener) {
50
50
  this.eventEmitter.on(event, listener);
@@ -76,18 +76,16 @@ var OGIAddon = class {
76
76
  * @returns {Promise<StoreData>}
77
77
  */
78
78
  async getAppDetails(appID, storefront) {
79
- const id = this.addonWSListener.send("get-app-details", {
79
+ return await this.addonWSListener.requestResponse("get-app-details", {
80
80
  appID,
81
81
  storefront
82
82
  });
83
- return await this.addonWSListener.waitForResponseFromServer(id);
84
83
  }
85
84
  async searchGame(query, storefront) {
86
- const id = this.addonWSListener.send("search-app-name", {
85
+ return await this.addonWSListener.requestResponse("search-app-name", {
87
86
  query,
88
87
  storefront
89
88
  });
90
- return await this.addonWSListener.waitForResponseFromServer(id);
91
89
  }
92
90
  /**
93
91
  * Notify the OGI Addon Server that you are performing a background task. This can be used to help users understand what is happening in the background.
@@ -305,81 +303,86 @@ const ZodLibraryInfo = z$1.object({
305
303
  });
306
304
  var OGIAddonWSListener = class {
307
305
  socket;
306
+ transport;
308
307
  eventEmitter;
309
308
  addon;
310
309
  constructor(ogiAddon, eventEmitter) {
311
- if (process.argv[process.argv.length - 1].split("=")[0] !== "--addonSecret") throw new Error("No secret provided. This usually happens because the addon was not started by the OGI Addon Server.");
310
+ const secret = process.argv.find((arg) => arg.startsWith("--addonSecret="))?.split("=")[1];
311
+ if (!secret) throw new Error("No secret provided. This usually happens because the addon was not started by the OGI Addon Server.");
312
+ let port = process.argv.find((arg) => arg.startsWith("--addonPort="))?.split("=")[1];
313
+ if (!port) port = defaultPort.toString();
312
314
  this.addon = ogiAddon;
313
315
  this.eventEmitter = eventEmitter;
314
- this.socket = new ws("ws://localhost:" + defaultPort);
315
- this.socket.on("open", () => {
316
+ const WebSocketConstructor = globalThis.WebSocket;
317
+ if (!WebSocketConstructor) throw new Error("WebSocket is not available in this runtime");
318
+ this.socket = new WebSocketConstructor("ws://localhost:" + port);
319
+ this.transport = new EventResponseSocket(this.socket);
320
+ this.socket.addEventListener("open", () => {
316
321
  console.log("Connected to OGI Addon Server");
317
322
  console.log("OGI Addon Server Version:", VERSION);
318
323
  this.send("authenticate", {
319
324
  ...this.addon.addonInfo,
320
- secret: process.argv[process.argv.length - 1].split("=")[1],
325
+ secret,
321
326
  ogiVersion: VERSION
322
327
  });
323
328
  let configBuilder = new ConfigurationBuilder();
324
329
  this.eventEmitter.emit("configure", configBuilder);
325
330
  this.send("configure", configBuilder.build(false));
326
331
  this.addon.config = new Configuration(configBuilder.build(true));
327
- const configListener = (event) => {
328
- if (event === void 0) return;
329
- let data;
330
- if (typeof event === "string") data = event;
331
- else if (event instanceof Buffer) data = event.toString();
332
- else if (event && typeof event.data === "string") data = event.data;
333
- else if (event && event.data instanceof Buffer) data = event.data.toString();
334
- else data = event.toString();
335
- if (JSON.parse(data).event === "config-update") {
336
- console.log("Config update received");
337
- this.socket.off("message", configListener);
338
- this.eventEmitter.emit("connect", new EventResponse((screen, name, description) => {
339
- return this.userInputAsked(screen, name, description, this.socket);
340
- }));
341
- }
342
- };
343
- this.socket.on("message", configListener);
332
+ const unsubscribeConfigListener = this.transport.on("config-update", () => {
333
+ console.log("Config update received");
334
+ unsubscribeConfigListener();
335
+ this.eventEmitter.emit("connect", new EventResponse((screen, name, description) => {
336
+ return this.userInputAsked(screen, name, description);
337
+ }));
338
+ });
344
339
  });
345
- this.socket.on("error", (error) => {
346
- if (error.message.includes("Failed to connect")) throw new Error("OGI Addon Server is not running/is unreachable. Please start the server and try again.");
347
- console.error("An error occurred:", error);
340
+ this.socket.addEventListener("error", (event) => {
341
+ this.transport.rejectPendingResponses("Websocket error");
342
+ if ((event instanceof ErrorEvent ? event.message : event.type).includes("Failed to connect")) throw new Error("OGI Addon Server is not running/is unreachable. Please start the server and try again.");
343
+ console.error("An error occurred:", event);
348
344
  });
349
- this.socket.on("close", (code, reason) => {
350
- if (code === 1008) {
351
- console.error("Authentication failed:", reason);
345
+ this.socket.addEventListener("close", (event) => {
346
+ this.transport.rejectPendingResponses("Websocket closed");
347
+ if (event.code === 1008) {
348
+ console.error("Authentication failed:", event.reason);
352
349
  return;
353
350
  }
354
- this.eventEmitter.emit("disconnect", reason);
351
+ this.eventEmitter.emit("disconnect", event.reason);
355
352
  console.log("Disconnected from OGI Addon Server");
356
- console.error(reason.toString());
353
+ console.error(event.reason);
357
354
  this.eventEmitter.emit("exit");
358
355
  this.socket.close();
359
356
  });
360
357
  this.registerMessageReceiver();
361
358
  }
362
- async userInputAsked(configBuilt, name, description, socket) {
359
+ async userInputAsked(configBuilt, name, description) {
363
360
  const config = configBuilt.build(false);
364
- const id = Math.random().toString(36).substring(7);
365
- if (!socket) throw new Error("Socket is not connected");
366
- socket.send(JSON.stringify({
361
+ return (await this.transport.send({
367
362
  event: "input-asked",
368
363
  args: {
369
364
  config,
370
365
  name,
371
366
  description
372
- },
373
- id
374
- }));
375
- return await this.waitForResponseFromServer(id);
367
+ }
368
+ }, { expectResponse: true })).args;
376
369
  }
377
370
  /**
378
371
  * Registers the message receiver for the socket. This is used to receive messages from the server and handle them.
379
372
  */
380
373
  registerMessageReceiver() {
381
- this.socket.on("message", async (data) => {
382
- const message = JSON.parse(data);
374
+ for (const event of [
375
+ "config-update",
376
+ "search",
377
+ "setup",
378
+ "library-search",
379
+ "game-details",
380
+ "check-for-updates",
381
+ "request-dl",
382
+ "catalog",
383
+ "task-run",
384
+ "launch-app"
385
+ ]) this.transport.on(event, async (message) => {
383
386
  switch (message.event) {
384
387
  case "config-update":
385
388
  const result = this.addon.config.updateConfig(message.args);
@@ -393,7 +396,7 @@ var OGIAddonWSListener = class {
393
396
  await this.handleEventWithResponse(message, (event) => this.eventEmitter.emit("search", message.args, event));
394
397
  break;
395
398
  case "setup": {
396
- let setupEvent = new EventResponse((screen, name, description) => this.userInputAsked(screen, name, description, this.socket));
399
+ let setupEvent = new EventResponse((screen, name, description) => this.userInputAsked(screen, name, description));
397
400
  this.eventEmitter.emit("setup", message.args, setupEvent);
398
401
  const interval = setInterval(() => {
399
402
  if (setupEvent.resolved) {
@@ -402,7 +405,7 @@ var OGIAddonWSListener = class {
402
405
  }
403
406
  this.send("defer-update", {
404
407
  logs: setupEvent.logs,
405
- deferID: message.args.deferID,
408
+ deferID: message.args,
406
409
  progress: setupEvent.progress,
407
410
  failed: setupEvent.failed
408
411
  });
@@ -424,12 +427,13 @@ var OGIAddonWSListener = class {
424
427
  await this.handleEventWithResponse(message, (event) => this.eventEmitter.emit("check-for-updates", message.args, event));
425
428
  break;
426
429
  case "request-dl":
427
- let requestDLEvent = new EventResponse((screen, name, description) => this.userInputAsked(screen, name, description, this.socket));
430
+ let requestDLEvent = new EventResponse((screen, name, description) => this.userInputAsked(screen, name, description));
428
431
  if (this.eventEmitter.listenerCount("request-dl") === 0) {
429
432
  this.respondToMessage(message.id, { error: "No event listener for request-dl" }, requestDLEvent);
430
433
  break;
431
434
  }
432
- this.eventEmitter.emit("request-dl", message.args.appID, message.args.info, requestDLEvent);
435
+ const { appID, info } = message.args;
436
+ this.eventEmitter.emit("request-dl", appID, info, requestDLEvent);
433
437
  const requestDLResult = await this.waitForEventToRespond(requestDLEvent);
434
438
  if (requestDLEvent.failed) {
435
439
  this.respondToMessage(message.id, void 0, requestDLEvent);
@@ -442,34 +446,36 @@ var OGIAddonWSListener = class {
442
446
  await this.handleEventWithResponseNoInput(message, (event) => this.eventEmitter.emit("catalog", event));
443
447
  break;
444
448
  case "task-run": {
445
- let taskRunEvent = new EventResponse((screen, name, description) => this.userInputAsked(screen, name, description, this.socket));
446
- const taskName = message.args.taskName && typeof message.args.taskName === "string" ? message.args.taskName : message.args.manifest && typeof message.args.manifest === "object" ? message.args.manifest.__taskName : void 0;
449
+ let taskRunEvent = new EventResponse((screen, name, description) => this.userInputAsked(screen, name, description));
450
+ const args = message.args;
451
+ const taskName = args.taskName && typeof args.taskName === "string" ? args.taskName : args.manifest && typeof args.manifest === "object" ? args.manifest.__taskName : void 0;
447
452
  if (taskName && typeof taskName === "string" && this.addon.hasTaskHandler(taskName)) {
448
453
  const handler = this.addon.getTaskHandler(taskName);
449
454
  const task = new Task(taskRunEvent);
455
+ const interval = setInterval(() => {
456
+ if (taskRunEvent.resolved) {
457
+ clearInterval(interval);
458
+ return;
459
+ }
460
+ this.send("defer-update", {
461
+ logs: taskRunEvent.logs,
462
+ deferID: args.deferID ?? "",
463
+ progress: taskRunEvent.progress,
464
+ failed: taskRunEvent.failed
465
+ });
466
+ }, 100);
450
467
  try {
451
- const interval = setInterval(() => {
452
- if (taskRunEvent.resolved) {
453
- clearInterval(interval);
454
- return;
455
- }
456
- this.send("defer-update", {
457
- logs: taskRunEvent.logs,
458
- deferID: message.args.deferID,
459
- progress: taskRunEvent.progress,
460
- failed: taskRunEvent.failed
461
- });
462
- }, 100);
463
468
  const result = handler(task, {
464
- manifest: message.args.manifest || {},
465
- downloadPath: message.args.downloadPath || "",
466
- name: message.args.name || "",
467
- libraryInfo: message.args.libraryInfo
469
+ manifest: args.manifest || {},
470
+ downloadPath: args.downloadPath || "",
471
+ name: args.name || "",
472
+ libraryInfo: args.libraryInfo
468
473
  });
469
474
  if (result instanceof Promise) await result;
470
- clearInterval(interval);
471
475
  } catch (error) {
472
476
  taskRunEvent.fail(error instanceof Error ? error.message : String(error));
477
+ } finally {
478
+ clearInterval(interval);
473
479
  }
474
480
  } else taskRunEvent.fail(taskName ? `No task handler registered for task name: ${taskName}` : "No task name provided");
475
481
  const taskRunResult = await this.waitForEventToRespond(taskRunEvent);
@@ -508,7 +514,7 @@ var OGIAddonWSListener = class {
508
514
  * If options.requireListener is set and that event has no listeners, responds with options.noListenerError and returns.
509
515
  */
510
516
  async handleEventWithResponse(message, emit, options) {
511
- const event = new EventResponse((screen, name, description) => this.userInputAsked(screen, name, description, this.socket));
517
+ const event = new EventResponse((screen, name, description) => this.userInputAsked(screen, name, description));
512
518
  if (options && this.eventEmitter.listenerCount(options.requireListener) === 0) {
513
519
  this.respondToMessage(message.id, { error: options.noListenerError }, event);
514
520
  return;
@@ -527,36 +533,27 @@ var OGIAddonWSListener = class {
527
533
  this.respondToMessage(message.id, result.data, event);
528
534
  }
529
535
  respondToMessage(messageID, response, originalEvent) {
530
- this.socket.send(JSON.stringify({
536
+ this.transport.send({
531
537
  event: "response",
532
538
  id: messageID,
533
539
  args: response,
534
540
  statusError: originalEvent ? originalEvent.failed : void 0
535
- }));
541
+ }, { expectResponse: false });
536
542
  console.log("dispatched response to " + messageID);
537
543
  }
538
- waitForResponseFromServer(messageID) {
539
- return new Promise((resolve) => {
540
- const waiter = (data) => {
541
- const message = JSON.parse(data);
542
- if (message.event !== "response") {
543
- this.socket.once("message", waiter);
544
- return;
545
- }
546
- console.log("received response from " + messageID);
547
- if (message.id === messageID) resolve(message.args);
548
- else this.socket.once("message", waiter);
549
- };
550
- this.socket.once("message", waiter);
551
- });
544
+ async requestResponse(event, args) {
545
+ return (await this.transport.send({
546
+ event,
547
+ args
548
+ }, { expectResponse: true })).args;
552
549
  }
553
550
  send(event, args) {
554
- const id = Math.random().toString(36).substring(7);
555
- this.socket.send(JSON.stringify({
551
+ const id = randomMessageId();
552
+ this.transport.send({
556
553
  event,
557
554
  args,
558
555
  id
559
- }));
556
+ }, { expectResponse: false });
560
557
  return id;
561
558
  }
562
559
  close() {
@@ -1 +1 @@
1
- {"version":3,"file":"main.mjs","names":["pjson.version","z"],"sources":["../package.json","../src/main.ts"],"sourcesContent":["","import ws, { WebSocket } from 'ws';\nimport events from 'node:events';\nimport { ConfigurationBuilder } from './config/ConfigurationBuilder';\nimport type { ConfigurationFile } from './config/ConfigurationBuilder';\nimport { Configuration } from './config/Configuration';\nimport EventResponse from './EventResponse';\nimport type { SearchResult } from './SearchEngine';\nimport Fuse, { IFuseOptions } from 'fuse.js';\n\n/**\n * Exposed events that the programmer can use to listen to and emit events.\n */\nexport type OGIAddonEvent =\n | 'connect'\n | 'disconnect'\n | 'configure'\n | 'authenticate'\n | 'search'\n | 'setup'\n | 'library-search'\n | 'game-details'\n | 'exit'\n | 'check-for-updates'\n | 'request-dl'\n | 'catalog'\n | 'launch-app';\n\n/**\n * The events that the client can send to the server and are handled by the server.\n */\nexport type OGIAddonClientSentEvent =\n | 'response'\n | 'authenticate'\n | 'configure'\n | 'defer-update'\n | 'notification'\n | 'input-asked'\n | 'get-app-details'\n | 'search-app-name'\n | 'flag'\n | 'task-update';\n\n/**\n * The events that the server sends to the client\n * This is the events that the server can send to the client and are handled by the client.\n */\nexport type OGIAddonServerSentEvent =\n | 'authenticate'\n | 'configure'\n | 'config-update'\n | 'launch-app'\n | 'search'\n | 'setup'\n | 'response'\n | 'library-search'\n | 'check-for-updates'\n | 'task-run'\n | 'game-details'\n | 'request-dl'\n | 'catalog';\nexport { ConfigurationBuilder, Configuration, EventResponse };\nexport { extraction };\nexport type { SearchResult };\nconst defaultPort = 7654;\nimport pjson from '../package.json';\nimport { z } from 'zod';\nimport { extraction } from './extraction';\nexport const VERSION = pjson.version;\n\nexport interface ClientSentEventTypes {\n response: any;\n authenticate: {\n name: string;\n id: string;\n description: string;\n version: string;\n author: string;\n };\n configure: ConfigurationFile;\n 'defer-update': {\n logs: string[];\n progress: number;\n };\n notification: Notification;\n 'input-asked': ConfigurationBuilder<\n Record<string, string | number | boolean>\n >;\n 'task-update': {\n id: string;\n progress: number;\n logs: string[];\n finished: boolean;\n failed: string | undefined;\n };\n 'get-app-details': {\n appID: number;\n storefront: string;\n };\n 'search-app-name': {\n query: string;\n storefront: string;\n };\n flag: {\n flag: string;\n value: string | string[];\n };\n}\n\nexport type BasicLibraryInfo = {\n name: string;\n capsuleImage: string;\n appID: number;\n storefront: string;\n};\n\nexport interface CatalogSection {\n name: string;\n description: string;\n listings: BasicLibraryInfo[];\n}\n\nexport interface CatalogCarouselItem {\n name: string;\n description: string;\n carouselImage: string;\n fullBannerImage?: string;\n appID?: number;\n storefront?: string;\n capsuleImage?: string;\n}\n\nexport interface CatalogWithCarousel {\n sections: Record<string, CatalogSection>;\n carousel?: Record<string, CatalogCarouselItem> | CatalogCarouselItem[];\n}\n\nexport type CatalogResponse =\n | Record<string, CatalogSection>\n | CatalogWithCarousel;\n\n/**\n * UMU ID format: 'steam:${number}' or 'umu:${string | number}'\n * - steam:${number} → maps to umu-${number} for Steam games\n * - umu:${string | number} → maps to umu-${string | number} for non-Steam games\n */\nexport type UmuId = `steam:${number}` | `umu:${string | number}`;\n\nexport type SetupEventResponse = Omit<\n LibraryInfo,\n | 'capsuleImage'\n | 'coverImage'\n | 'name'\n | 'appID'\n | 'storefront'\n | 'addonsource'\n | 'titleImage'\n> & {\n redistributables?: {\n name: string;\n path: string;\n }[];\n /**\n * UMU Proton integration configuration\n */\n umu?: {\n /**\n * UMU ID for the game. Format: 'steam:${number}' or 'umu:${string | number}'\n * - steam:${number} → maps to umu-${number} for Steam games\n * - umu:${string | number} → maps to umu-${string | number} for non-Steam games\n */\n umuId: UmuId;\n /**\n * Optional DLL overrides. Can be WINEDLLOVERRIDES-style (e.g. \"dinput8=n,b\") or bare DLL names.\n * Bare names get \"=n,b\" inferred; entries that already include \"=...\" are used as-is.\n */\n dllOverrides?: string[];\n /**\n * Optional PROTONPATH override to use for UMU launches.\n * Omit this unless you absolutely need a specific Proton build/path.\n */\n protonVersion?: string;\n /**\n * Optional store identifier for protonfixes (e.g., 'gog', 'egs', 'none')\n */\n store?: string;\n /**\n * Cached Steam shortcut app ID after adding UMU game to Steam (avoids re-adding on each launch)\n */\n steamShortcutId?: number;\n };\n};\n\nexport interface EventListenerTypes {\n /**\n * This event is emitted when the addon connects to the OGI Addon Server. Addon does not need to resolve anything.\n * @param event\n * @returns\n */\n connect: (event: EventResponse<void>) => void;\n\n /**\n * 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.\n * @param reason\n * @returns\n */\n disconnect: (reason: string) => void;\n /**\n * 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)\n * @param config\n * @returns\n */\n configure: (config: ConfigurationBuilder) => ConfigurationBuilder;\n /**\n * This event is called when the client provides a response to any event. This should be treated as middleware.\n * @param response\n * @returns\n */\n response: (response: any) => void;\n\n /**\n * This event is called when the client requests for the addon to authenticate itself. You don't need to provide any info.\n * @param config\n * @returns\n */\n authenticate: (config: any) => void;\n /**\n * 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)\n * @param query\n * @param event\n * @returns\n */\n search: (\n query: {\n storefront: string;\n appID: number;\n } & (\n | {\n for: 'game' | 'task' | 'all';\n }\n | {\n for: 'update';\n libraryInfo: LibraryInfo;\n }\n ),\n event: EventResponse<SearchResult[]>\n ) => void;\n /**\n * 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)\n * @param data\n * @param event\n * @returns\n */\n setup: (\n data: {\n path: string;\n type: 'direct' | 'torrent' | 'magnet' | 'empty';\n name: string;\n usedRealDebrid: boolean;\n clearOldFilesBeforeUpdate?: boolean;\n multiPartFiles?: {\n name: string;\n downloadURL: string;\n }[];\n appID: number;\n storefront: string;\n manifest?: Record<string, unknown>;\n } & (\n | {\n for: 'game';\n }\n | {\n for: 'update';\n currentLibraryInfo: LibraryInfo;\n }\n ),\n event: EventResponse<SetupEventResponse>\n ) => void;\n\n /**\n * This event is emitted when the client requires for a search to be performed. Input is the search query.\n * @param query\n * @param event\n * @returns\n */\n 'library-search': (\n query: string,\n event: EventResponse<BasicLibraryInfo[]>\n ) => void;\n\n /**\n * 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.\n * @param appID\n * @param event\n * @returns\n */\n 'game-details': (\n details: { appID: number; storefront: string },\n event: EventResponse<StoreData | undefined>\n ) => void;\n\n /**\n * 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)`.\n * @returns\n */\n exit: () => void;\n\n /**\n * 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.\n * @param appID\n * @param info\n * @param event\n * @returns\n */\n 'request-dl': (\n appID: number,\n info: SearchResult,\n event: EventResponse<SearchResult>\n ) => void;\n\n /**\n * This event is emitted when the client requests for a catalog to be fetched. Addon should resolve the event with the catalog.\n * @param event\n * @returns\n */\n catalog: (event: Omit<EventResponse<CatalogResponse>, 'askForInput'>) => void;\n\n /**\n * This event is emitted when the client requests for an addon to check for updates. Addon should resolve the event with the update information.\n * @param data\n * @param event\n * @returns\n */\n 'check-for-updates': (\n data: { appID: number; storefront: string; currentVersion: string },\n event: EventResponse<\n | {\n available: true;\n version: string;\n }\n | {\n available: false;\n }\n >\n ) => void;\n\n /**\n * This event is emitted when the client is going to launch an app. Addon should use this to perform any pre or post launch tasks.\n * @param data {LibraryInfo} The library information for the app to be launched.\n * @param launchType { 'pre' | 'post' } The type of launch task to perform.\n * @param event {EventResponse<void>} The event response from the server.\n */\n 'launch-app': (\n data: { libraryInfo: LibraryInfo; launchType: 'pre' | 'post' },\n event: EventResponse<void>\n ) => void;\n}\n\nexport interface StoreData {\n name: string;\n publishers: string[];\n developers: string[];\n appID: number;\n releaseDate: string;\n capsuleImage: string;\n coverImage: string;\n basicDescription: string;\n description: string;\n headerImage: string;\n latestVersion: string;\n}\nexport interface WebsocketMessageClient {\n event: OGIAddonClientSentEvent;\n id?: string;\n args: any;\n statusError?: string;\n}\nexport interface WebsocketMessageServer {\n event: OGIAddonServerSentEvent;\n id?: string;\n args: any;\n statusError?: string;\n}\n\n/**\n * The configuration for the addon. This is used to identify the addon and provide information about it.\n * Storefronts is an array of names of stores that the addon supports.\n */\nexport interface OGIAddonConfiguration {\n name: string;\n id: string;\n description: string;\n version: string;\n\n author: string;\n repository: string;\n storefronts: string[];\n}\n\n/**\n * 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.\n * @example\n * ```typescript\n * const addon = new OGIAddon({\n * name: 'Test Addon',\n * id: 'test-addon',\n * description: 'A test addon',\n * version: '1.0.0',\n * author: 'OGI Developers',\n * repository: ''\n * });\n * ```\n *\n */\nexport default class OGIAddon {\n public eventEmitter = new events.EventEmitter();\n public addonWSListener: OGIAddonWSListener;\n public addonInfo: OGIAddonConfiguration;\n public config: Configuration = new Configuration({});\n private eventsAvailable: OGIAddonEvent[] = [];\n private registeredConnectEvent: boolean = false;\n private taskHandlers: Map<\n string,\n (\n task: Task,\n data: {\n manifest: Record<string, unknown>;\n downloadPath: string;\n name: string;\n libraryInfo: LibraryInfo;\n }\n ) => Promise<void> | void\n > = new Map();\n\n constructor(addonInfo: OGIAddonConfiguration) {\n this.addonInfo = addonInfo;\n this.addonWSListener = new OGIAddonWSListener(this, this.eventEmitter);\n }\n\n /**\n * Register an event listener for the addon. (See EventListenerTypes)\n * @param event {OGIAddonEvent}\n * @param listener {EventListenerTypes[OGIAddonEvent]}\n */\n public on<T extends OGIAddonEvent>(\n event: T,\n listener: EventListenerTypes[T]\n ) {\n this.eventEmitter.on(event, listener);\n this.eventsAvailable.push(event);\n // wait for the addon to be connected\n if (!this.registeredConnectEvent) {\n this.addonWSListener.eventEmitter.once('connect', () => {\n this.addonWSListener.send('flag', {\n flag: 'events-available',\n value: this.eventsAvailable,\n });\n });\n this.registeredConnectEvent = true;\n }\n }\n\n public emit<T extends OGIAddonEvent>(\n event: T,\n ...args: Parameters<EventListenerTypes[T]>\n ) {\n this.eventEmitter.emit(event, ...args);\n }\n\n /**\n * Notify the client using a notification. Provide the type of notification, the message, and an ID.\n * @param notification {Notification}\n */\n public notify(notification: Notification) {\n this.addonWSListener.send('notification', [notification]);\n }\n\n /**\n * Get the app details for a given appID and storefront.\n * @param appID {number}\n * @param storefront {string}\n * @returns {Promise<StoreData>}\n */\n public async getAppDetails(appID: number, storefront: string) {\n const id = this.addonWSListener.send('get-app-details', {\n appID,\n storefront,\n });\n return await this.addonWSListener.waitForResponseFromServer<\n StoreData | undefined\n >(id);\n }\n\n public async searchGame(query: string, storefront: string) {\n const id = this.addonWSListener.send('search-app-name', {\n query,\n storefront,\n });\n return await this.addonWSListener.waitForResponseFromServer<\n BasicLibraryInfo[]\n >(id);\n }\n\n /**\n * Notify the OGI Addon Server that you are performing a background task. This can be used to help users understand what is happening in the background.\n * @returns {Promise<Task>} A Task instance for managing the background task.\n */\n public async task(): Promise<Task> {\n const id = Math.random().toString(36).substring(7);\n const progress = 0;\n const logs: string[] = [];\n const task = new Task(this.addonWSListener, id, progress, logs);\n this.addonWSListener.send('task-update', {\n id,\n progress,\n logs,\n finished: false,\n failed: undefined,\n });\n return task;\n }\n\n /**\n * Register a task handler for a specific task name. The task name should match the taskName field in SearchResult or ActionOption.\n * @param taskName {string} The name of the task (should match taskName in SearchResult or ActionOption.setTaskName()).\n * @param handler {(task: Task, data: { manifest: Record<string, unknown>; downloadPath: string; name: string; libraryInfo: LibraryInfo }) => Promise<void> | void} The handler function.\n * @example\n * ```typescript\n * addon.onTask('clearCache', async (task) => {\n * task.log('Clearing cache...');\n * task.setProgress(50);\n * await clearCacheFiles();\n * task.setProgress(100);\n * task.complete();\n * });\n * ```\n */\n public onTask(\n taskName: string,\n handler: (\n task: Task,\n data: {\n manifest: Record<string, unknown>;\n downloadPath: string;\n name: string;\n libraryInfo: LibraryInfo;\n }\n ) => Promise<void> | void\n ): void {\n this.taskHandlers.set(taskName, handler);\n }\n\n /**\n * Check if a task handler is registered for the given task name.\n * @param taskName {string} The task name to check.\n * @returns {boolean} True if a handler is registered.\n */\n public hasTaskHandler(taskName: string): boolean {\n return this.taskHandlers.has(taskName);\n }\n\n /**\n * Get a task handler for the given task name.\n * @param taskName {string} The task name.\n * @returns The handler function or undefined if not found.\n */\n public getTaskHandler(taskName: string):\n | ((\n task: Task,\n data: {\n manifest: Record<string, unknown>;\n downloadPath: string;\n name: string;\n libraryInfo?: LibraryInfo;\n }\n ) => Promise<void> | void)\n | undefined {\n return this.taskHandlers.get(taskName);\n }\n\n /**\n * Extract a file using 7-Zip on Windows, unzip on Linux/Mac.\n * @param path {string}\n * @param outputPath {string}\n * @returns {Promise<void>}\n */\n public async extractFile(path: string, outputPath: string) {\n return await extraction(path, outputPath);\n }\n}\n\n/**\n * A unified task API for both server-initiated tasks (via onTask handlers)\n * and addon-initiated background tasks (via addon.task()).\n * Provides chainable methods for logging, progress updates, and completion.\n */\nexport class Task {\n // EventResponse-based mode (for onTask handlers)\n private event: EventResponse<void> | undefined;\n\n // WebSocket-based mode (for addon.task())\n private ws: OGIAddonWSListener | undefined;\n private readonly id: string | undefined;\n private progress: number = 0;\n private logs: string[] = [];\n private finished: boolean = false;\n private failed: string | undefined = undefined;\n\n /**\n * Construct a Task from an EventResponse (for onTask handlers).\n * @param event {EventResponse<void>} The event response to wrap.\n */\n constructor(event: EventResponse<void>);\n\n /**\n * Construct a Task from WebSocket listener (for addon.task()).\n * @param ws {OGIAddonWSListener} The WebSocket listener.\n * @param id {string} The task ID.\n * @param progress {number} Initial progress (0-100).\n * @param logs {string[]} Initial logs array.\n */\n constructor(\n ws: OGIAddonWSListener,\n id: string,\n progress: number,\n logs: string[]\n );\n\n constructor(\n eventOrWs: EventResponse<void> | OGIAddonWSListener,\n id?: string,\n progress?: number,\n logs?: string[]\n ) {\n if (eventOrWs instanceof EventResponse) {\n // EventResponse-based mode\n this.event = eventOrWs;\n this.event.defer();\n } else {\n // WebSocket-based mode\n this.ws = eventOrWs;\n this.id = id!;\n this.progress = progress ?? 0;\n this.logs = logs ?? [];\n }\n }\n\n /**\n * Log a message to the task. Returns this for chaining.\n * @param message {string} The message to log.\n */\n log(message: string): this {\n if (this.event) {\n this.event.log(message);\n } else {\n this.logs.push(message);\n this.update();\n }\n return this;\n }\n\n /**\n * Set the progress of the task (0-100). Returns this for chaining.\n * @param progress {number} The progress value (0-100).\n */\n setProgress(progress: number): this {\n if (this.event) {\n this.event.progress = progress;\n } else {\n this.progress = progress;\n this.update();\n }\n return this;\n }\n\n /**\n * Complete the task successfully.\n */\n complete(): void {\n if (this.event) {\n this.event.complete();\n } else {\n this.finished = true;\n this.update();\n }\n }\n\n /**\n * Fail the task with an error message.\n * @param message {string} The error message.\n */\n fail(message: string): void {\n if (this.event) {\n this.event.fail(message);\n } else {\n this.failed = message;\n this.update();\n }\n }\n\n /**\n * Ask the user for input using a ConfigurationBuilder screen.\n * Only available for EventResponse-based tasks (onTask handlers).\n * The return type is inferred from the ConfigurationBuilder's accumulated option types.\n * @param name {string} The name/title of the input prompt.\n * @param description {string} The description of what input is needed.\n * @param screen {ConfigurationBuilder<U>} The configuration builder for the input form.\n * @returns {Promise<U>} The user's input with types matching the configuration options.\n * @throws {Error} If called on a WebSocket-based task.\n */\n async askForInput<U extends Record<string, string | number | boolean>>(\n name: string,\n description: string,\n screen: ConfigurationBuilder<U>\n ): Promise<U> {\n if (!this.event) {\n throw new Error(\n 'askForInput() is only available for EventResponse-based tasks (onTask handlers)'\n );\n }\n return this.event.askForInput(name, description, screen);\n }\n\n /**\n * Update the task state (for WebSocket-based tasks only).\n * Called automatically when using log(), setProgress(), complete(), or fail().\n */\n private update(): void {\n if (this.ws && this.id !== undefined) {\n this.ws.send('task-update', {\n id: this.id,\n progress: this.progress,\n logs: this.logs,\n finished: this.finished,\n failed: this.failed,\n });\n }\n }\n}\n/**\n * A search tool wrapper over Fuse.js for the OGI Addon. This tool is used to search for items in the library.\n * @example\n * ```typescript\n * const searchTool = new SearchTool<LibraryInfo>([{ name: 'test', appID: 123 }, { name: 'test2', appID: 124 }], ['name']);\n * const results = searchTool.search('test', 10);\n * ```\n */\nexport class SearchTool<T> {\n private fuse: Fuse<T>;\n constructor(\n items: T[],\n keys: string[],\n options: Omit<IFuseOptions<T>, 'keys'> = {\n threshold: 0.3,\n includeScore: true,\n }\n ) {\n this.fuse = new Fuse(items, {\n keys,\n ...options,\n });\n }\n public search(query: string, limit: number = 10): T[] {\n return this.fuse\n .search(query)\n .slice(0, limit)\n .map((result) => result.item);\n }\n public addItems(items: T[]) {\n items.map((item) => this.fuse.add(item));\n }\n}\n/**\n * Library Info is the metadata for a library entry after setting up a game.\n */\nexport const ZodLibraryInfo = z.object({\n name: z.string(),\n version: z.string(),\n cwd: z.string(),\n appID: z.number(),\n launchExecutable: z.string(),\n launchArguments: z.string().optional(),\n launchEnv: z.record(z.string(), z.string()).optional(),\n capsuleImage: z.string(),\n storefront: z.string(),\n addonsource: z.string(),\n coverImage: z.string(),\n titleImage: z.string().optional(),\n /**\n * UMU Proton integration configuration (Linux only)\n */\n umu: z\n .object({\n umuId: z\n .string()\n .regex(\n /^(steam|umu):\\S+$/,\n 'Must be in format steam:{number} or umu:{string | number}'\n ),\n dllOverrides: z.array(z.string()).optional(),\n protonVersion: z.string().optional(),\n store: z.string().optional(),\n winePrefixPath: z.string().optional(),\n steamShortcutId: z.number().optional(),\n })\n .optional(),\n /**\n * Redistributables to install (for backward compatibility)\n */\n redistributables: z\n .array(\n z.object({\n name: z.string(),\n path: z.string(),\n })\n )\n .optional(),\n});\nexport type LibraryInfo = z.infer<typeof ZodLibraryInfo>;\ninterface Notification {\n type: 'warning' | 'error' | 'info' | 'success';\n message: string;\n id: string;\n}\nclass OGIAddonWSListener {\n private socket: WebSocket;\n public eventEmitter: events.EventEmitter;\n public addon: OGIAddon;\n\n constructor(ogiAddon: OGIAddon, eventEmitter: events.EventEmitter) {\n if (\n process.argv[process.argv.length - 1].split('=')[0] !== '--addonSecret'\n ) {\n throw new Error(\n 'No secret provided. This usually happens because the addon was not started by the OGI Addon Server.'\n );\n }\n this.addon = ogiAddon;\n this.eventEmitter = eventEmitter;\n this.socket = new ws('ws://localhost:' + defaultPort);\n this.socket.on('open', () => {\n console.log('Connected to OGI Addon Server');\n console.log('OGI Addon Server Version:', VERSION);\n\n // Authenticate with OGI Addon Server\n this.send('authenticate', {\n ...this.addon.addonInfo,\n secret: process.argv[process.argv.length - 1].split('=')[1],\n ogiVersion: VERSION,\n });\n\n // send a configuration request\n let configBuilder = new ConfigurationBuilder();\n this.eventEmitter.emit('configure', configBuilder);\n this.send('configure', configBuilder.build(false));\n this.addon.config = new Configuration(configBuilder.build(true));\n\n // wait for the config-update to be received then send connect\n const configListener = (event: ws.MessageEvent) => {\n if (event === undefined) return;\n // event can be a Buffer, string, ArrayBuffer, or Buffer[]\n let data: string;\n if (typeof event === 'string') {\n data = event;\n } else if (event instanceof Buffer) {\n data = event.toString();\n } else if (event && typeof (event as any).data === 'string') {\n data = (event as any).data;\n } else if (event && (event as any).data instanceof Buffer) {\n data = (event as any).data.toString();\n } else {\n // fallback for other types\n data = event.toString();\n }\n const message: WebsocketMessageServer = JSON.parse(data);\n if (message.event === 'config-update') {\n console.log('Config update received');\n this.socket.off('message', configListener);\n this.eventEmitter.emit(\n 'connect',\n new EventResponse<void>((screen, name, description) => {\n return this.userInputAsked(\n screen,\n name,\n description,\n this.socket\n );\n })\n );\n }\n };\n this.socket.on('message', configListener);\n });\n\n this.socket.on('error', (error) => {\n if (error.message.includes('Failed to connect')) {\n throw new Error(\n 'OGI Addon Server is not running/is unreachable. Please start the server and try again.'\n );\n }\n console.error('An error occurred:', error);\n });\n\n this.socket.on('close', (code, reason) => {\n if (code === 1008) {\n console.error('Authentication failed:', reason);\n return;\n }\n this.eventEmitter.emit('disconnect', reason);\n console.log('Disconnected from OGI Addon Server');\n console.error(reason.toString());\n this.eventEmitter.emit('exit');\n this.socket.close();\n });\n\n this.registerMessageReceiver();\n }\n\n private async userInputAsked<\n U extends Record<string, string | number | boolean>,\n >(\n configBuilt: ConfigurationBuilder<U>,\n name: string,\n description: string,\n socket: WebSocket\n ): Promise<U> {\n const config = configBuilt.build(false);\n const id = Math.random().toString(36).substring(7);\n if (!socket) {\n throw new Error('Socket is not connected');\n }\n socket.send(\n JSON.stringify({\n event: 'input-asked',\n args: {\n config,\n name,\n description,\n },\n id: id,\n })\n );\n return await this.waitForResponseFromServer<U>(id);\n }\n\n /**\n * Registers the message receiver for the socket. This is used to receive messages from the server and handle them.\n */\n private registerMessageReceiver() {\n this.socket.on('message', async (data: string) => {\n const message: WebsocketMessageServer = JSON.parse(data);\n switch (message.event) {\n case 'config-update':\n const result = this.addon.config.updateConfig(message.args);\n if (!result[0]) {\n this.respondToMessage(\n message.id!!,\n {\n success: false,\n error: result[1],\n },\n undefined\n );\n } else {\n this.respondToMessage(message.id!!, { success: true }, undefined);\n }\n break;\n case 'search':\n await this.handleEventWithResponse<SearchResult[]>(message, (event) =>\n this.eventEmitter.emit('search', message.args, event)\n );\n break;\n case 'setup': {\n let setupEvent = new EventResponse<SetupEventResponse>(\n (screen, name, description) =>\n this.userInputAsked(screen, name, description, this.socket)\n );\n this.eventEmitter.emit('setup', message.args, setupEvent);\n const interval = setInterval(() => {\n if (setupEvent.resolved) {\n clearInterval(interval);\n return;\n }\n this.send('defer-update', {\n logs: setupEvent.logs,\n deferID: message.args.deferID,\n progress: setupEvent.progress,\n failed: setupEvent.failed,\n } as ClientSentEventTypes['defer-update']);\n }, 100);\n const setupResult = await this.waitForEventToRespond(setupEvent);\n this.respondToMessage(message.id!!, setupResult.data, setupEvent);\n break;\n }\n case 'library-search':\n await this.handleEventWithResponse<BasicLibraryInfo[]>(\n message,\n (event) =>\n this.eventEmitter.emit('library-search', message.args, event)\n );\n break;\n case 'game-details':\n await this.handleEventWithResponse<StoreData | undefined>(\n message,\n (event) =>\n this.eventEmitter.emit('game-details', message.args, event),\n {\n requireListener: 'game-details',\n noListenerError: 'No event listener for game-details',\n }\n );\n break;\n case 'check-for-updates':\n await this.handleEventWithResponse<\n { available: true; version: string } | { available: false }\n >(message, (event) =>\n this.eventEmitter.emit('check-for-updates', message.args, event)\n );\n break;\n case 'request-dl':\n let requestDLEvent = new EventResponse<SearchResult>(\n (screen, name, description) =>\n this.userInputAsked(screen, name, description, this.socket)\n );\n if (this.eventEmitter.listenerCount('request-dl') === 0) {\n this.respondToMessage(\n message.id!!,\n {\n error: 'No event listener for request-dl',\n },\n requestDLEvent\n );\n break;\n }\n this.eventEmitter.emit(\n 'request-dl',\n message.args.appID,\n message.args.info,\n requestDLEvent\n );\n const requestDLResult =\n await this.waitForEventToRespond(requestDLEvent);\n if (requestDLEvent.failed) {\n this.respondToMessage(message.id!!, undefined, requestDLEvent);\n break;\n }\n if (\n requestDLEvent.data === undefined ||\n requestDLEvent.data?.downloadType === 'request'\n ) {\n throw new Error(\n 'Request DL event did not return a valid result. Please ensure that the event does not resolve with another `request` download type.'\n );\n }\n this.respondToMessage(\n message.id!!,\n requestDLResult.data,\n requestDLEvent\n );\n break;\n case 'catalog':\n await this.handleEventWithResponseNoInput<CatalogResponse>(\n message,\n (event) => this.eventEmitter.emit('catalog', event)\n );\n break;\n case 'task-run': {\n let taskRunEvent = new EventResponse<void>(\n (screen, name, description) =>\n this.userInputAsked(screen, name, description, this.socket)\n );\n\n // Check for taskName: first from args directly (from SearchResult), then from manifest.__taskName (for ActionOption)\n const taskName =\n message.args.taskName && typeof message.args.taskName === 'string'\n ? message.args.taskName\n : message.args.manifest &&\n typeof message.args.manifest === 'object'\n ? (message.args.manifest as Record<string, unknown>).__taskName\n : undefined;\n\n if (\n taskName &&\n typeof taskName === 'string' &&\n this.addon.hasTaskHandler(taskName)\n ) {\n // Use the registered task handler\n const handler = this.addon.getTaskHandler(taskName)!;\n const task = new Task(taskRunEvent);\n try {\n const interval = setInterval(() => {\n if (taskRunEvent.resolved) {\n clearInterval(interval);\n return;\n }\n this.send('defer-update', {\n logs: taskRunEvent.logs,\n deferID: message.args.deferID,\n progress: taskRunEvent.progress,\n failed: taskRunEvent.failed,\n } as ClientSentEventTypes['defer-update']);\n }, 100);\n const result = handler(task, {\n manifest: message.args.manifest || {},\n downloadPath: message.args.downloadPath || '',\n name: message.args.name || '',\n libraryInfo: message.args.libraryInfo,\n });\n // If handler returns a promise, wait for it\n if (result instanceof Promise) {\n await result;\n }\n\n clearInterval(interval);\n } catch (error) {\n taskRunEvent.fail(\n error instanceof Error ? error.message : String(error)\n );\n }\n } else {\n // No handler found - fail the task\n taskRunEvent.fail(\n taskName\n ? `No task handler registered for task name: ${taskName}`\n : 'No task name provided'\n );\n }\n\n const taskRunResult = await this.waitForEventToRespond(taskRunEvent);\n this.respondToMessage(message.id!!, taskRunResult.data, taskRunEvent);\n break;\n }\n case 'launch-app':\n await this.handleEventWithResponse<void>(message, (event) =>\n this.eventEmitter.emit('launch-app', message.args, event)\n );\n break;\n }\n });\n }\n\n private waitForEventToRespond<T>(\n event: EventResponse<T>\n ): Promise<EventResponse<T>> {\n // check the handlers to see if there even is any\n return new Promise((resolve, reject) => {\n const dataGet = setInterval(() => {\n if (event.resolved) {\n resolve(event);\n clearTimeout(timeout);\n }\n }, 5);\n\n const timeout = setTimeout(() => {\n if (event.deffered) {\n clearInterval(dataGet);\n const interval = setInterval(() => {\n if (event.resolved) {\n clearInterval(interval);\n resolve(event);\n }\n }, 100);\n } else {\n reject('Event did not respond in time');\n }\n }, 5000);\n });\n }\n\n /**\n * Common flow for events that use EventResponse with userInputAsked: create event, emit via callback, wait, respond.\n * If options.requireListener is set and that event has no listeners, responds with options.noListenerError and returns.\n */\n private async handleEventWithResponse<T>(\n message: WebsocketMessageServer,\n emit: (event: EventResponse<T>) => void,\n options?: { requireListener: string; noListenerError: string }\n ): Promise<void> {\n const event = new EventResponse<T>((screen, name, description) =>\n this.userInputAsked(screen, name, description, this.socket)\n );\n if (\n options &&\n this.eventEmitter.listenerCount(options.requireListener) === 0\n ) {\n this.respondToMessage(\n message.id!!,\n { error: options.noListenerError },\n event\n );\n return;\n }\n emit(event);\n const result = await this.waitForEventToRespond(event);\n this.respondToMessage(message.id!!, result.data, event);\n }\n\n /**\n * Same as handleEventWithResponse but for events that don't need userInputAsked (e.g. catalog).\n */\n private async handleEventWithResponseNoInput<T>(\n message: WebsocketMessageServer,\n emit: (event: EventResponse<T>) => void\n ): Promise<void> {\n const event = new EventResponse<T>();\n emit(event);\n const result = await this.waitForEventToRespond(event);\n this.respondToMessage(message.id!!, result.data, event);\n }\n\n public respondToMessage(\n messageID: string,\n response: any,\n originalEvent: EventResponse<any> | undefined\n ) {\n this.socket.send(\n JSON.stringify({\n event: 'response',\n id: messageID,\n args: response,\n statusError: originalEvent ? originalEvent.failed : undefined,\n })\n );\n console.log('dispatched response to ' + messageID);\n }\n\n public waitForResponseFromServer<T>(messageID: string): Promise<T> {\n return new Promise((resolve) => {\n const waiter = (data: string) => {\n const message: WebsocketMessageClient = JSON.parse(data);\n if (message.event !== 'response') {\n this.socket.once('message', waiter);\n return;\n }\n console.log('received response from ' + messageID);\n\n if (message.id === messageID) {\n resolve(message.args);\n } else {\n this.socket.once('message', waiter);\n }\n };\n this.socket.once('message', waiter);\n });\n }\n\n public send(\n event: OGIAddonClientSentEvent,\n args: ClientSentEventTypes[OGIAddonClientSentEvent]\n ): string {\n // generate a random id\n const id = Math.random().toString(36).substring(7);\n this.socket.send(\n JSON.stringify({\n event,\n args,\n id,\n })\n );\n return id;\n }\n\n public close() {\n this.socket.close();\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"main.mjs","names":["pjson.version","z"],"sources":["../package.json","../src/main.ts"],"sourcesContent":["","import { EventResponseSocket, randomMessageId } from '@ogi-sdk/connect';\nimport type {\n AddonClientToServerEventArgs,\n AddonClientToServerEventName,\n AddonClientToServerWebsocketMessage,\n AddonNotificationMessage,\n AddonProtocolEventListenerTypes,\n AddonSDKLifecycleEventListenerTypes,\n AddonServerToClientWebsocketMessage,\n BasicLibraryInfo,\n CatalogResponse,\n LibraryInfo,\n OGIAddonConfiguration,\n OGIAddonSDKEventListener,\n SearchResult,\n SetupResponse,\n StoreData,\n AddonTaskRunEventArgs,\n} from '@ogi-sdk/connect';\nimport events from 'node:events';\nimport { ConfigurationBuilder } from './config/ConfigurationBuilder';\nimport { Configuration, DefiniteConfig } from './config/Configuration';\nimport EventResponse from './EventResponse';\nimport Fuse, { IFuseOptions } from 'fuse.js';\n\nexport { ConfigurationBuilder, Configuration, EventResponse };\nexport { extraction };\nconst defaultPort = 7654;\nimport pjson from '../package.json';\nimport { z } from 'zod';\nimport { extraction } from './extraction';\nexport const VERSION = pjson.version;\n\nexport type {\n AddonClientToServerEventArgs,\n AddonClientToServerEventName,\n AddonClientToServerWebsocketMessage,\n AddonNotificationMessage,\n AddonServerToClientEventName,\n AddonServerToClientWebsocketMessage,\n BasicLibraryInfo,\n CatalogCarouselItem,\n CatalogResponse,\n CatalogSection,\n CatalogWithCarousel,\n ConfigurationFile,\n ConfigurationOptionType,\n ConfigurationOptionWire,\n LibraryInfo,\n OGIAddonConfiguration,\n OGIAddonSDKEventListener,\n SearchResult,\n SetupResponse,\n SetupEventResponse,\n StoreData,\n UmuId,\n SetupCommandData,\n AddonProtocolEventListenerTypes,\n AddonSDKLifecycleEventListenerTypes,\n AddonServerHostEventListeners,\n AddonServerHostEventName,\n AddonServerLifecycleEvent,\n} from '@ogi-sdk/connect';\n\n/** @deprecated Use {@link AddonNotificationMessage}. */\nexport type Notification = AddonNotificationMessage;\n\n/**\n * Addon SDK listener signatures. Protocol commands come from `addonProtocol` in\n * `@ogi-sdk/connect`; lifecycle and builder-specific hooks are merged below.\n */\nexport type EventListenerTypes = AddonSDKLifecycleEventListenerTypes<\n EventResponse<unknown>\n> &\n AddonProtocolEventListenerTypes<\n EventResponse<unknown>,\n 'authenticate' | 'configure' | 'catalog'\n > & {\n authenticate: (config: unknown) => void;\n configure: (config: ConfigurationBuilder) => ConfigurationBuilder;\n catalog: (event: Omit<EventResponse<CatalogResponse>, 'askForInput'>) => void;\n };\n\n/**\n * 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.\n * @example\n * ```typescript\n * const addon = new OGIAddon({\n * name: 'Test Addon',\n * id: 'test-addon',\n * description: 'A test addon',\n * version: '1.0.0',\n * author: 'OGI Developers',\n * repository: ''\n * });\n * ```\n *\n */\nexport default class OGIAddon {\n public eventEmitter = new events.EventEmitter();\n public addonWSListener: OGIAddonWSListener;\n public addonInfo: OGIAddonConfiguration;\n public config: Configuration = new Configuration({});\n private eventsAvailable: OGIAddonSDKEventListener[] = [];\n private registeredConnectEvent: boolean = false;\n private taskHandlers: Map<\n string,\n (\n task: Task,\n data: {\n manifest: Record<string, unknown>;\n downloadPath: string;\n name: string;\n libraryInfo: LibraryInfo;\n }\n ) => Promise<void> | void\n > = new Map();\n\n constructor(addonInfo: OGIAddonConfiguration) {\n this.addonInfo = addonInfo;\n this.addonWSListener = new OGIAddonWSListener(this, this.eventEmitter);\n }\n\n /**\n * Register an event listener for the addon. (See EventListenerTypes)\n * @param event {OGIAddonSDKEventListener}\n * @param listener {EventListenerTypes[OGIAddonSDKEventListener]}\n */\n public on<T extends OGIAddonSDKEventListener>(\n event: T,\n listener: EventListenerTypes[T]\n ) {\n this.eventEmitter.on(event, listener);\n this.eventsAvailable.push(event);\n // wait for the addon to be connected\n if (!this.registeredConnectEvent) {\n this.addonWSListener.eventEmitter.once('connect', () => {\n this.addonWSListener.send('flag', {\n flag: 'events-available',\n value: this.eventsAvailable,\n });\n });\n this.registeredConnectEvent = true;\n }\n }\n\n public emit<T extends OGIAddonSDKEventListener>(\n event: T,\n ...args: Parameters<EventListenerTypes[T]>\n ) {\n this.eventEmitter.emit(event, ...args);\n }\n\n /**\n * Notify the client using a notification. Provide the type of notification, the message, and an ID.\n * @param notification {Notification}\n */\n public notify(notification: AddonNotificationMessage) {\n this.addonWSListener.send('notification', [notification]);\n }\n\n /**\n * Get the app details for a given appID and storefront.\n * @param appID {number}\n * @param storefront {string}\n * @returns {Promise<StoreData>}\n */\n public async getAppDetails(appID: number, storefront: string) {\n return await this.addonWSListener.requestResponse<StoreData | undefined>(\n 'get-app-details',\n {\n appID,\n storefront,\n }\n );\n }\n\n public async searchGame(query: string, storefront: string) {\n return await this.addonWSListener.requestResponse<BasicLibraryInfo[]>(\n 'search-app-name',\n {\n query,\n storefront,\n }\n );\n }\n\n /**\n * Notify the OGI Addon Server that you are performing a background task. This can be used to help users understand what is happening in the background.\n * @returns {Promise<Task>} A Task instance for managing the background task.\n */\n public async task(): Promise<Task> {\n const id = Math.random().toString(36).substring(7);\n const progress = 0;\n const logs: string[] = [];\n const task = new Task(this.addonWSListener, id, progress, logs);\n this.addonWSListener.send('task-update', {\n id,\n progress,\n logs,\n finished: false,\n failed: undefined,\n });\n return task;\n }\n\n /**\n * Register a task handler for a specific task name. The task name should match the taskName field in SearchResult or ActionOption.\n * @param taskName {string} The name of the task (should match taskName in SearchResult or ActionOption.setTaskName()).\n * @param handler {(task: Task, data: { manifest: Record<string, unknown>; downloadPath: string; name: string; libraryInfo: LibraryInfo }) => Promise<void> | void} The handler function.\n * @example\n * ```typescript\n * addon.onTask('clearCache', async (task) => {\n * task.log('Clearing cache...');\n * task.setProgress(50);\n * await clearCacheFiles();\n * task.setProgress(100);\n * task.complete();\n * });\n * ```\n */\n public onTask(\n taskName: string,\n handler: (\n task: Task,\n data: {\n manifest: Record<string, unknown>;\n downloadPath: string;\n name: string;\n libraryInfo: LibraryInfo;\n }\n ) => Promise<void> | void\n ): void {\n this.taskHandlers.set(taskName, handler);\n }\n\n /**\n * Check if a task handler is registered for the given task name.\n * @param taskName {string} The task name to check.\n * @returns {boolean} True if a handler is registered.\n */\n public hasTaskHandler(taskName: string): boolean {\n return this.taskHandlers.has(taskName);\n }\n\n /**\n * Get a task handler for the given task name.\n * @param taskName {string} The task name.\n * @returns The handler function or undefined if not found.\n */\n public getTaskHandler(taskName: string):\n | ((\n task: Task,\n data: {\n manifest: Record<string, unknown>;\n downloadPath: string;\n name: string;\n libraryInfo?: LibraryInfo;\n }\n ) => Promise<void> | void)\n | undefined {\n return this.taskHandlers.get(taskName);\n }\n\n /**\n * Extract a file using 7-Zip on Windows, unzip on Linux/Mac.\n * @param path {string}\n * @param outputPath {string}\n * @returns {Promise<void>}\n */\n public async extractFile(path: string, outputPath: string) {\n return await extraction(path, outputPath);\n }\n}\n\n/**\n * A unified task API for both server-initiated tasks (via onTask handlers)\n * and addon-initiated background tasks (via addon.task()).\n * Provides chainable methods for logging, progress updates, and completion.\n */\nexport class Task {\n // EventResponse-based mode (for onTask handlers)\n private event: EventResponse<void> | undefined;\n\n // WebSocket-based mode (for addon.task())\n private ws: OGIAddonWSListener | undefined;\n private readonly id: string | undefined;\n private progress: number = 0;\n private logs: string[] = [];\n private finished: boolean = false;\n private failed: string | undefined = undefined;\n\n /**\n * Construct a Task from an EventResponse (for onTask handlers).\n * @param event {EventResponse<void>} The event response to wrap.\n */\n constructor(event: EventResponse<void>);\n\n /**\n * Construct a Task from WebSocket listener (for addon.task()).\n * @param ws {OGIAddonWSListener} The WebSocket listener.\n * @param id {string} The task ID.\n * @param progress {number} Initial progress (0-100).\n * @param logs {string[]} Initial logs array.\n */\n constructor(\n ws: OGIAddonWSListener,\n id: string,\n progress: number,\n logs: string[]\n );\n\n constructor(\n eventOrWs: EventResponse<void> | OGIAddonWSListener,\n id?: string,\n progress?: number,\n logs?: string[]\n ) {\n if (eventOrWs instanceof EventResponse) {\n // EventResponse-based mode\n this.event = eventOrWs;\n this.event.defer();\n } else {\n // WebSocket-based mode\n this.ws = eventOrWs;\n this.id = id!;\n this.progress = progress ?? 0;\n this.logs = logs ?? [];\n }\n }\n\n /**\n * Log a message to the task. Returns this for chaining.\n * @param message {string} The message to log.\n */\n log(message: string): this {\n if (this.event) {\n this.event.log(message);\n } else {\n this.logs.push(message);\n this.update();\n }\n return this;\n }\n\n /**\n * Set the progress of the task (0-100). Returns this for chaining.\n * @param progress {number} The progress value (0-100).\n */\n setProgress(progress: number): this {\n if (this.event) {\n this.event.progress = progress;\n } else {\n this.progress = progress;\n this.update();\n }\n return this;\n }\n\n /**\n * Complete the task successfully.\n */\n complete(): void {\n if (this.event) {\n this.event.complete();\n } else {\n this.finished = true;\n this.update();\n }\n }\n\n /**\n * Fail the task with an error message.\n * @param message {string} The error message.\n */\n fail(message: string): void {\n if (this.event) {\n this.event.fail(message);\n } else {\n this.failed = message;\n this.update();\n }\n }\n\n /**\n * Ask the user for input using a ConfigurationBuilder screen.\n * Only available for EventResponse-based tasks (onTask handlers).\n * The return type is inferred from the ConfigurationBuilder's accumulated option types.\n * @param name {string} The name/title of the input prompt.\n * @param description {string} The description of what input is needed.\n * @param screen {ConfigurationBuilder<U>} The configuration builder for the input form.\n * @returns {Promise<U>} The user's input with types matching the configuration options.\n * @throws {Error} If called on a WebSocket-based task.\n */\n async askForInput<U extends Record<string, string | number | boolean>>(\n name: string,\n description: string,\n screen: ConfigurationBuilder<U>\n ): Promise<U> {\n if (!this.event) {\n throw new Error(\n 'askForInput() is only available for EventResponse-based tasks (onTask handlers)'\n );\n }\n return this.event.askForInput(name, description, screen);\n }\n\n /**\n * Update the task state (for WebSocket-based tasks only).\n * Called automatically when using log(), setProgress(), complete(), or fail().\n */\n private update(): void {\n if (this.ws && this.id !== undefined) {\n this.ws.send('task-update', {\n id: this.id,\n progress: this.progress,\n logs: this.logs,\n finished: this.finished,\n failed: this.failed,\n });\n }\n }\n}\n/**\n * A search tool wrapper over Fuse.js for the OGI Addon. This tool is used to search for items in the library.\n * @example\n * ```typescript\n * const searchTool = new SearchTool<LibraryInfo>([{ name: 'test', appID: 123 }, { name: 'test2', appID: 124 }], ['name']);\n * const results = searchTool.search('test', 10);\n * ```\n */\nexport class SearchTool<T> {\n private fuse: Fuse<T>;\n constructor(\n items: T[],\n keys: string[],\n options: Omit<IFuseOptions<T>, 'keys'> = {\n threshold: 0.3,\n includeScore: true,\n }\n ) {\n this.fuse = new Fuse(items, {\n keys,\n ...options,\n });\n }\n public search(query: string, limit: number = 10): T[] {\n return this.fuse\n .search(query)\n .slice(0, limit)\n .map((result) => result.item);\n }\n public addItems(items: T[]) {\n items.map((item) => this.fuse.add(item));\n }\n}\n/**\n * Library Info is the metadata for a library entry after setting up a game.\n */\nexport const ZodLibraryInfo: z.ZodType<LibraryInfo> = z.object({\n name: z.string(),\n version: z.string(),\n cwd: z.string(),\n appID: z.number(),\n launchExecutable: z.string(),\n launchArguments: z.string().optional(),\n launchEnv: z.record(z.string(), z.string()).optional(),\n capsuleImage: z.string(),\n storefront: z.string(),\n addonsource: z.string(),\n coverImage: z.string(),\n titleImage: z.string().optional(),\n /**\n * UMU Proton integration configuration (Linux only)\n */\n umu: z\n .object({\n umuId: z\n .string()\n .regex(\n /^(steam|umu):\\S+$/,\n 'Must be in format steam:{number} or umu:{string | number}'\n ),\n dllOverrides: z.array(z.string()).optional(),\n protonVersion: z.string().optional(),\n store: z.string().optional(),\n winePrefixPath: z.string().optional(),\n steamShortcutId: z.number().optional(),\n })\n .optional(),\n /**\n * Redistributables to install (for backward compatibility)\n */\n redistributables: z\n .array(\n z.object({\n name: z.string(),\n path: z.string(),\n })\n )\n .optional(),\n});\n\nexport type { AddonTaskRunEventArgs as TaskRunMessageArgs } from '@ogi-sdk/connect';\n\nclass OGIAddonWSListener {\n private socket: InstanceType<typeof globalThis.WebSocket>;\n private transport: EventResponseSocket<\n AddonServerToClientWebsocketMessage,\n AddonClientToServerWebsocketMessage\n >;\n public eventEmitter: events.EventEmitter;\n public addon: OGIAddon;\n\n constructor(ogiAddon: OGIAddon, eventEmitter: events.EventEmitter) {\n const secret = process.argv\n .find((arg) => arg.startsWith('--addonSecret='))\n ?.split('=')[1];\n if (!secret) {\n throw new Error(\n 'No secret provided. This usually happens because the addon was not started by the OGI Addon Server.'\n );\n }\n\n // get the port from the arguments\n let port = process.argv\n .find((arg) => arg.startsWith('--addonPort='))\n ?.split('=')[1];\n if (!port) {\n port = defaultPort.toString();\n }\n\n this.addon = ogiAddon;\n this.eventEmitter = eventEmitter;\n const WebSocketConstructor = globalThis.WebSocket;\n if (!WebSocketConstructor) {\n throw new Error('WebSocket is not available in this runtime');\n }\n this.socket = new WebSocketConstructor('ws://localhost:' + port);\n this.transport = new EventResponseSocket(this.socket);\n this.socket.addEventListener('open', () => {\n console.log('Connected to OGI Addon Server');\n console.log('OGI Addon Server Version:', VERSION);\n\n // Authenticate with OGI Addon Server\n this.send('authenticate' as AddonClientToServerEventName, {\n ...this.addon.addonInfo,\n secret,\n ogiVersion: VERSION,\n });\n\n // send a configuration request\n let configBuilder = new ConfigurationBuilder();\n this.eventEmitter.emit('configure', configBuilder);\n this.send('configure', configBuilder.build(false));\n this.addon.config = new Configuration(configBuilder.build(true));\n\n // wait for the config-update to be received then send connect\n const unsubscribeConfigListener = this.transport.on(\n 'config-update',\n () => {\n console.log('Config update received');\n unsubscribeConfigListener();\n this.eventEmitter.emit(\n 'connect',\n new EventResponse<void>((screen, name, description) => {\n return this.userInputAsked(screen, name, description);\n })\n );\n }\n );\n });\n\n this.socket.addEventListener('error', (event) => {\n this.transport.rejectPendingResponses('Websocket error');\n const message =\n event instanceof ErrorEvent\n ? event.message\n : event.type;\n if (message.includes('Failed to connect')) {\n throw new Error(\n 'OGI Addon Server is not running/is unreachable. Please start the server and try again.'\n );\n }\n console.error('An error occurred:', event);\n });\n\n this.socket.addEventListener('close', (event) => {\n this.transport.rejectPendingResponses('Websocket closed');\n if (event.code === 1008) {\n console.error('Authentication failed:', event.reason);\n return;\n }\n this.eventEmitter.emit('disconnect', event.reason);\n console.log('Disconnected from OGI Addon Server');\n console.error(event.reason);\n this.eventEmitter.emit('exit');\n this.socket.close();\n });\n\n this.registerMessageReceiver();\n }\n\n private async userInputAsked<\n U extends Record<string, string | number | boolean>,\n >(\n configBuilt: ConfigurationBuilder<U>,\n name: string,\n description: string\n ): Promise<U> {\n const config = configBuilt.build(false);\n const response = await this.transport.send(\n {\n event: 'input-asked',\n args: {\n config,\n name,\n description,\n },\n } as AddonClientToServerWebsocketMessage,\n { expectResponse: true }\n );\n return response.args as U;\n }\n\n /**\n * Registers the message receiver for the socket. This is used to receive messages from the server and handle them.\n */\n private registerMessageReceiver() {\n const events: AddonServerToClientWebsocketMessage['event'][] = [\n 'config-update',\n 'search',\n 'setup',\n 'library-search',\n 'game-details',\n 'check-for-updates',\n 'request-dl',\n 'catalog',\n 'task-run',\n 'launch-app',\n ];\n\n for (const event of events) {\n this.transport.on(event, async (message) => {\n switch (message.event) {\n case 'config-update':\n const result = this.addon.config.updateConfig(\n message.args as DefiniteConfig\n );\n if (!result[0]) {\n this.respondToMessage(\n message.id!!,\n {\n success: false,\n error: result[1],\n },\n undefined\n );\n } else {\n this.respondToMessage(message.id!!, { success: true }, undefined);\n }\n break;\n case 'search':\n await this.handleEventWithResponse<SearchResult[]>(\n message,\n (event) => this.eventEmitter.emit('search', message.args, event)\n );\n break;\n case 'setup': {\n let setupEvent = new EventResponse<SetupResponse>(\n (screen, name, description) =>\n this.userInputAsked(screen, name, description)\n );\n this.eventEmitter.emit('setup', message.args, setupEvent);\n const interval = setInterval(() => {\n if (setupEvent.resolved) {\n clearInterval(interval);\n return;\n }\n this.send('defer-update', {\n logs: setupEvent.logs,\n deferID:\n message.args as AddonClientToServerEventArgs['defer-update']['deferID'],\n progress: setupEvent.progress,\n failed: setupEvent.failed,\n } as AddonClientToServerEventArgs['defer-update']);\n }, 100);\n const setupResult = await this.waitForEventToRespond(setupEvent);\n this.respondToMessage(message.id!!, setupResult.data, setupEvent);\n break;\n }\n case 'library-search':\n await this.handleEventWithResponse<BasicLibraryInfo[]>(\n message,\n (event) =>\n this.eventEmitter.emit('library-search', message.args, event)\n );\n break;\n case 'game-details':\n await this.handleEventWithResponse<StoreData | undefined>(\n message,\n (event) =>\n this.eventEmitter.emit('game-details', message.args, event),\n {\n requireListener: 'game-details',\n noListenerError: 'No event listener for game-details',\n }\n );\n break;\n case 'check-for-updates':\n await this.handleEventWithResponse<\n { available: true; version: string } | { available: false }\n >(message, (event) =>\n this.eventEmitter.emit('check-for-updates', message.args, event)\n );\n break;\n case 'request-dl':\n let requestDLEvent = new EventResponse<SearchResult>(\n (screen, name, description) =>\n this.userInputAsked(screen, name, description)\n );\n if (this.eventEmitter.listenerCount('request-dl') === 0) {\n this.respondToMessage(\n message.id!!,\n {\n error: 'No event listener for request-dl',\n },\n requestDLEvent\n );\n break;\n }\n const { appID, info } = message.args as {\n appID: number;\n info: SearchResult;\n };\n this.eventEmitter.emit(\n 'request-dl',\n appID,\n info,\n requestDLEvent as EventResponse<SearchResult>\n );\n const requestDLResult =\n await this.waitForEventToRespond(requestDLEvent);\n if (requestDLEvent.failed) {\n this.respondToMessage(message.id!!, undefined, requestDLEvent);\n break;\n }\n if (\n requestDLEvent.data === undefined ||\n requestDLEvent.data?.downloadType === 'request'\n ) {\n throw new Error(\n 'Request DL event did not return a valid result. Please ensure that the event does not resolve with another `request` download type.'\n );\n }\n this.respondToMessage(\n message.id!!,\n requestDLResult.data,\n requestDLEvent\n );\n break;\n case 'catalog':\n await this.handleEventWithResponseNoInput<CatalogResponse>(\n message,\n (event) => this.eventEmitter.emit('catalog', event)\n );\n break;\n case 'task-run': {\n let taskRunEvent = new EventResponse<void>(\n (screen, name, description) =>\n this.userInputAsked(screen, name, description)\n );\n const args = message.args as AddonTaskRunEventArgs;\n\n // Check for taskName: first from args directly (from SearchResult), then from manifest.__taskName (for ActionOption)\n const taskName =\n args.taskName && typeof args.taskName === 'string'\n ? args.taskName\n : args.manifest && typeof args.manifest === 'object'\n ? args.manifest.__taskName\n : undefined;\n\n if (\n taskName &&\n typeof taskName === 'string' &&\n this.addon.hasTaskHandler(taskName)\n ) {\n // Use the registered task handler\n const handler = this.addon.getTaskHandler(taskName)!;\n const task = new Task(taskRunEvent);\n const interval = setInterval(() => {\n if (taskRunEvent.resolved) {\n clearInterval(interval);\n return;\n }\n this.send('defer-update', {\n logs: taskRunEvent.logs,\n deferID: args.deferID ?? '',\n progress: taskRunEvent.progress,\n failed: taskRunEvent.failed,\n } as AddonClientToServerEventArgs['defer-update']);\n }, 100);\n try {\n const result = handler(task, {\n manifest: args.manifest || {},\n downloadPath: args.downloadPath || '',\n name: args.name || '',\n libraryInfo: args.libraryInfo,\n });\n // If handler returns a promise, wait for it\n if (result instanceof Promise) {\n await result;\n }\n } catch (error) {\n taskRunEvent.fail(\n error instanceof Error ? error.message : String(error)\n );\n } finally {\n clearInterval(interval);\n }\n } else {\n // No handler found - fail the task\n taskRunEvent.fail(\n taskName\n ? `No task handler registered for task name: ${taskName}`\n : 'No task name provided'\n );\n }\n\n const taskRunResult =\n await this.waitForEventToRespond(taskRunEvent);\n this.respondToMessage(\n message.id!!,\n taskRunResult.data,\n taskRunEvent\n );\n break;\n }\n case 'launch-app':\n await this.handleEventWithResponse<void>(message, (event) =>\n this.eventEmitter.emit('launch-app', message.args, event)\n );\n break;\n }\n });\n }\n }\n\n private waitForEventToRespond<T>(\n event: EventResponse<T>\n ): Promise<EventResponse<T>> {\n // check the handlers to see if there even is any\n return new Promise((resolve, reject) => {\n const dataGet = setInterval(() => {\n if (event.resolved) {\n resolve(event);\n clearTimeout(timeout);\n }\n }, 5);\n\n const timeout = setTimeout(() => {\n if (event.deffered) {\n clearInterval(dataGet);\n const interval = setInterval(() => {\n if (event.resolved) {\n clearInterval(interval);\n resolve(event);\n }\n }, 100);\n } else {\n reject('Event did not respond in time');\n }\n }, 5000);\n });\n }\n\n /**\n * Common flow for events that use EventResponse with userInputAsked: create event, emit via callback, wait, respond.\n * If options.requireListener is set and that event has no listeners, responds with options.noListenerError and returns.\n */\n private async handleEventWithResponse<T>(\n message: AddonServerToClientWebsocketMessage,\n emit: (event: EventResponse<T>) => void,\n options?: { requireListener: string; noListenerError: string }\n ): Promise<void> {\n const event = new EventResponse<T>((screen, name, description) =>\n this.userInputAsked(screen, name, description)\n );\n if (\n options &&\n this.eventEmitter.listenerCount(options.requireListener) === 0\n ) {\n this.respondToMessage(\n message.id!!,\n { error: options.noListenerError },\n event\n );\n return;\n }\n emit(event);\n const result = await this.waitForEventToRespond(event);\n this.respondToMessage(message.id!!, result.data, event);\n }\n\n /**\n * Same as handleEventWithResponse but for events that don't need userInputAsked (e.g. catalog).\n */\n private async handleEventWithResponseNoInput<T>(\n message: AddonServerToClientWebsocketMessage,\n emit: (event: EventResponse<T>) => void\n ): Promise<void> {\n const event = new EventResponse<T>();\n emit(event);\n const result = await this.waitForEventToRespond(event);\n this.respondToMessage(message.id!!, result.data, event);\n }\n\n public respondToMessage(\n messageID: string,\n response: any,\n originalEvent: EventResponse<any> | undefined\n ) {\n void this.transport.send(\n {\n event: 'response',\n id: messageID,\n args: response,\n statusError: originalEvent ? originalEvent.failed : undefined,\n } as AddonClientToServerWebsocketMessage,\n { expectResponse: false }\n );\n console.log('dispatched response to ' + messageID);\n }\n\n public async requestResponse<T>(\n event: AddonClientToServerEventName,\n args: AddonClientToServerEventArgs[AddonClientToServerEventName]\n ): Promise<T> {\n const response = await this.transport.send(\n { event, args } as AddonClientToServerWebsocketMessage,\n { expectResponse: true }\n );\n return response.args as T;\n }\n\n public send(\n event: AddonClientToServerEventName,\n args: AddonClientToServerEventArgs[AddonClientToServerEventName]\n ): string {\n const id = randomMessageId();\n void this.transport.send(\n {\n event,\n args,\n id,\n } as AddonClientToServerWebsocketMessage,\n { expectResponse: false }\n );\n return id;\n }\n\n public close() {\n this.socket.close();\n }\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "module": "./build/main.mjs",
4
4
  "type": "module",
5
5
  "main": "./build/main.cjs",
6
- "version": "3.1.0",
6
+ "version": "4.0.0",
7
7
  "exports": {
8
8
  ".": {
9
9
  "import": {
@@ -38,13 +38,13 @@
38
38
  "directory": "packages/ogi-addon"
39
39
  },
40
40
  "dependencies": {
41
+ "@ogi-sdk/connect": "workspace:*",
41
42
  "fuse.js": "^7.1.0",
42
- "ws": "^8.4.0",
43
43
  "zod": "^3.23.8"
44
44
  },
45
45
  "scripts": {
46
46
  "auto-build": "tsc -w",
47
- "build": "tsdown --config tsdown.config.js",
47
+ "build": "tsdown --config tsdown.config.js && bun run --filter '../../packages/addon-server' generate:event-proxy",
48
48
  "typecheck": "tsc --noEmit",
49
49
  "release": "bun run build && npm publish",
50
50
  "format": "prettier --write \"src/**/*.{ts,svelte,json}\"",
@@ -53,7 +53,6 @@
53
53
  "devDependencies": {
54
54
  "@types/minimatch": "^6.0.0",
55
55
  "@types/node": "^20.14.12",
56
- "@types/ws": "^8.4.0",
57
56
  "prettier": "^3.6.0",
58
57
  "tsdown": "^0.20.0-beta.4",
59
58
  "typescript": "^5.0.0"
@@ -1,34 +1 @@
1
- type BaseRequiredFields = {
2
- name: string;
3
- manifest?: Record<string, any>;
4
- /**
5
- * Update-only hint for OGI's setup stage.
6
- * When false, OGI keeps existing files in place instead of moving them to `old_files`
7
- * before running update setup. Defaults to true/undefined behavior.
8
- */
9
- clearOldFilesBeforeUpdate?: boolean;
10
- };
11
-
12
- export type SearchResult = BaseRequiredFields &
13
- (
14
- | {
15
- downloadType: 'torrent' | 'magnet';
16
- filename: string;
17
- downloadURL: string;
18
- }
19
- | {
20
- downloadType: 'direct';
21
- files: {
22
- name: string;
23
- downloadURL: string;
24
- headers?: Record<string, string>;
25
- }[];
26
- }
27
- | {
28
- downloadType: 'task';
29
- taskName: string;
30
- }
31
- | {
32
- downloadType: 'request' | 'empty';
33
- }
34
- );
1
+ export type { SearchResult } from '@ogi-sdk/connect';
@@ -10,10 +10,7 @@ import {
10
10
  isStringOption,
11
11
  isActionOption,
12
12
  } from './ConfigurationBuilder';
13
- import type {
14
- ConfigurationFile,
15
- ConfigurationOptionType,
16
- } from './ConfigurationBuilder';
13
+ import type { ConfigurationFile } from './ConfigurationBuilder';
17
14
 
18
15
  interface DefiniteConfig {
19
16
  [key: string]: string | number | boolean;
@@ -65,9 +62,9 @@ export class Configuration {
65
62
  continue;
66
63
  }
67
64
 
68
- const result = this.storedConfigTemplate[key].validate(
69
- this.definiteConfig[key]
70
- );
65
+ const result = (
66
+ this.storedConfigTemplate[key] as unknown as ConfigurationOption<string>
67
+ ).validate(this.definiteConfig[key]);
71
68
  if (!result[0]) {
72
69
  erroredKeys.set(key, result[1]);
73
70
  }
@@ -145,4 +142,5 @@ export {
145
142
  isActionOption,
146
143
  };
147
144
 
148
- export type { ConfigurationFile, ConfigurationOptionType };
145
+ export type { ConfigurationFile, ConfigurationOptionWire } from '@ogi-sdk/connect';
146
+ export type { DefiniteConfig };