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 +5 -3
- package/build/main.cjs.map +1 -1
- package/build/main.mjs +5 -3
- package/build/main.mjs.map +1 -1
- package/package.json +1 -1
- package/src/main.ts +5 -3
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
|
|
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",
|
|
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
|
|
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
|
}
|
package/build/main.cjs.map
CHANGED
|
@@ -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":";;;;;;;;;;;;;;;;;;AC2BA,MAAM,cAAc;AAIpB,MAAa,UAAUA;;;;;;;;;;;;;;;;AAmEvB,IAAqB,WAArB,MAA8B;CAC5B,AAAO,eAAe,IAAIC,oBAAO,cAAc;CAC/C,AAAO;CACP,AAAO;CACP,AAAO,SAAwB,IAAIC,2CAAc,EAAE,CAAC;CACpD,AAAQ,kBAA8C,EAAE;CACxD,AAAQ,yBAAkC;CAC1C,AAAQ,+BAWJ,IAAI,KAAK;CAEb,YAAY,WAAkC;AAC5C,OAAK,YAAY;AACjB,OAAK,kBAAkB,IAAI,mBAAmB,MAAM,KAAK,aAAa;;;;;;;CAQxE,AAAO,GACL,OACA,UACA;AACA,OAAK,aAAa,GAAG,OAAO,SAAS;AACrC,OAAK,gBAAgB,KAAK,MAAM;AAEhC,MAAI,CAAC,KAAK,wBAAwB;AAChC,QAAK,gBAAgB,aAAa,KAAK,iBAAiB;AACtD,SAAK,gBAAgB,KAAK,QAAQ;KAChC,MAAM;KACN,OAAO,KAAK;KACb,CAAC;KACF;AACF,QAAK,yBAAyB;;;CAIlC,AAAO,KACL,OACA,GAAG,MACH;AACA,OAAK,aAAa,KAAK,OAAO,GAAG,KAAK;;;;;;CAOxC,AAAO,OAAO,cAAwC;AACpD,OAAK,gBAAgB,KAAK,gBAAgB,aAAa;;;;;;;;CASzD,MAAa,cAAc,OAAe,YAAoB;AAC5D,SAAO,MAAM,KAAK,gBAAgB,gBAChC,mBACA;GACE;GACA;GACD,CACF;;CAGH,MAAa,WAAW,OAAe,YAAoB;AACzD,SAAO,MAAM,KAAK,gBAAgB,gBAChC,mBACA;GACE;GACA;GACD,CACF;;;;;;CAOH,MAAa,OAAsB;EACjC,MAAM,KAAK,KAAK,QAAQ,CAAC,SAAS,GAAG,CAAC,UAAU,EAAE;EAClD,MAAM,WAAW;EACjB,MAAM,OAAiB,EAAE;EACzB,MAAM,OAAO,IAAI,KAAK,KAAK,iBAAiB,IAAI,UAAU,KAAK;AAC/D,OAAK,gBAAgB,KAAK,eAAe;GACvC;GACA;GACA;GACA,UAAU;GACV,QAAQ;GACT,CAAC;AACF,SAAO;;;;;;;;;;;;;;;;;CAkBT,AAAO,OACL,UACA,SASM;AACN,OAAK,aAAa,IAAI,UAAU,QAAQ;;;;;;;CAQ1C,AAAO,eAAe,UAA2B;AAC/C,SAAO,KAAK,aAAa,IAAI,SAAS;;;;;;;CAQxC,AAAO,eAAe,UAUR;AACZ,SAAO,KAAK,aAAa,IAAI,SAAS;;;;;;;;CASxC,MAAa,YAAY,MAAc,YAAoB;AACzD,SAAO,MAAMC,8BAAW,MAAM,WAAW;;;;;;;;AAS7C,IAAa,OAAb,MAAkB;CAEhB,AAAQ;CAGR,AAAQ;CACR,AAAiB;CACjB,AAAQ,WAAmB;CAC3B,AAAQ,OAAiB,EAAE;CAC3B,AAAQ,WAAoB;CAC5B,AAAQ,SAA6B;CAsBrC,YACE,WACA,IACA,UACA,MACA;AACA,MAAI,qBAAqBC,uBAAe;AAEtC,QAAK,QAAQ;AACb,QAAK,MAAM,OAAO;SACb;AAEL,QAAK,KAAK;AACV,QAAK,KAAK;AACV,QAAK,WAAW,YAAY;AAC5B,QAAK,OAAO,QAAQ,EAAE;;;;;;;CAQ1B,IAAI,SAAuB;AACzB,MAAI,KAAK,MACP,MAAK,MAAM,IAAI,QAAQ;OAClB;AACL,QAAK,KAAK,KAAK,QAAQ;AACvB,QAAK,QAAQ;;AAEf,SAAO;;;;;;CAOT,YAAY,UAAwB;AAClC,MAAI,KAAK,MACP,MAAK,MAAM,WAAW;OACjB;AACL,QAAK,WAAW;AAChB,QAAK,QAAQ;;AAEf,SAAO;;;;;CAMT,WAAiB;AACf,MAAI,KAAK,MACP,MAAK,MAAM,UAAU;OAChB;AACL,QAAK,WAAW;AAChB,QAAK,QAAQ;;;;;;;CAQjB,KAAK,SAAuB;AAC1B,MAAI,KAAK,MACP,MAAK,MAAM,KAAK,QAAQ;OACnB;AACL,QAAK,SAAS;AACd,QAAK,QAAQ;;;;;;;;;;;;;CAcjB,MAAM,YACJ,MACA,aACA,QACY;AACZ,MAAI,CAAC,KAAK,MACR,OAAM,IAAI,MACR,kFACD;AAEH,SAAO,KAAK,MAAM,YAAY,MAAM,aAAa,OAAO;;;;;;CAO1D,AAAQ,SAAe;AACrB,MAAI,KAAK,MAAM,KAAK,OAAO,OACzB,MAAK,GAAG,KAAK,eAAe;GAC1B,IAAI,KAAK;GACT,UAAU,KAAK;GACf,MAAM,KAAK;GACX,UAAU,KAAK;GACf,QAAQ,KAAK;GACd,CAAC;;;;;;;;;;;AAYR,IAAa,aAAb,MAA2B;CACzB,AAAQ;CACR,YACE,OACA,MACA,UAAyC;EACvC,WAAW;EACX,cAAc;EACf,EACD;AACA,OAAK,OAAO,IAAIC,gBAAK,OAAO;GAC1B;GACA,GAAG;GACJ,CAAC;;CAEJ,AAAO,OAAO,OAAe,QAAgB,IAAS;AACpD,SAAO,KAAK,KACT,OAAO,MAAM,CACb,MAAM,GAAG,MAAM,CACf,KAAK,WAAW,OAAO,KAAK;;CAEjC,AAAO,SAAS,OAAY;AAC1B,QAAM,KAAK,SAAS,KAAK,KAAK,IAAI,KAAK,CAAC;;;;;;AAM5C,MAAa,iBAAyCC,MAAE,OAAO;CAC7D,MAAMA,MAAE,QAAQ;CAChB,SAASA,MAAE,QAAQ;CACnB,KAAKA,MAAE,QAAQ;CACf,OAAOA,MAAE,QAAQ;CACjB,kBAAkBA,MAAE,QAAQ;CAC5B,iBAAiBA,MAAE,QAAQ,CAAC,UAAU;CACtC,WAAWA,MAAE,OAAOA,MAAE,QAAQ,EAAEA,MAAE,QAAQ,CAAC,CAAC,UAAU;CACtD,cAAcA,MAAE,QAAQ;CACxB,YAAYA,MAAE,QAAQ;CACtB,aAAaA,MAAE,QAAQ;CACvB,YAAYA,MAAE,QAAQ;CACtB,YAAYA,MAAE,QAAQ,CAAC,UAAU;CAIjC,KAAKA,MACF,OAAO;EACN,OAAOA,MACJ,QAAQ,CACR,MACC,qBACA,4DACD;EACH,cAAcA,MAAE,MAAMA,MAAE,QAAQ,CAAC,CAAC,UAAU;EAC5C,eAAeA,MAAE,QAAQ,CAAC,UAAU;EACpC,OAAOA,MAAE,QAAQ,CAAC,UAAU;EAC5B,gBAAgBA,MAAE,QAAQ,CAAC,UAAU;EACrC,iBAAiBA,MAAE,QAAQ,CAAC,UAAU;EACvC,CAAC,CACD,UAAU;CAIb,kBAAkBA,MACf,MACCA,MAAE,OAAO;EACP,MAAMA,MAAE,QAAQ;EAChB,MAAMA,MAAE,QAAQ;EACjB,CAAC,CACH,CACA,UAAU;CACd,CAAC;AAIF,IAAM,qBAAN,MAAyB;CACvB,AAAQ;CACR,AAAQ;CAIR,AAAO;CACP,AAAO;CAEP,YAAY,UAAoB,cAAmC;EACjE,MAAM,SAAS,QAAQ,KACpB,MAAM,QAAQ,IAAI,WAAW,iBAAiB,CAAC,EAC9C,MAAM,IAAI,CAAC;AACf,MAAI,CAAC,OACH,OAAM,IAAI,MACR,sGACD;EAIH,IAAI,OAAO,QAAQ,KAChB,MAAM,QAAQ,IAAI,WAAW,eAAe,CAAC,EAC5C,MAAM,IAAI,CAAC;AACf,MAAI,CAAC,KACH,QAAO,YAAY,UAAU;AAG/B,OAAK,QAAQ;AACb,OAAK,eAAe;EACpB,MAAM,uBAAuB,WAAW;AACxC,MAAI,CAAC,qBACH,OAAM,IAAI,MAAM,6CAA6C;AAE/D,OAAK,SAAS,IAAI,qBAAqB,oBAAoB,KAAK;AAChE,OAAK,YAAY,IAAIC,qCAAoB,KAAK,OAAO;AACrD,OAAK,OAAO,iBAAiB,cAAc;AACzC,WAAQ,IAAI,gCAAgC;AAC5C,WAAQ,IAAI,6BAA6B,QAAQ;AAGjD,QAAK,KAAK,gBAAgD;IACxD,GAAG,KAAK,MAAM;IACd;IACA,YAAY;IACb,CAAC;GAGF,IAAI,gBAAgB,IAAIC,0DAAsB;AAC9C,QAAK,aAAa,KAAK,aAAa,cAAc;AAClD,QAAK,KAAK,aAAa,cAAc,MAAM,MAAM,CAAC;AAClD,QAAK,MAAM,SAAS,IAAIN,2CAAc,cAAc,MAAM,KAAK,CAAC;GAGhE,MAAM,4BAA4B,KAAK,UAAU,GAC/C,uBACM;AACJ,YAAQ,IAAI,yBAAyB;AACrC,+BAA2B;AAC3B,SAAK,aAAa,KAChB,WACA,IAAIE,uBAAqB,QAAQ,MAAM,gBAAgB;AACrD,YAAO,KAAK,eAAe,QAAQ,MAAM,YAAY;MACrD,CACH;KAEJ;IACD;AAEF,OAAK,OAAO,iBAAiB,UAAU,UAAU;AAC/C,QAAK,UAAU,uBAAuB,kBAAkB;AAKxD,QAHE,iBAAiB,aACb,MAAM,UACN,MAAM,MACA,SAAS,oBAAoB,CACvC,OAAM,IAAI,MACR,yFACD;AAEH,WAAQ,MAAM,sBAAsB,MAAM;IAC1C;AAEF,OAAK,OAAO,iBAAiB,UAAU,UAAU;AAC/C,QAAK,UAAU,uBAAuB,mBAAmB;AACzD,OAAI,MAAM,SAAS,MAAM;AACvB,YAAQ,MAAM,0BAA0B,MAAM,OAAO;AACrD;;AAEF,QAAK,aAAa,KAAK,cAAc,MAAM,OAAO;AAClD,WAAQ,IAAI,qCAAqC;AACjD,WAAQ,MAAM,MAAM,OAAO;AAC3B,QAAK,aAAa,KAAK,OAAO;AAC9B,QAAK,OAAO,OAAO;IACnB;AAEF,OAAK,yBAAyB;;CAGhC,MAAc,eAGZ,aACA,MACA,aACY;EACZ,MAAM,SAAS,YAAY,MAAM,MAAM;AAYvC,UAXiB,MAAM,KAAK,UAAU,KACpC;GACE,OAAO;GACP,MAAM;IACJ;IACA;IACA;IACD;GACF,EACD,EAAE,gBAAgB,MAAM,CACzB,EACe;;;;;CAMlB,AAAQ,0BAA0B;AAchC,OAAK,MAAM,SAboD;GAC7D;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACD,CAGC,MAAK,UAAU,GAAG,OAAO,OAAO,YAAY;AAC1C,WAAQ,QAAQ,OAAhB;IACE,KAAK;KACH,MAAM,SAAS,KAAK,MAAM,OAAO,aAC/B,QAAQ,KACT;AACD,SAAI,CAAC,OAAO,GACV,MAAK,iBACH,QAAQ,IACR;MACE,SAAS;MACT,OAAO,OAAO;MACf,EACD,OACD;SAED,MAAK,iBAAiB,QAAQ,IAAM,EAAE,SAAS,MAAM,EAAE,OAAU;AAEnE;IACF,KAAK;AACH,WAAM,KAAK,wBACT,UACC,UAAU,KAAK,aAAa,KAAK,UAAU,QAAQ,MAAM,MAAM,CACjE;AACD;IACF,KAAK,SAAS;KACZ,IAAI,aAAa,IAAIA,uBAClB,QAAQ,MAAM,gBACb,KAAK,eAAe,QAAQ,MAAM,YAAY,CACjD;AACD,UAAK,aAAa,KAAK,SAAS,QAAQ,MAAM,WAAW;KACzD,MAAM,UACJ,QAAQ;KACV,MAAM,WAAW,kBAAkB;AACjC,UAAI,WAAW,UAAU;AACvB,qBAAc,SAAS;AACvB;;AAEF,WAAK,KAAK,gBAAgB;OACxB,MAAM,WAAW;OACjB;OACA,UAAU,WAAW;OACrB,QAAQ,WAAW;OACpB,CAAiD;QACjD,IAAI;KACP,MAAM,cAAc,MAAM,KAAK,sBAAsB,WAAW;AAChE,mBAAc,SAAS;AACvB,UAAK,iBAAiB,QAAQ,IAAM,YAAY,MAAM,WAAW;AACjE;;IAEF,KAAK;AACH,WAAM,KAAK,wBACT,UACC,UACC,KAAK,aAAa,KAAK,kBAAkB,QAAQ,MAAM,MAAM,CAChE;AACD;IACF,KAAK;AACH,WAAM,KAAK,wBACT,UACC,UACC,KAAK,aAAa,KAAK,gBAAgB,QAAQ,MAAM,MAAM,EAC7D;MACE,iBAAiB;MACjB,iBAAiB;MAClB,CACF;AACD;IACF,KAAK;AACH,WAAM,KAAK,wBAET,UAAU,UACV,KAAK,aAAa,KAAK,qBAAqB,QAAQ,MAAM,MAAM,CACjE;AACD;IACF,KAAK;KACH,IAAI,iBAAiB,IAAIA,uBACtB,QAAQ,MAAM,gBACb,KAAK,eAAe,QAAQ,MAAM,YAAY,CACjD;AACD,SAAI,KAAK,aAAa,cAAc,aAAa,KAAK,GAAG;AACvD,WAAK,iBACH,QAAQ,IACR,EACE,OAAO,oCACR,EACD,eACD;AACD;;KAEF,MAAM,EAAE,OAAO,SAAS,QAAQ;AAIhC,UAAK,aAAa,KAChB,cACA,OACA,MACA,eACD;KACD,MAAM,kBACJ,MAAM,KAAK,sBAAsB,eAAe;AAClD,SAAI,eAAe,QAAQ;AACzB,WAAK,iBAAiB,QAAQ,IAAM,QAAW,eAAe;AAC9D;;AAEF,SACE,eAAe,SAAS,UACxB,eAAe,MAAM,iBAAiB,UAEtC,OAAM,IAAI,MACR,sIACD;AAEH,UAAK,iBACH,QAAQ,IACR,gBAAgB,MAChB,eACD;AACD;IACF,KAAK;AACH,WAAM,KAAK,+BACT,UACC,UAAU,KAAK,aAAa,KAAK,WAAW,MAAM,CACpD;AACD;IACF,KAAK,YAAY;KACf,IAAI,eAAe,IAAIA,uBACpB,QAAQ,MAAM,gBACb,KAAK,eAAe,QAAQ,MAAM,YAAY,CACjD;KACD,MAAM,OAAO,QAAQ;KAGrB,MAAM,WACJ,KAAK,YAAY,OAAO,KAAK,aAAa,WACtC,KAAK,WACL,KAAK,YAAY,OAAO,KAAK,aAAa,WACxC,KAAK,SAAS,aACd;AAER,SACE,YACA,OAAO,aAAa,YACpB,KAAK,MAAM,eAAe,SAAS,EACnC;MAEA,MAAM,UAAU,KAAK,MAAM,eAAe,SAAS;MACnD,MAAM,OAAO,IAAI,KAAK,aAAa;MACnC,MAAM,WAAW,kBAAkB;AACjC,WAAI,aAAa,UAAU;AACzB,sBAAc,SAAS;AACvB;;AAEF,YAAK,KAAK,gBAAgB;QACxB,MAAM,aAAa;QACnB,SAAS,KAAK,WAAW;QACzB,UAAU,aAAa;QACvB,QAAQ,aAAa;QACtB,CAAiD;SACjD,IAAI;AACP,UAAI;OACF,MAAM,SAAS,QAAQ,MAAM;QAC3B,UAAU,KAAK,YAAY,EAAE;QAC7B,cAAc,KAAK,gBAAgB;QACnC,MAAM,KAAK,QAAQ;QACnB,aAAa,KAAK;QACnB,CAAC;AAEF,WAAI,kBAAkB,QACpB,OAAM;eAED,OAAO;AACd,oBAAa,KACX,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM,CACvD;gBACO;AACR,qBAAc,SAAS;;WAIzB,cAAa,KACX,WACI,6CAA6C,aAC7C,wBACL;KAGH,MAAM,gBACJ,MAAM,KAAK,sBAAsB,aAAa;AAChD,UAAK,iBACH,QAAQ,IACR,cAAc,MACd,aACD;AACD;;IAEF,KAAK;AACH,WAAM,KAAK,wBAA8B,UAAU,UACjD,KAAK,aAAa,KAAK,cAAc,QAAQ,MAAM,MAAM,CAC1D;AACD;;IAEJ;;CAIN,AAAQ,sBACN,OAC2B;AAE3B,SAAO,IAAI,SAAS,SAAS,WAAW;GACtC,MAAM,UAAU,kBAAkB;AAChC,QAAI,MAAM,UAAU;AAClB,aAAQ,MAAM;AACd,kBAAa,QAAQ;;MAEtB,EAAE;GAEL,MAAM,UAAU,iBAAiB;AAC/B,QAAI,MAAM,UAAU;AAClB,mBAAc,QAAQ;KACtB,MAAM,WAAW,kBAAkB;AACjC,UAAI,MAAM,UAAU;AAClB,qBAAc,SAAS;AACvB,eAAQ,MAAM;;QAEf,IAAI;UAEP,QAAO,gCAAgC;MAExC,IAAK;IACR;;;;;;CAOJ,MAAc,wBACZ,SACA,MACA,SACe;EACf,MAAM,QAAQ,IAAIA,uBAAkB,QAAQ,MAAM,gBAChD,KAAK,eAAe,QAAQ,MAAM,YAAY,CAC/C;AACD,MACE,WACA,KAAK,aAAa,cAAc,QAAQ,gBAAgB,KAAK,GAC7D;AACA,QAAK,iBACH,QAAQ,IACR,EAAE,OAAO,QAAQ,iBAAiB,EAClC,MACD;AACD;;AAEF,OAAK,MAAM;EACX,MAAM,SAAS,MAAM,KAAK,sBAAsB,MAAM;AACtD,OAAK,iBAAiB,QAAQ,IAAM,OAAO,MAAM,MAAM;;;;;CAMzD,MAAc,+BACZ,SACA,MACe;EACf,MAAM,QAAQ,IAAIA,uBAAkB;AACpC,OAAK,MAAM;EACX,MAAM,SAAS,MAAM,KAAK,sBAAsB,MAAM;AACtD,OAAK,iBAAiB,QAAQ,IAAM,OAAO,MAAM,MAAM;;CAGzD,AAAO,iBACL,WACA,UACA,eACA;AACA,EAAK,KAAK,UAAU,KAClB;GACE,OAAO;GACP,IAAI;GACJ,MAAM;GACN,aAAa,gBAAgB,cAAc,SAAS;GACrD,EACD,EAAE,gBAAgB,OAAO,CAC1B;AACD,UAAQ,IAAI,4BAA4B,UAAU;;CAGpD,MAAa,gBACX,OACA,MACY;AAKZ,UAJiB,MAAM,KAAK,UAAU,KACpC;GAAE;GAAO;GAAM,EACf,EAAE,gBAAgB,MAAM,CACzB,EACe;;CAGlB,AAAO,KACL,OACA,MACQ;EACR,MAAM,4CAAsB;AAC5B,EAAK,KAAK,UAAU,KAClB;GACE;GACA;GACA;GACD,EACD,EAAE,gBAAgB,OAAO,CAC1B;AACD,SAAO;;CAGT,AAAO,QAAQ;AACb,OAAK,OAAO,OAAO"}
|
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
|
|
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",
|
|
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
|
|
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
|
}
|
package/build/main.mjs.map
CHANGED
|
@@ -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":";;;;;;;;;;;;;;AC2BA,MAAM,cAAc;AAIpB,MAAa,UAAUA;;;;;;;;;;;;;;;;AAmEvB,IAAqB,WAArB,MAA8B;CAC5B,AAAO,eAAe,IAAI,OAAO,cAAc;CAC/C,AAAO;CACP,AAAO;CACP,AAAO,SAAwB,IAAI,cAAc,EAAE,CAAC;CACpD,AAAQ,kBAA8C,EAAE;CACxD,AAAQ,yBAAkC;CAC1C,AAAQ,+BAWJ,IAAI,KAAK;CAEb,YAAY,WAAkC;AAC5C,OAAK,YAAY;AACjB,OAAK,kBAAkB,IAAI,mBAAmB,MAAM,KAAK,aAAa;;;;;;;CAQxE,AAAO,GACL,OACA,UACA;AACA,OAAK,aAAa,GAAG,OAAO,SAAS;AACrC,OAAK,gBAAgB,KAAK,MAAM;AAEhC,MAAI,CAAC,KAAK,wBAAwB;AAChC,QAAK,gBAAgB,aAAa,KAAK,iBAAiB;AACtD,SAAK,gBAAgB,KAAK,QAAQ;KAChC,MAAM;KACN,OAAO,KAAK;KACb,CAAC;KACF;AACF,QAAK,yBAAyB;;;CAIlC,AAAO,KACL,OACA,GAAG,MACH;AACA,OAAK,aAAa,KAAK,OAAO,GAAG,KAAK;;;;;;CAOxC,AAAO,OAAO,cAAwC;AACpD,OAAK,gBAAgB,KAAK,gBAAgB,aAAa;;;;;;;;CASzD,MAAa,cAAc,OAAe,YAAoB;AAC5D,SAAO,MAAM,KAAK,gBAAgB,gBAChC,mBACA;GACE;GACA;GACD,CACF;;CAGH,MAAa,WAAW,OAAe,YAAoB;AACzD,SAAO,MAAM,KAAK,gBAAgB,gBAChC,mBACA;GACE;GACA;GACD,CACF;;;;;;CAOH,MAAa,OAAsB;EACjC,MAAM,KAAK,KAAK,QAAQ,CAAC,SAAS,GAAG,CAAC,UAAU,EAAE;EAClD,MAAM,WAAW;EACjB,MAAM,OAAiB,EAAE;EACzB,MAAM,OAAO,IAAI,KAAK,KAAK,iBAAiB,IAAI,UAAU,KAAK;AAC/D,OAAK,gBAAgB,KAAK,eAAe;GACvC;GACA;GACA;GACA,UAAU;GACV,QAAQ;GACT,CAAC;AACF,SAAO;;;;;;;;;;;;;;;;;CAkBT,AAAO,OACL,UACA,SASM;AACN,OAAK,aAAa,IAAI,UAAU,QAAQ;;;;;;;CAQ1C,AAAO,eAAe,UAA2B;AAC/C,SAAO,KAAK,aAAa,IAAI,SAAS;;;;;;;CAQxC,AAAO,eAAe,UAUR;AACZ,SAAO,KAAK,aAAa,IAAI,SAAS;;;;;;;;CASxC,MAAa,YAAY,MAAc,YAAoB;AACzD,SAAO,MAAM,WAAW,MAAM,WAAW;;;;;;;;AAS7C,IAAa,OAAb,MAAkB;CAEhB,AAAQ;CAGR,AAAQ;CACR,AAAiB;CACjB,AAAQ,WAAmB;CAC3B,AAAQ,OAAiB,EAAE;CAC3B,AAAQ,WAAoB;CAC5B,AAAQ,SAA6B;CAsBrC,YACE,WACA,IACA,UACA,MACA;AACA,MAAI,qBAAqB,eAAe;AAEtC,QAAK,QAAQ;AACb,QAAK,MAAM,OAAO;SACb;AAEL,QAAK,KAAK;AACV,QAAK,KAAK;AACV,QAAK,WAAW,YAAY;AAC5B,QAAK,OAAO,QAAQ,EAAE;;;;;;;CAQ1B,IAAI,SAAuB;AACzB,MAAI,KAAK,MACP,MAAK,MAAM,IAAI,QAAQ;OAClB;AACL,QAAK,KAAK,KAAK,QAAQ;AACvB,QAAK,QAAQ;;AAEf,SAAO;;;;;;CAOT,YAAY,UAAwB;AAClC,MAAI,KAAK,MACP,MAAK,MAAM,WAAW;OACjB;AACL,QAAK,WAAW;AAChB,QAAK,QAAQ;;AAEf,SAAO;;;;;CAMT,WAAiB;AACf,MAAI,KAAK,MACP,MAAK,MAAM,UAAU;OAChB;AACL,QAAK,WAAW;AAChB,QAAK,QAAQ;;;;;;;CAQjB,KAAK,SAAuB;AAC1B,MAAI,KAAK,MACP,MAAK,MAAM,KAAK,QAAQ;OACnB;AACL,QAAK,SAAS;AACd,QAAK,QAAQ;;;;;;;;;;;;;CAcjB,MAAM,YACJ,MACA,aACA,QACY;AACZ,MAAI,CAAC,KAAK,MACR,OAAM,IAAI,MACR,kFACD;AAEH,SAAO,KAAK,MAAM,YAAY,MAAM,aAAa,OAAO;;;;;;CAO1D,AAAQ,SAAe;AACrB,MAAI,KAAK,MAAM,KAAK,OAAO,OACzB,MAAK,GAAG,KAAK,eAAe;GAC1B,IAAI,KAAK;GACT,UAAU,KAAK;GACf,MAAM,KAAK;GACX,UAAU,KAAK;GACf,QAAQ,KAAK;GACd,CAAC;;;;;;;;;;;AAYR,IAAa,aAAb,MAA2B;CACzB,AAAQ;CACR,YACE,OACA,MACA,UAAyC;EACvC,WAAW;EACX,cAAc;EACf,EACD;AACA,OAAK,OAAO,IAAI,KAAK,OAAO;GAC1B;GACA,GAAG;GACJ,CAAC;;CAEJ,AAAO,OAAO,OAAe,QAAgB,IAAS;AACpD,SAAO,KAAK,KACT,OAAO,MAAM,CACb,MAAM,GAAG,MAAM,CACf,KAAK,WAAW,OAAO,KAAK;;CAEjC,AAAO,SAAS,OAAY;AAC1B,QAAM,KAAK,SAAS,KAAK,KAAK,IAAI,KAAK,CAAC;;;;;;AAM5C,MAAa,iBAAyCC,IAAE,OAAO;CAC7D,MAAMA,IAAE,QAAQ;CAChB,SAASA,IAAE,QAAQ;CACnB,KAAKA,IAAE,QAAQ;CACf,OAAOA,IAAE,QAAQ;CACjB,kBAAkBA,IAAE,QAAQ;CAC5B,iBAAiBA,IAAE,QAAQ,CAAC,UAAU;CACtC,WAAWA,IAAE,OAAOA,IAAE,QAAQ,EAAEA,IAAE,QAAQ,CAAC,CAAC,UAAU;CACtD,cAAcA,IAAE,QAAQ;CACxB,YAAYA,IAAE,QAAQ;CACtB,aAAaA,IAAE,QAAQ;CACvB,YAAYA,IAAE,QAAQ;CACtB,YAAYA,IAAE,QAAQ,CAAC,UAAU;CAIjC,KAAKA,IACF,OAAO;EACN,OAAOA,IACJ,QAAQ,CACR,MACC,qBACA,4DACD;EACH,cAAcA,IAAE,MAAMA,IAAE,QAAQ,CAAC,CAAC,UAAU;EAC5C,eAAeA,IAAE,QAAQ,CAAC,UAAU;EACpC,OAAOA,IAAE,QAAQ,CAAC,UAAU;EAC5B,gBAAgBA,IAAE,QAAQ,CAAC,UAAU;EACrC,iBAAiBA,IAAE,QAAQ,CAAC,UAAU;EACvC,CAAC,CACD,UAAU;CAIb,kBAAkBA,IACf,MACCA,IAAE,OAAO;EACP,MAAMA,IAAE,QAAQ;EAChB,MAAMA,IAAE,QAAQ;EACjB,CAAC,CACH,CACA,UAAU;CACd,CAAC;AAIF,IAAM,qBAAN,MAAyB;CACvB,AAAQ;CACR,AAAQ;CAIR,AAAO;CACP,AAAO;CAEP,YAAY,UAAoB,cAAmC;EACjE,MAAM,SAAS,QAAQ,KACpB,MAAM,QAAQ,IAAI,WAAW,iBAAiB,CAAC,EAC9C,MAAM,IAAI,CAAC;AACf,MAAI,CAAC,OACH,OAAM,IAAI,MACR,sGACD;EAIH,IAAI,OAAO,QAAQ,KAChB,MAAM,QAAQ,IAAI,WAAW,eAAe,CAAC,EAC5C,MAAM,IAAI,CAAC;AACf,MAAI,CAAC,KACH,QAAO,YAAY,UAAU;AAG/B,OAAK,QAAQ;AACb,OAAK,eAAe;EACpB,MAAM,uBAAuB,WAAW;AACxC,MAAI,CAAC,qBACH,OAAM,IAAI,MAAM,6CAA6C;AAE/D,OAAK,SAAS,IAAI,qBAAqB,oBAAoB,KAAK;AAChE,OAAK,YAAY,IAAI,oBAAoB,KAAK,OAAO;AACrD,OAAK,OAAO,iBAAiB,cAAc;AACzC,WAAQ,IAAI,gCAAgC;AAC5C,WAAQ,IAAI,6BAA6B,QAAQ;AAGjD,QAAK,KAAK,gBAAgD;IACxD,GAAG,KAAK,MAAM;IACd;IACA,YAAY;IACb,CAAC;GAGF,IAAI,gBAAgB,IAAI,sBAAsB;AAC9C,QAAK,aAAa,KAAK,aAAa,cAAc;AAClD,QAAK,KAAK,aAAa,cAAc,MAAM,MAAM,CAAC;AAClD,QAAK,MAAM,SAAS,IAAI,cAAc,cAAc,MAAM,KAAK,CAAC;GAGhE,MAAM,4BAA4B,KAAK,UAAU,GAC/C,uBACM;AACJ,YAAQ,IAAI,yBAAyB;AACrC,+BAA2B;AAC3B,SAAK,aAAa,KAChB,WACA,IAAI,eAAqB,QAAQ,MAAM,gBAAgB;AACrD,YAAO,KAAK,eAAe,QAAQ,MAAM,YAAY;MACrD,CACH;KAEJ;IACD;AAEF,OAAK,OAAO,iBAAiB,UAAU,UAAU;AAC/C,QAAK,UAAU,uBAAuB,kBAAkB;AAKxD,QAHE,iBAAiB,aACb,MAAM,UACN,MAAM,MACA,SAAS,oBAAoB,CACvC,OAAM,IAAI,MACR,yFACD;AAEH,WAAQ,MAAM,sBAAsB,MAAM;IAC1C;AAEF,OAAK,OAAO,iBAAiB,UAAU,UAAU;AAC/C,QAAK,UAAU,uBAAuB,mBAAmB;AACzD,OAAI,MAAM,SAAS,MAAM;AACvB,YAAQ,MAAM,0BAA0B,MAAM,OAAO;AACrD;;AAEF,QAAK,aAAa,KAAK,cAAc,MAAM,OAAO;AAClD,WAAQ,IAAI,qCAAqC;AACjD,WAAQ,MAAM,MAAM,OAAO;AAC3B,QAAK,aAAa,KAAK,OAAO;AAC9B,QAAK,OAAO,OAAO;IACnB;AAEF,OAAK,yBAAyB;;CAGhC,MAAc,eAGZ,aACA,MACA,aACY;EACZ,MAAM,SAAS,YAAY,MAAM,MAAM;AAYvC,UAXiB,MAAM,KAAK,UAAU,KACpC;GACE,OAAO;GACP,MAAM;IACJ;IACA;IACA;IACD;GACF,EACD,EAAE,gBAAgB,MAAM,CACzB,EACe;;;;;CAMlB,AAAQ,0BAA0B;AAchC,OAAK,MAAM,SAboD;GAC7D;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACD,CAGC,MAAK,UAAU,GAAG,OAAO,OAAO,YAAY;AAC1C,WAAQ,QAAQ,OAAhB;IACE,KAAK;KACH,MAAM,SAAS,KAAK,MAAM,OAAO,aAC/B,QAAQ,KACT;AACD,SAAI,CAAC,OAAO,GACV,MAAK,iBACH,QAAQ,IACR;MACE,SAAS;MACT,OAAO,OAAO;MACf,EACD,OACD;SAED,MAAK,iBAAiB,QAAQ,IAAM,EAAE,SAAS,MAAM,EAAE,OAAU;AAEnE;IACF,KAAK;AACH,WAAM,KAAK,wBACT,UACC,UAAU,KAAK,aAAa,KAAK,UAAU,QAAQ,MAAM,MAAM,CACjE;AACD;IACF,KAAK,SAAS;KACZ,IAAI,aAAa,IAAI,eAClB,QAAQ,MAAM,gBACb,KAAK,eAAe,QAAQ,MAAM,YAAY,CACjD;AACD,UAAK,aAAa,KAAK,SAAS,QAAQ,MAAM,WAAW;KACzD,MAAM,UACJ,QAAQ;KACV,MAAM,WAAW,kBAAkB;AACjC,UAAI,WAAW,UAAU;AACvB,qBAAc,SAAS;AACvB;;AAEF,WAAK,KAAK,gBAAgB;OACxB,MAAM,WAAW;OACjB;OACA,UAAU,WAAW;OACrB,QAAQ,WAAW;OACpB,CAAiD;QACjD,IAAI;KACP,MAAM,cAAc,MAAM,KAAK,sBAAsB,WAAW;AAChE,mBAAc,SAAS;AACvB,UAAK,iBAAiB,QAAQ,IAAM,YAAY,MAAM,WAAW;AACjE;;IAEF,KAAK;AACH,WAAM,KAAK,wBACT,UACC,UACC,KAAK,aAAa,KAAK,kBAAkB,QAAQ,MAAM,MAAM,CAChE;AACD;IACF,KAAK;AACH,WAAM,KAAK,wBACT,UACC,UACC,KAAK,aAAa,KAAK,gBAAgB,QAAQ,MAAM,MAAM,EAC7D;MACE,iBAAiB;MACjB,iBAAiB;MAClB,CACF;AACD;IACF,KAAK;AACH,WAAM,KAAK,wBAET,UAAU,UACV,KAAK,aAAa,KAAK,qBAAqB,QAAQ,MAAM,MAAM,CACjE;AACD;IACF,KAAK;KACH,IAAI,iBAAiB,IAAI,eACtB,QAAQ,MAAM,gBACb,KAAK,eAAe,QAAQ,MAAM,YAAY,CACjD;AACD,SAAI,KAAK,aAAa,cAAc,aAAa,KAAK,GAAG;AACvD,WAAK,iBACH,QAAQ,IACR,EACE,OAAO,oCACR,EACD,eACD;AACD;;KAEF,MAAM,EAAE,OAAO,SAAS,QAAQ;AAIhC,UAAK,aAAa,KAChB,cACA,OACA,MACA,eACD;KACD,MAAM,kBACJ,MAAM,KAAK,sBAAsB,eAAe;AAClD,SAAI,eAAe,QAAQ;AACzB,WAAK,iBAAiB,QAAQ,IAAM,QAAW,eAAe;AAC9D;;AAEF,SACE,eAAe,SAAS,UACxB,eAAe,MAAM,iBAAiB,UAEtC,OAAM,IAAI,MACR,sIACD;AAEH,UAAK,iBACH,QAAQ,IACR,gBAAgB,MAChB,eACD;AACD;IACF,KAAK;AACH,WAAM,KAAK,+BACT,UACC,UAAU,KAAK,aAAa,KAAK,WAAW,MAAM,CACpD;AACD;IACF,KAAK,YAAY;KACf,IAAI,eAAe,IAAI,eACpB,QAAQ,MAAM,gBACb,KAAK,eAAe,QAAQ,MAAM,YAAY,CACjD;KACD,MAAM,OAAO,QAAQ;KAGrB,MAAM,WACJ,KAAK,YAAY,OAAO,KAAK,aAAa,WACtC,KAAK,WACL,KAAK,YAAY,OAAO,KAAK,aAAa,WACxC,KAAK,SAAS,aACd;AAER,SACE,YACA,OAAO,aAAa,YACpB,KAAK,MAAM,eAAe,SAAS,EACnC;MAEA,MAAM,UAAU,KAAK,MAAM,eAAe,SAAS;MACnD,MAAM,OAAO,IAAI,KAAK,aAAa;MACnC,MAAM,WAAW,kBAAkB;AACjC,WAAI,aAAa,UAAU;AACzB,sBAAc,SAAS;AACvB;;AAEF,YAAK,KAAK,gBAAgB;QACxB,MAAM,aAAa;QACnB,SAAS,KAAK,WAAW;QACzB,UAAU,aAAa;QACvB,QAAQ,aAAa;QACtB,CAAiD;SACjD,IAAI;AACP,UAAI;OACF,MAAM,SAAS,QAAQ,MAAM;QAC3B,UAAU,KAAK,YAAY,EAAE;QAC7B,cAAc,KAAK,gBAAgB;QACnC,MAAM,KAAK,QAAQ;QACnB,aAAa,KAAK;QACnB,CAAC;AAEF,WAAI,kBAAkB,QACpB,OAAM;eAED,OAAO;AACd,oBAAa,KACX,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM,CACvD;gBACO;AACR,qBAAc,SAAS;;WAIzB,cAAa,KACX,WACI,6CAA6C,aAC7C,wBACL;KAGH,MAAM,gBACJ,MAAM,KAAK,sBAAsB,aAAa;AAChD,UAAK,iBACH,QAAQ,IACR,cAAc,MACd,aACD;AACD;;IAEF,KAAK;AACH,WAAM,KAAK,wBAA8B,UAAU,UACjD,KAAK,aAAa,KAAK,cAAc,QAAQ,MAAM,MAAM,CAC1D;AACD;;IAEJ;;CAIN,AAAQ,sBACN,OAC2B;AAE3B,SAAO,IAAI,SAAS,SAAS,WAAW;GACtC,MAAM,UAAU,kBAAkB;AAChC,QAAI,MAAM,UAAU;AAClB,aAAQ,MAAM;AACd,kBAAa,QAAQ;;MAEtB,EAAE;GAEL,MAAM,UAAU,iBAAiB;AAC/B,QAAI,MAAM,UAAU;AAClB,mBAAc,QAAQ;KACtB,MAAM,WAAW,kBAAkB;AACjC,UAAI,MAAM,UAAU;AAClB,qBAAc,SAAS;AACvB,eAAQ,MAAM;;QAEf,IAAI;UAEP,QAAO,gCAAgC;MAExC,IAAK;IACR;;;;;;CAOJ,MAAc,wBACZ,SACA,MACA,SACe;EACf,MAAM,QAAQ,IAAI,eAAkB,QAAQ,MAAM,gBAChD,KAAK,eAAe,QAAQ,MAAM,YAAY,CAC/C;AACD,MACE,WACA,KAAK,aAAa,cAAc,QAAQ,gBAAgB,KAAK,GAC7D;AACA,QAAK,iBACH,QAAQ,IACR,EAAE,OAAO,QAAQ,iBAAiB,EAClC,MACD;AACD;;AAEF,OAAK,MAAM;EACX,MAAM,SAAS,MAAM,KAAK,sBAAsB,MAAM;AACtD,OAAK,iBAAiB,QAAQ,IAAM,OAAO,MAAM,MAAM;;;;;CAMzD,MAAc,+BACZ,SACA,MACe;EACf,MAAM,QAAQ,IAAI,eAAkB;AACpC,OAAK,MAAM;EACX,MAAM,SAAS,MAAM,KAAK,sBAAsB,MAAM;AACtD,OAAK,iBAAiB,QAAQ,IAAM,OAAO,MAAM,MAAM;;CAGzD,AAAO,iBACL,WACA,UACA,eACA;AACA,EAAK,KAAK,UAAU,KAClB;GACE,OAAO;GACP,IAAI;GACJ,MAAM;GACN,aAAa,gBAAgB,cAAc,SAAS;GACrD,EACD,EAAE,gBAAgB,OAAO,CAC1B;AACD,UAAQ,IAAI,4BAA4B,UAAU;;CAGpD,MAAa,gBACX,OACA,MACY;AAKZ,UAJiB,MAAM,KAAK,UAAU,KACpC;GAAE;GAAO;GAAM,EACf,EAAE,gBAAgB,MAAM,CACzB,EACe;;CAGlB,AAAO,KACL,OACA,MACQ;EACR,MAAM,KAAK,iBAAiB;AAC5B,EAAK,KAAK,UAAU,KAClB;GACE;GACA;GACA;GACD,EACD,EAAE,gBAAgB,OAAO,CAC1B;AACD,SAAO;;CAGT,AAAO,QAAQ;AACb,OAAK,OAAO,OAAO"}
|
package/package.json
CHANGED
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',
|
|
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
|
}
|