ogi-addon 4.0.1 → 4.1.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.cjs CHANGED
@@ -12,7 +12,7 @@ let fuse_js = require("fuse.js");
12
12
  fuse_js = require_chunk.__toESM(fuse_js);
13
13
 
14
14
  //#region package.json
15
- var version = "4.0.1";
15
+ var version = "4.1.0";
16
16
 
17
17
  //#endregion
18
18
  //#region src/main.ts
@@ -71,7 +71,7 @@ var OGIAddon = class {
71
71
  * @param notification {Notification}
72
72
  */
73
73
  notify(notification) {
74
- this.addonWSListener.send("notification", [notification]);
74
+ this.addonWSListener.send("notification", notification);
75
75
  }
76
76
  /**
77
77
  * Get the app details for a given appID and storefront.
@@ -402,6 +402,7 @@ var OGIAddonWSListener = class {
402
402
  case "setup": {
403
403
  let setupEvent = new require_EventResponse((screen, name, description) => this.userInputAsked(screen, name, description));
404
404
  this.eventEmitter.emit("setup", message.args, setupEvent);
405
+ const deferID = message.id;
405
406
  const interval = setInterval(() => {
406
407
  if (setupEvent.resolved) {
407
408
  clearInterval(interval);
@@ -409,12 +410,13 @@ var OGIAddonWSListener = class {
409
410
  }
410
411
  this.send("defer-update", {
411
412
  logs: setupEvent.logs,
412
- deferID: message.args,
413
+ deferID,
413
414
  progress: setupEvent.progress,
414
415
  failed: setupEvent.failed
415
416
  });
416
417
  }, 100);
417
418
  const setupResult = await this.waitForEventToRespond(setupEvent);
419
+ clearInterval(interval);
418
420
  this.respondToMessage(message.id, setupResult.data, setupEvent);
419
421
  break;
420
422
  }
@@ -1 +1 @@
1
- {"version":3,"file":"main.cjs","names":["pjson.version","events","Configuration","extraction","EventResponse","Fuse","z","EventResponseSocket","ConfigurationBuilder"],"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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"}
1
+ {"version":3,"file":"main.cjs","names":["pjson.version","events","Configuration","extraction","EventResponse","Fuse","z","EventResponseSocket","ConfigurationBuilder"],"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 deferID =\n message.id as AddonClientToServerEventArgs['defer-update']['deferID'];\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 progress: setupEvent.progress,\n failed: setupEvent.failed,\n } as AddonClientToServerEventArgs['defer-update']);\n }, 100);\n const setupResult = await this.waitForEventToRespond(setupEvent);\n clearInterval(interval);\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/build/main.mjs CHANGED
@@ -8,7 +8,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 = "4.0.1";
11
+ var version = "4.1.0";
12
12
 
13
13
  //#endregion
14
14
  //#region src/main.ts
@@ -67,7 +67,7 @@ var OGIAddon = class {
67
67
  * @param notification {Notification}
68
68
  */
69
69
  notify(notification) {
70
- this.addonWSListener.send("notification", [notification]);
70
+ this.addonWSListener.send("notification", notification);
71
71
  }
72
72
  /**
73
73
  * Get the app details for a given appID and storefront.
@@ -398,6 +398,7 @@ var OGIAddonWSListener = class {
398
398
  case "setup": {
399
399
  let setupEvent = new EventResponse((screen, name, description) => this.userInputAsked(screen, name, description));
400
400
  this.eventEmitter.emit("setup", message.args, setupEvent);
401
+ const deferID = message.id;
401
402
  const interval = setInterval(() => {
402
403
  if (setupEvent.resolved) {
403
404
  clearInterval(interval);
@@ -405,12 +406,13 @@ var OGIAddonWSListener = class {
405
406
  }
406
407
  this.send("defer-update", {
407
408
  logs: setupEvent.logs,
408
- deferID: message.args,
409
+ deferID,
409
410
  progress: setupEvent.progress,
410
411
  failed: setupEvent.failed
411
412
  });
412
413
  }, 100);
413
414
  const setupResult = await this.waitForEventToRespond(setupEvent);
415
+ clearInterval(interval);
414
416
  this.respondToMessage(message.id, setupResult.data, setupEvent);
415
417
  break;
416
418
  }
@@ -1 +1 @@
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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"}
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 deferID =\n message.id as AddonClientToServerEventArgs['defer-update']['deferID'];\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 progress: setupEvent.progress,\n failed: setupEvent.failed,\n } as AddonClientToServerEventArgs['defer-update']);\n }, 100);\n const setupResult = await this.waitForEventToRespond(setupEvent);\n clearInterval(interval);\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": "4.0.1",
6
+ "version": "4.1.0",
7
7
  "exports": {
8
8
  ".": {
9
9
  "import": {
package/src/main.ts CHANGED
@@ -156,7 +156,7 @@ export default class OGIAddon {
156
156
  * @param notification {Notification}
157
157
  */
158
158
  public notify(notification: AddonNotificationMessage) {
159
- this.addonWSListener.send('notification', [notification]);
159
+ this.addonWSListener.send('notification', notification);
160
160
  }
161
161
 
162
162
  /**
@@ -672,6 +672,8 @@ class OGIAddonWSListener {
672
672
  this.userInputAsked(screen, name, description)
673
673
  );
674
674
  this.eventEmitter.emit('setup', message.args, setupEvent);
675
+ const deferID =
676
+ message.id as AddonClientToServerEventArgs['defer-update']['deferID'];
675
677
  const interval = setInterval(() => {
676
678
  if (setupEvent.resolved) {
677
679
  clearInterval(interval);
@@ -679,13 +681,13 @@ class OGIAddonWSListener {
679
681
  }
680
682
  this.send('defer-update', {
681
683
  logs: setupEvent.logs,
682
- deferID:
683
- message.args as AddonClientToServerEventArgs['defer-update']['deferID'],
684
+ deferID,
684
685
  progress: setupEvent.progress,
685
686
  failed: setupEvent.failed,
686
687
  } as AddonClientToServerEventArgs['defer-update']);
687
688
  }, 100);
688
689
  const setupResult = await this.waitForEventToRespond(setupEvent);
690
+ clearInterval(interval);
689
691
  this.respondToMessage(message.id!!, setupResult.data, setupEvent);
690
692
  break;
691
693
  }