ogi-addon 3.1.0 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/SearchEngine.d.cts +2 -31
- package/build/SearchEngine.d.mts +2 -31
- package/build/config/Configuration.cjs.map +1 -1
- package/build/config/Configuration.d.cts +5 -4
- package/build/config/Configuration.d.mts +5 -4
- package/build/config/Configuration.mjs.map +1 -1
- package/build/config/ConfigurationBuilder.cjs.map +1 -1
- package/build/config/ConfigurationBuilder.d.cts +13 -15
- package/build/config/ConfigurationBuilder.d.mts +13 -15
- package/build/config/ConfigurationBuilder.mjs.map +1 -1
- package/build/main.cjs +87 -91
- package/build/main.cjs.map +1 -1
- package/build/main.d.cts +25 -407
- package/build/main.d.mts +25 -407
- package/build/main.mjs +87 -90
- package/build/main.mjs.map +1 -1
- package/package.json +3 -4
- package/src/SearchEngine.ts +1 -34
- package/src/config/Configuration.ts +6 -8
- package/src/config/ConfigurationBuilder.ts +32 -23
- package/src/main.ts +354 -654
- package/tsconfig.json +1 -1
package/src/main.ts
CHANGED
|
@@ -1,400 +1,85 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { EventResponseSocket, randomMessageId } from '@ogi-sdk/connect';
|
|
2
|
+
import type {
|
|
3
|
+
AddonClientToServerEventArgs,
|
|
4
|
+
AddonClientToServerEventName,
|
|
5
|
+
AddonClientToServerWebsocketMessage,
|
|
6
|
+
AddonNotificationMessage,
|
|
7
|
+
AddonProtocolEventListenerTypes,
|
|
8
|
+
AddonSDKLifecycleEventListenerTypes,
|
|
9
|
+
AddonServerToClientWebsocketMessage,
|
|
10
|
+
BasicLibraryInfo,
|
|
11
|
+
CatalogResponse,
|
|
12
|
+
LibraryInfo,
|
|
13
|
+
OGIAddonConfiguration,
|
|
14
|
+
OGIAddonSDKEventListener,
|
|
15
|
+
SearchResult,
|
|
16
|
+
SetupResponse,
|
|
17
|
+
StoreData,
|
|
18
|
+
AddonTaskRunEventArgs,
|
|
19
|
+
} from '@ogi-sdk/connect';
|
|
2
20
|
import events from 'node:events';
|
|
3
21
|
import { ConfigurationBuilder } from './config/ConfigurationBuilder';
|
|
4
|
-
import
|
|
5
|
-
import { Configuration } from './config/Configuration';
|
|
22
|
+
import { Configuration, DefiniteConfig } from './config/Configuration';
|
|
6
23
|
import EventResponse from './EventResponse';
|
|
7
|
-
import type { SearchResult } from './SearchEngine';
|
|
8
24
|
import Fuse, { IFuseOptions } from 'fuse.js';
|
|
9
25
|
|
|
10
|
-
/**
|
|
11
|
-
* Exposed events that the programmer can use to listen to and emit events.
|
|
12
|
-
*/
|
|
13
|
-
export type OGIAddonEvent =
|
|
14
|
-
| 'connect'
|
|
15
|
-
| 'disconnect'
|
|
16
|
-
| 'configure'
|
|
17
|
-
| 'authenticate'
|
|
18
|
-
| 'search'
|
|
19
|
-
| 'setup'
|
|
20
|
-
| 'library-search'
|
|
21
|
-
| 'game-details'
|
|
22
|
-
| 'exit'
|
|
23
|
-
| 'check-for-updates'
|
|
24
|
-
| 'request-dl'
|
|
25
|
-
| 'catalog'
|
|
26
|
-
| 'launch-app';
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* The events that the client can send to the server and are handled by the server.
|
|
30
|
-
*/
|
|
31
|
-
export type OGIAddonClientSentEvent =
|
|
32
|
-
| 'response'
|
|
33
|
-
| 'authenticate'
|
|
34
|
-
| 'configure'
|
|
35
|
-
| 'defer-update'
|
|
36
|
-
| 'notification'
|
|
37
|
-
| 'input-asked'
|
|
38
|
-
| 'get-app-details'
|
|
39
|
-
| 'search-app-name'
|
|
40
|
-
| 'flag'
|
|
41
|
-
| 'task-update';
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* The events that the server sends to the client
|
|
45
|
-
* This is the events that the server can send to the client and are handled by the client.
|
|
46
|
-
*/
|
|
47
|
-
export type OGIAddonServerSentEvent =
|
|
48
|
-
| 'authenticate'
|
|
49
|
-
| 'configure'
|
|
50
|
-
| 'config-update'
|
|
51
|
-
| 'launch-app'
|
|
52
|
-
| 'search'
|
|
53
|
-
| 'setup'
|
|
54
|
-
| 'response'
|
|
55
|
-
| 'library-search'
|
|
56
|
-
| 'check-for-updates'
|
|
57
|
-
| 'task-run'
|
|
58
|
-
| 'game-details'
|
|
59
|
-
| 'request-dl'
|
|
60
|
-
| 'catalog';
|
|
61
26
|
export { ConfigurationBuilder, Configuration, EventResponse };
|
|
62
27
|
export { extraction };
|
|
63
|
-
export type { SearchResult };
|
|
64
28
|
const defaultPort = 7654;
|
|
65
29
|
import pjson from '../package.json';
|
|
66
30
|
import { z } from 'zod';
|
|
67
31
|
import { extraction } from './extraction';
|
|
68
32
|
export const VERSION = pjson.version;
|
|
69
33
|
|
|
70
|
-
export
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
'input-asked': ConfigurationBuilder<
|
|
86
|
-
Record<string, string | number | boolean>
|
|
87
|
-
>;
|
|
88
|
-
'task-update': {
|
|
89
|
-
id: string;
|
|
90
|
-
progress: number;
|
|
91
|
-
logs: string[];
|
|
92
|
-
finished: boolean;
|
|
93
|
-
failed: string | undefined;
|
|
94
|
-
};
|
|
95
|
-
'get-app-details': {
|
|
96
|
-
appID: number;
|
|
97
|
-
storefront: string;
|
|
98
|
-
};
|
|
99
|
-
'search-app-name': {
|
|
100
|
-
query: string;
|
|
101
|
-
storefront: string;
|
|
102
|
-
};
|
|
103
|
-
flag: {
|
|
104
|
-
flag: string;
|
|
105
|
-
value: string | string[];
|
|
106
|
-
};
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
export type BasicLibraryInfo = {
|
|
110
|
-
name: string;
|
|
111
|
-
capsuleImage: string;
|
|
112
|
-
appID: number;
|
|
113
|
-
storefront: string;
|
|
114
|
-
};
|
|
115
|
-
|
|
116
|
-
export interface CatalogSection {
|
|
117
|
-
name: string;
|
|
118
|
-
description: string;
|
|
119
|
-
listings: BasicLibraryInfo[];
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
export interface CatalogCarouselItem {
|
|
123
|
-
name: string;
|
|
124
|
-
description: string;
|
|
125
|
-
carouselImage: string;
|
|
126
|
-
fullBannerImage?: string;
|
|
127
|
-
appID?: number;
|
|
128
|
-
storefront?: string;
|
|
129
|
-
capsuleImage?: string;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
export interface CatalogWithCarousel {
|
|
133
|
-
sections: Record<string, CatalogSection>;
|
|
134
|
-
carousel?: Record<string, CatalogCarouselItem> | CatalogCarouselItem[];
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
export type CatalogResponse =
|
|
138
|
-
| Record<string, CatalogSection>
|
|
139
|
-
| CatalogWithCarousel;
|
|
140
|
-
|
|
141
|
-
/**
|
|
142
|
-
* UMU ID format: 'steam:${number}' or 'umu:${string | number}'
|
|
143
|
-
* - steam:${number} → maps to umu-${number} for Steam games
|
|
144
|
-
* - umu:${string | number} → maps to umu-${string | number} for non-Steam games
|
|
145
|
-
*/
|
|
146
|
-
export type UmuId = `steam:${number}` | `umu:${string | number}`;
|
|
147
|
-
|
|
148
|
-
export type SetupEventResponse = Omit<
|
|
34
|
+
export type {
|
|
35
|
+
AddonClientToServerEventArgs,
|
|
36
|
+
AddonClientToServerEventName,
|
|
37
|
+
AddonClientToServerWebsocketMessage,
|
|
38
|
+
AddonNotificationMessage,
|
|
39
|
+
AddonServerToClientEventName,
|
|
40
|
+
AddonServerToClientWebsocketMessage,
|
|
41
|
+
BasicLibraryInfo,
|
|
42
|
+
CatalogCarouselItem,
|
|
43
|
+
CatalogResponse,
|
|
44
|
+
CatalogSection,
|
|
45
|
+
CatalogWithCarousel,
|
|
46
|
+
ConfigurationFile,
|
|
47
|
+
ConfigurationOptionType,
|
|
48
|
+
ConfigurationOptionWire,
|
|
149
49
|
LibraryInfo,
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
* UMU ID for the game. Format: 'steam:${number}' or 'umu:${string | number}'
|
|
168
|
-
* - steam:${number} → maps to umu-${number} for Steam games
|
|
169
|
-
* - umu:${string | number} → maps to umu-${string | number} for non-Steam games
|
|
170
|
-
*/
|
|
171
|
-
umuId: UmuId;
|
|
172
|
-
/**
|
|
173
|
-
* Optional DLL overrides. Can be WINEDLLOVERRIDES-style (e.g. "dinput8=n,b") or bare DLL names.
|
|
174
|
-
* Bare names get "=n,b" inferred; entries that already include "=..." are used as-is.
|
|
175
|
-
*/
|
|
176
|
-
dllOverrides?: string[];
|
|
177
|
-
/**
|
|
178
|
-
* Optional PROTONPATH override to use for UMU launches.
|
|
179
|
-
* Omit this unless you absolutely need a specific Proton build/path.
|
|
180
|
-
*/
|
|
181
|
-
protonVersion?: string;
|
|
182
|
-
/**
|
|
183
|
-
* Optional store identifier for protonfixes (e.g., 'gog', 'egs', 'none')
|
|
184
|
-
*/
|
|
185
|
-
store?: string;
|
|
186
|
-
/**
|
|
187
|
-
* Cached Steam shortcut app ID after adding UMU game to Steam (avoids re-adding on each launch)
|
|
188
|
-
*/
|
|
189
|
-
steamShortcutId?: number;
|
|
190
|
-
};
|
|
191
|
-
};
|
|
192
|
-
|
|
193
|
-
export interface EventListenerTypes {
|
|
194
|
-
/**
|
|
195
|
-
* This event is emitted when the addon connects to the OGI Addon Server. Addon does not need to resolve anything.
|
|
196
|
-
* @param event
|
|
197
|
-
* @returns
|
|
198
|
-
*/
|
|
199
|
-
connect: (event: EventResponse<void>) => void;
|
|
200
|
-
|
|
201
|
-
/**
|
|
202
|
-
* This event is emitted when the client requests for the addon to disconnect. Addon does not need to resolve this event, but we recommend `process.exit(0)` so the addon can exit gracefully instead of by force by the addon server.
|
|
203
|
-
* @param reason
|
|
204
|
-
* @returns
|
|
205
|
-
*/
|
|
206
|
-
disconnect: (reason: string) => void;
|
|
207
|
-
/**
|
|
208
|
-
* This event is emitted when the client requests for the addon to configure itself. Addon should resolve the event with the internal configuration. (See ConfigurationBuilder)
|
|
209
|
-
* @param config
|
|
210
|
-
* @returns
|
|
211
|
-
*/
|
|
212
|
-
configure: (config: ConfigurationBuilder) => ConfigurationBuilder;
|
|
213
|
-
/**
|
|
214
|
-
* This event is called when the client provides a response to any event. This should be treated as middleware.
|
|
215
|
-
* @param response
|
|
216
|
-
* @returns
|
|
217
|
-
*/
|
|
218
|
-
response: (response: any) => void;
|
|
219
|
-
|
|
220
|
-
/**
|
|
221
|
-
* This event is called when the client requests for the addon to authenticate itself. You don't need to provide any info.
|
|
222
|
-
* @param config
|
|
223
|
-
* @returns
|
|
224
|
-
*/
|
|
225
|
-
authenticate: (config: any) => void;
|
|
226
|
-
/**
|
|
227
|
-
* This event is emitted when the client requests for a torrent/direct download search to be performed. Addon is given the gameID (could be a steam appID or custom store appID), along with the storefront type. Addon should resolve the event with the search results. (See SearchResult)
|
|
228
|
-
* @param query
|
|
229
|
-
* @param event
|
|
230
|
-
* @returns
|
|
231
|
-
*/
|
|
232
|
-
search: (
|
|
233
|
-
query: {
|
|
234
|
-
storefront: string;
|
|
235
|
-
appID: number;
|
|
236
|
-
} & (
|
|
237
|
-
| {
|
|
238
|
-
for: 'game' | 'task' | 'all';
|
|
239
|
-
}
|
|
240
|
-
| {
|
|
241
|
-
for: 'update';
|
|
242
|
-
libraryInfo: LibraryInfo;
|
|
243
|
-
}
|
|
244
|
-
),
|
|
245
|
-
event: EventResponse<SearchResult[]>
|
|
246
|
-
) => void;
|
|
247
|
-
/**
|
|
248
|
-
* This event is emitted when the client requests for app setup to be performed. Addon should resolve the event with the metadata for the library entry. (See LibraryInfo)
|
|
249
|
-
* @param data
|
|
250
|
-
* @param event
|
|
251
|
-
* @returns
|
|
252
|
-
*/
|
|
253
|
-
setup: (
|
|
254
|
-
data: {
|
|
255
|
-
path: string;
|
|
256
|
-
type: 'direct' | 'torrent' | 'magnet' | 'empty';
|
|
257
|
-
name: string;
|
|
258
|
-
usedRealDebrid: boolean;
|
|
259
|
-
clearOldFilesBeforeUpdate?: boolean;
|
|
260
|
-
multiPartFiles?: {
|
|
261
|
-
name: string;
|
|
262
|
-
downloadURL: string;
|
|
263
|
-
}[];
|
|
264
|
-
appID: number;
|
|
265
|
-
storefront: string;
|
|
266
|
-
manifest?: Record<string, unknown>;
|
|
267
|
-
} & (
|
|
268
|
-
| {
|
|
269
|
-
for: 'game';
|
|
270
|
-
}
|
|
271
|
-
| {
|
|
272
|
-
for: 'update';
|
|
273
|
-
currentLibraryInfo: LibraryInfo;
|
|
274
|
-
}
|
|
275
|
-
),
|
|
276
|
-
event: EventResponse<SetupEventResponse>
|
|
277
|
-
) => void;
|
|
278
|
-
|
|
279
|
-
/**
|
|
280
|
-
* This event is emitted when the client requires for a search to be performed. Input is the search query.
|
|
281
|
-
* @param query
|
|
282
|
-
* @param event
|
|
283
|
-
* @returns
|
|
284
|
-
*/
|
|
285
|
-
'library-search': (
|
|
286
|
-
query: string,
|
|
287
|
-
event: EventResponse<BasicLibraryInfo[]>
|
|
288
|
-
) => void;
|
|
289
|
-
|
|
290
|
-
/**
|
|
291
|
-
* This event is emitted when the client requests for a game details to be fetched. Addon should resolve the event with the game details. This is used to generate a store page for the game.
|
|
292
|
-
* @param appID
|
|
293
|
-
* @param event
|
|
294
|
-
* @returns
|
|
295
|
-
*/
|
|
296
|
-
'game-details': (
|
|
297
|
-
details: { appID: number; storefront: string },
|
|
298
|
-
event: EventResponse<StoreData | undefined>
|
|
299
|
-
) => void;
|
|
300
|
-
|
|
301
|
-
/**
|
|
302
|
-
* This event is emitted when the client requests for the addon to exit. Use this to perform any cleanup tasks, ending with a `process.exit(0)`.
|
|
303
|
-
* @returns
|
|
304
|
-
*/
|
|
305
|
-
exit: () => void;
|
|
306
|
-
|
|
307
|
-
/**
|
|
308
|
-
* This event is emitted when the client requests for a download to be performed with the 'request' type. Addon should resolve the event with a SearchResult containing the actual download info.
|
|
309
|
-
* @param appID
|
|
310
|
-
* @param info
|
|
311
|
-
* @param event
|
|
312
|
-
* @returns
|
|
313
|
-
*/
|
|
314
|
-
'request-dl': (
|
|
315
|
-
appID: number,
|
|
316
|
-
info: SearchResult,
|
|
317
|
-
event: EventResponse<SearchResult>
|
|
318
|
-
) => void;
|
|
319
|
-
|
|
320
|
-
/**
|
|
321
|
-
* This event is emitted when the client requests for a catalog to be fetched. Addon should resolve the event with the catalog.
|
|
322
|
-
* @param event
|
|
323
|
-
* @returns
|
|
324
|
-
*/
|
|
325
|
-
catalog: (event: Omit<EventResponse<CatalogResponse>, 'askForInput'>) => void;
|
|
326
|
-
|
|
327
|
-
/**
|
|
328
|
-
* This event is emitted when the client requests for an addon to check for updates. Addon should resolve the event with the update information.
|
|
329
|
-
* @param data
|
|
330
|
-
* @param event
|
|
331
|
-
* @returns
|
|
332
|
-
*/
|
|
333
|
-
'check-for-updates': (
|
|
334
|
-
data: { appID: number; storefront: string; currentVersion: string },
|
|
335
|
-
event: EventResponse<
|
|
336
|
-
| {
|
|
337
|
-
available: true;
|
|
338
|
-
version: string;
|
|
339
|
-
}
|
|
340
|
-
| {
|
|
341
|
-
available: false;
|
|
342
|
-
}
|
|
343
|
-
>
|
|
344
|
-
) => void;
|
|
345
|
-
|
|
346
|
-
/**
|
|
347
|
-
* This event is emitted when the client is going to launch an app. Addon should use this to perform any pre or post launch tasks.
|
|
348
|
-
* @param data {LibraryInfo} The library information for the app to be launched.
|
|
349
|
-
* @param launchType { 'pre' | 'post' } The type of launch task to perform.
|
|
350
|
-
* @param event {EventResponse<void>} The event response from the server.
|
|
351
|
-
*/
|
|
352
|
-
'launch-app': (
|
|
353
|
-
data: { libraryInfo: LibraryInfo; launchType: 'pre' | 'post' },
|
|
354
|
-
event: EventResponse<void>
|
|
355
|
-
) => void;
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
export interface StoreData {
|
|
359
|
-
name: string;
|
|
360
|
-
publishers: string[];
|
|
361
|
-
developers: string[];
|
|
362
|
-
appID: number;
|
|
363
|
-
releaseDate: string;
|
|
364
|
-
capsuleImage: string;
|
|
365
|
-
coverImage: string;
|
|
366
|
-
basicDescription: string;
|
|
367
|
-
description: string;
|
|
368
|
-
headerImage: string;
|
|
369
|
-
latestVersion: string;
|
|
370
|
-
}
|
|
371
|
-
export interface WebsocketMessageClient {
|
|
372
|
-
event: OGIAddonClientSentEvent;
|
|
373
|
-
id?: string;
|
|
374
|
-
args: any;
|
|
375
|
-
statusError?: string;
|
|
376
|
-
}
|
|
377
|
-
export interface WebsocketMessageServer {
|
|
378
|
-
event: OGIAddonServerSentEvent;
|
|
379
|
-
id?: string;
|
|
380
|
-
args: any;
|
|
381
|
-
statusError?: string;
|
|
382
|
-
}
|
|
50
|
+
OGIAddonConfiguration,
|
|
51
|
+
OGIAddonSDKEventListener,
|
|
52
|
+
SearchResult,
|
|
53
|
+
SetupResponse,
|
|
54
|
+
SetupEventResponse,
|
|
55
|
+
StoreData,
|
|
56
|
+
UmuId,
|
|
57
|
+
SetupCommandData,
|
|
58
|
+
AddonProtocolEventListenerTypes,
|
|
59
|
+
AddonSDKLifecycleEventListenerTypes,
|
|
60
|
+
AddonServerHostEventListeners,
|
|
61
|
+
AddonServerHostEventName,
|
|
62
|
+
AddonServerLifecycleEvent,
|
|
63
|
+
} from '@ogi-sdk/connect';
|
|
64
|
+
|
|
65
|
+
/** @deprecated Use {@link AddonNotificationMessage}. */
|
|
66
|
+
export type Notification = AddonNotificationMessage;
|
|
383
67
|
|
|
384
68
|
/**
|
|
385
|
-
*
|
|
386
|
-
*
|
|
69
|
+
* Addon SDK listener signatures. Protocol commands come from `addonProtocol` in
|
|
70
|
+
* `@ogi-sdk/connect`; lifecycle and builder-specific hooks are merged below.
|
|
387
71
|
*/
|
|
388
|
-
export
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
72
|
+
export type EventListenerTypes = AddonSDKLifecycleEventListenerTypes<
|
|
73
|
+
EventResponse<unknown>
|
|
74
|
+
> &
|
|
75
|
+
AddonProtocolEventListenerTypes<
|
|
76
|
+
EventResponse<unknown>,
|
|
77
|
+
'authenticate' | 'configure' | 'catalog'
|
|
78
|
+
> & {
|
|
79
|
+
authenticate: (config: unknown) => void;
|
|
80
|
+
configure: (config: ConfigurationBuilder) => ConfigurationBuilder;
|
|
81
|
+
catalog: (event: Omit<EventResponse<CatalogResponse>, 'askForInput'>) => void;
|
|
82
|
+
};
|
|
398
83
|
|
|
399
84
|
/**
|
|
400
85
|
* 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.
|
|
@@ -416,7 +101,7 @@ export default class OGIAddon {
|
|
|
416
101
|
public addonWSListener: OGIAddonWSListener;
|
|
417
102
|
public addonInfo: OGIAddonConfiguration;
|
|
418
103
|
public config: Configuration = new Configuration({});
|
|
419
|
-
private eventsAvailable:
|
|
104
|
+
private eventsAvailable: OGIAddonSDKEventListener[] = [];
|
|
420
105
|
private registeredConnectEvent: boolean = false;
|
|
421
106
|
private taskHandlers: Map<
|
|
422
107
|
string,
|
|
@@ -438,10 +123,10 @@ export default class OGIAddon {
|
|
|
438
123
|
|
|
439
124
|
/**
|
|
440
125
|
* Register an event listener for the addon. (See EventListenerTypes)
|
|
441
|
-
* @param event {
|
|
442
|
-
* @param listener {EventListenerTypes[
|
|
126
|
+
* @param event {OGIAddonSDKEventListener}
|
|
127
|
+
* @param listener {EventListenerTypes[OGIAddonSDKEventListener]}
|
|
443
128
|
*/
|
|
444
|
-
public on<T extends
|
|
129
|
+
public on<T extends OGIAddonSDKEventListener>(
|
|
445
130
|
event: T,
|
|
446
131
|
listener: EventListenerTypes[T]
|
|
447
132
|
) {
|
|
@@ -459,7 +144,7 @@ export default class OGIAddon {
|
|
|
459
144
|
}
|
|
460
145
|
}
|
|
461
146
|
|
|
462
|
-
public emit<T extends
|
|
147
|
+
public emit<T extends OGIAddonSDKEventListener>(
|
|
463
148
|
event: T,
|
|
464
149
|
...args: Parameters<EventListenerTypes[T]>
|
|
465
150
|
) {
|
|
@@ -470,7 +155,7 @@ export default class OGIAddon {
|
|
|
470
155
|
* Notify the client using a notification. Provide the type of notification, the message, and an ID.
|
|
471
156
|
* @param notification {Notification}
|
|
472
157
|
*/
|
|
473
|
-
public notify(notification:
|
|
158
|
+
public notify(notification: AddonNotificationMessage) {
|
|
474
159
|
this.addonWSListener.send('notification', [notification]);
|
|
475
160
|
}
|
|
476
161
|
|
|
@@ -481,23 +166,23 @@ export default class OGIAddon {
|
|
|
481
166
|
* @returns {Promise<StoreData>}
|
|
482
167
|
*/
|
|
483
168
|
public async getAppDetails(appID: number, storefront: string) {
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
169
|
+
return await this.addonWSListener.requestResponse<StoreData | undefined>(
|
|
170
|
+
'get-app-details',
|
|
171
|
+
{
|
|
172
|
+
appID,
|
|
173
|
+
storefront,
|
|
174
|
+
}
|
|
175
|
+
);
|
|
491
176
|
}
|
|
492
177
|
|
|
493
178
|
public async searchGame(query: string, storefront: string) {
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
179
|
+
return await this.addonWSListener.requestResponse<BasicLibraryInfo[]>(
|
|
180
|
+
'search-app-name',
|
|
181
|
+
{
|
|
182
|
+
query,
|
|
183
|
+
storefront,
|
|
184
|
+
}
|
|
185
|
+
);
|
|
501
186
|
}
|
|
502
187
|
|
|
503
188
|
/**
|
|
@@ -772,7 +457,7 @@ export class SearchTool<T> {
|
|
|
772
457
|
/**
|
|
773
458
|
* Library Info is the metadata for a library entry after setting up a game.
|
|
774
459
|
*/
|
|
775
|
-
export const ZodLibraryInfo = z.object({
|
|
460
|
+
export const ZodLibraryInfo: z.ZodType<LibraryInfo> = z.object({
|
|
776
461
|
name: z.string(),
|
|
777
462
|
version: z.string(),
|
|
778
463
|
cwd: z.string(),
|
|
@@ -815,36 +500,52 @@ export const ZodLibraryInfo = z.object({
|
|
|
815
500
|
)
|
|
816
501
|
.optional(),
|
|
817
502
|
});
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
message: string;
|
|
822
|
-
id: string;
|
|
823
|
-
}
|
|
503
|
+
|
|
504
|
+
export type { AddonTaskRunEventArgs as TaskRunMessageArgs } from '@ogi-sdk/connect';
|
|
505
|
+
|
|
824
506
|
class OGIAddonWSListener {
|
|
825
|
-
private socket: WebSocket
|
|
507
|
+
private socket: InstanceType<typeof globalThis.WebSocket>;
|
|
508
|
+
private transport: EventResponseSocket<
|
|
509
|
+
AddonServerToClientWebsocketMessage,
|
|
510
|
+
AddonClientToServerWebsocketMessage
|
|
511
|
+
>;
|
|
826
512
|
public eventEmitter: events.EventEmitter;
|
|
827
513
|
public addon: OGIAddon;
|
|
828
514
|
|
|
829
515
|
constructor(ogiAddon: OGIAddon, eventEmitter: events.EventEmitter) {
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
516
|
+
const secret = process.argv
|
|
517
|
+
.find((arg) => arg.startsWith('--addonSecret='))
|
|
518
|
+
?.split('=')[1];
|
|
519
|
+
if (!secret) {
|
|
833
520
|
throw new Error(
|
|
834
521
|
'No secret provided. This usually happens because the addon was not started by the OGI Addon Server.'
|
|
835
522
|
);
|
|
836
523
|
}
|
|
524
|
+
|
|
525
|
+
// get the port from the arguments
|
|
526
|
+
let port = process.argv
|
|
527
|
+
.find((arg) => arg.startsWith('--addonPort='))
|
|
528
|
+
?.split('=')[1];
|
|
529
|
+
if (!port) {
|
|
530
|
+
port = defaultPort.toString();
|
|
531
|
+
}
|
|
532
|
+
|
|
837
533
|
this.addon = ogiAddon;
|
|
838
534
|
this.eventEmitter = eventEmitter;
|
|
839
|
-
|
|
840
|
-
|
|
535
|
+
const WebSocketConstructor = globalThis.WebSocket;
|
|
536
|
+
if (!WebSocketConstructor) {
|
|
537
|
+
throw new Error('WebSocket is not available in this runtime');
|
|
538
|
+
}
|
|
539
|
+
this.socket = new WebSocketConstructor('ws://localhost:' + port);
|
|
540
|
+
this.transport = new EventResponseSocket(this.socket);
|
|
541
|
+
this.socket.addEventListener('open', () => {
|
|
841
542
|
console.log('Connected to OGI Addon Server');
|
|
842
543
|
console.log('OGI Addon Server Version:', VERSION);
|
|
843
544
|
|
|
844
545
|
// Authenticate with OGI Addon Server
|
|
845
|
-
this.send('authenticate', {
|
|
546
|
+
this.send('authenticate' as AddonClientToServerEventName, {
|
|
846
547
|
...this.addon.addonInfo,
|
|
847
|
-
secret
|
|
548
|
+
secret,
|
|
848
549
|
ogiVersion: VERSION,
|
|
849
550
|
});
|
|
850
551
|
|
|
@@ -855,59 +556,44 @@ class OGIAddonWSListener {
|
|
|
855
556
|
this.addon.config = new Configuration(configBuilder.build(true));
|
|
856
557
|
|
|
857
558
|
// wait for the config-update to be received then send connect
|
|
858
|
-
const
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
let data: string;
|
|
862
|
-
if (typeof event === 'string') {
|
|
863
|
-
data = event;
|
|
864
|
-
} else if (event instanceof Buffer) {
|
|
865
|
-
data = event.toString();
|
|
866
|
-
} else if (event && typeof (event as any).data === 'string') {
|
|
867
|
-
data = (event as any).data;
|
|
868
|
-
} else if (event && (event as any).data instanceof Buffer) {
|
|
869
|
-
data = (event as any).data.toString();
|
|
870
|
-
} else {
|
|
871
|
-
// fallback for other types
|
|
872
|
-
data = event.toString();
|
|
873
|
-
}
|
|
874
|
-
const message: WebsocketMessageServer = JSON.parse(data);
|
|
875
|
-
if (message.event === 'config-update') {
|
|
559
|
+
const unsubscribeConfigListener = this.transport.on(
|
|
560
|
+
'config-update',
|
|
561
|
+
() => {
|
|
876
562
|
console.log('Config update received');
|
|
877
|
-
|
|
563
|
+
unsubscribeConfigListener();
|
|
878
564
|
this.eventEmitter.emit(
|
|
879
565
|
'connect',
|
|
880
566
|
new EventResponse<void>((screen, name, description) => {
|
|
881
|
-
return this.userInputAsked(
|
|
882
|
-
screen,
|
|
883
|
-
name,
|
|
884
|
-
description,
|
|
885
|
-
this.socket
|
|
886
|
-
);
|
|
567
|
+
return this.userInputAsked(screen, name, description);
|
|
887
568
|
})
|
|
888
569
|
);
|
|
889
570
|
}
|
|
890
|
-
|
|
891
|
-
this.socket.on('message', configListener);
|
|
571
|
+
);
|
|
892
572
|
});
|
|
893
573
|
|
|
894
|
-
this.socket.
|
|
895
|
-
|
|
574
|
+
this.socket.addEventListener('error', (event) => {
|
|
575
|
+
this.transport.rejectPendingResponses('Websocket error');
|
|
576
|
+
const message =
|
|
577
|
+
event instanceof ErrorEvent
|
|
578
|
+
? event.message
|
|
579
|
+
: event.type;
|
|
580
|
+
if (message.includes('Failed to connect')) {
|
|
896
581
|
throw new Error(
|
|
897
582
|
'OGI Addon Server is not running/is unreachable. Please start the server and try again.'
|
|
898
583
|
);
|
|
899
584
|
}
|
|
900
|
-
console.error('An error occurred:',
|
|
585
|
+
console.error('An error occurred:', event);
|
|
901
586
|
});
|
|
902
587
|
|
|
903
|
-
this.socket.
|
|
904
|
-
|
|
905
|
-
|
|
588
|
+
this.socket.addEventListener('close', (event) => {
|
|
589
|
+
this.transport.rejectPendingResponses('Websocket closed');
|
|
590
|
+
if (event.code === 1008) {
|
|
591
|
+
console.error('Authentication failed:', event.reason);
|
|
906
592
|
return;
|
|
907
593
|
}
|
|
908
|
-
this.eventEmitter.emit('disconnect', reason);
|
|
594
|
+
this.eventEmitter.emit('disconnect', event.reason);
|
|
909
595
|
console.log('Disconnected from OGI Addon Server');
|
|
910
|
-
console.error(reason
|
|
596
|
+
console.error(event.reason);
|
|
911
597
|
this.eventEmitter.emit('exit');
|
|
912
598
|
this.socket.close();
|
|
913
599
|
});
|
|
@@ -920,173 +606,188 @@ class OGIAddonWSListener {
|
|
|
920
606
|
>(
|
|
921
607
|
configBuilt: ConfigurationBuilder<U>,
|
|
922
608
|
name: string,
|
|
923
|
-
description: string
|
|
924
|
-
socket: WebSocket
|
|
609
|
+
description: string
|
|
925
610
|
): Promise<U> {
|
|
926
611
|
const config = configBuilt.build(false);
|
|
927
|
-
const
|
|
928
|
-
|
|
929
|
-
throw new Error('Socket is not connected');
|
|
930
|
-
}
|
|
931
|
-
socket.send(
|
|
932
|
-
JSON.stringify({
|
|
612
|
+
const response = await this.transport.send(
|
|
613
|
+
{
|
|
933
614
|
event: 'input-asked',
|
|
934
615
|
args: {
|
|
935
616
|
config,
|
|
936
617
|
name,
|
|
937
618
|
description,
|
|
938
619
|
},
|
|
939
|
-
|
|
940
|
-
}
|
|
620
|
+
} as AddonClientToServerWebsocketMessage,
|
|
621
|
+
{ expectResponse: true }
|
|
941
622
|
);
|
|
942
|
-
return
|
|
623
|
+
return response.args as U;
|
|
943
624
|
}
|
|
944
625
|
|
|
945
626
|
/**
|
|
946
627
|
* Registers the message receiver for the socket. This is used to receive messages from the server and handle them.
|
|
947
628
|
*/
|
|
948
629
|
private registerMessageReceiver() {
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
630
|
+
const events: AddonServerToClientWebsocketMessage['event'][] = [
|
|
631
|
+
'config-update',
|
|
632
|
+
'search',
|
|
633
|
+
'setup',
|
|
634
|
+
'library-search',
|
|
635
|
+
'game-details',
|
|
636
|
+
'check-for-updates',
|
|
637
|
+
'request-dl',
|
|
638
|
+
'catalog',
|
|
639
|
+
'task-run',
|
|
640
|
+
'launch-app',
|
|
641
|
+
];
|
|
642
|
+
|
|
643
|
+
for (const event of events) {
|
|
644
|
+
this.transport.on(event, async (message) => {
|
|
645
|
+
switch (message.event) {
|
|
646
|
+
case 'config-update':
|
|
647
|
+
const result = this.addon.config.updateConfig(
|
|
648
|
+
message.args as DefiniteConfig
|
|
962
649
|
);
|
|
963
|
-
|
|
964
|
-
|
|
650
|
+
if (!result[0]) {
|
|
651
|
+
this.respondToMessage(
|
|
652
|
+
message.id!!,
|
|
653
|
+
{
|
|
654
|
+
success: false,
|
|
655
|
+
error: result[1],
|
|
656
|
+
},
|
|
657
|
+
undefined
|
|
658
|
+
);
|
|
659
|
+
} else {
|
|
660
|
+
this.respondToMessage(message.id!!, { success: true }, undefined);
|
|
661
|
+
}
|
|
662
|
+
break;
|
|
663
|
+
case 'search':
|
|
664
|
+
await this.handleEventWithResponse<SearchResult[]>(
|
|
665
|
+
message,
|
|
666
|
+
(event) => this.eventEmitter.emit('search', message.args, event)
|
|
667
|
+
);
|
|
668
|
+
break;
|
|
669
|
+
case 'setup': {
|
|
670
|
+
let setupEvent = new EventResponse<SetupResponse>(
|
|
671
|
+
(screen, name, description) =>
|
|
672
|
+
this.userInputAsked(screen, name, description)
|
|
673
|
+
);
|
|
674
|
+
this.eventEmitter.emit('setup', message.args, setupEvent);
|
|
675
|
+
const interval = setInterval(() => {
|
|
676
|
+
if (setupEvent.resolved) {
|
|
677
|
+
clearInterval(interval);
|
|
678
|
+
return;
|
|
679
|
+
}
|
|
680
|
+
this.send('defer-update', {
|
|
681
|
+
logs: setupEvent.logs,
|
|
682
|
+
deferID:
|
|
683
|
+
message.args as AddonClientToServerEventArgs['defer-update']['deferID'],
|
|
684
|
+
progress: setupEvent.progress,
|
|
685
|
+
failed: setupEvent.failed,
|
|
686
|
+
} as AddonClientToServerEventArgs['defer-update']);
|
|
687
|
+
}, 100);
|
|
688
|
+
const setupResult = await this.waitForEventToRespond(setupEvent);
|
|
689
|
+
this.respondToMessage(message.id!!, setupResult.data, setupEvent);
|
|
690
|
+
break;
|
|
965
691
|
}
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
692
|
+
case 'library-search':
|
|
693
|
+
await this.handleEventWithResponse<BasicLibraryInfo[]>(
|
|
694
|
+
message,
|
|
695
|
+
(event) =>
|
|
696
|
+
this.eventEmitter.emit('library-search', message.args, event)
|
|
697
|
+
);
|
|
698
|
+
break;
|
|
699
|
+
case 'game-details':
|
|
700
|
+
await this.handleEventWithResponse<StoreData | undefined>(
|
|
701
|
+
message,
|
|
702
|
+
(event) =>
|
|
703
|
+
this.eventEmitter.emit('game-details', message.args, event),
|
|
704
|
+
{
|
|
705
|
+
requireListener: 'game-details',
|
|
706
|
+
noListenerError: 'No event listener for game-details',
|
|
707
|
+
}
|
|
708
|
+
);
|
|
709
|
+
break;
|
|
710
|
+
case 'check-for-updates':
|
|
711
|
+
await this.handleEventWithResponse<
|
|
712
|
+
{ available: true; version: string } | { available: false }
|
|
713
|
+
>(message, (event) =>
|
|
714
|
+
this.eventEmitter.emit('check-for-updates', message.args, event)
|
|
715
|
+
);
|
|
716
|
+
break;
|
|
717
|
+
case 'request-dl':
|
|
718
|
+
let requestDLEvent = new EventResponse<SearchResult>(
|
|
719
|
+
(screen, name, description) =>
|
|
720
|
+
this.userInputAsked(screen, name, description)
|
|
721
|
+
);
|
|
722
|
+
if (this.eventEmitter.listenerCount('request-dl') === 0) {
|
|
723
|
+
this.respondToMessage(
|
|
724
|
+
message.id!!,
|
|
725
|
+
{
|
|
726
|
+
error: 'No event listener for request-dl',
|
|
727
|
+
},
|
|
728
|
+
requestDLEvent
|
|
729
|
+
);
|
|
730
|
+
break;
|
|
982
731
|
}
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
{
|
|
1007
|
-
requireListener: 'game-details',
|
|
1008
|
-
noListenerError: 'No event listener for game-details',
|
|
732
|
+
const { appID, info } = message.args as {
|
|
733
|
+
appID: number;
|
|
734
|
+
info: SearchResult;
|
|
735
|
+
};
|
|
736
|
+
this.eventEmitter.emit(
|
|
737
|
+
'request-dl',
|
|
738
|
+
appID,
|
|
739
|
+
info,
|
|
740
|
+
requestDLEvent as EventResponse<SearchResult>
|
|
741
|
+
);
|
|
742
|
+
const requestDLResult =
|
|
743
|
+
await this.waitForEventToRespond(requestDLEvent);
|
|
744
|
+
if (requestDLEvent.failed) {
|
|
745
|
+
this.respondToMessage(message.id!!, undefined, requestDLEvent);
|
|
746
|
+
break;
|
|
747
|
+
}
|
|
748
|
+
if (
|
|
749
|
+
requestDLEvent.data === undefined ||
|
|
750
|
+
requestDLEvent.data?.downloadType === 'request'
|
|
751
|
+
) {
|
|
752
|
+
throw new Error(
|
|
753
|
+
'Request DL event did not return a valid result. Please ensure that the event does not resolve with another `request` download type.'
|
|
754
|
+
);
|
|
1009
755
|
}
|
|
1010
|
-
);
|
|
1011
|
-
break;
|
|
1012
|
-
case 'check-for-updates':
|
|
1013
|
-
await this.handleEventWithResponse<
|
|
1014
|
-
{ available: true; version: string } | { available: false }
|
|
1015
|
-
>(message, (event) =>
|
|
1016
|
-
this.eventEmitter.emit('check-for-updates', message.args, event)
|
|
1017
|
-
);
|
|
1018
|
-
break;
|
|
1019
|
-
case 'request-dl':
|
|
1020
|
-
let requestDLEvent = new EventResponse<SearchResult>(
|
|
1021
|
-
(screen, name, description) =>
|
|
1022
|
-
this.userInputAsked(screen, name, description, this.socket)
|
|
1023
|
-
);
|
|
1024
|
-
if (this.eventEmitter.listenerCount('request-dl') === 0) {
|
|
1025
756
|
this.respondToMessage(
|
|
1026
757
|
message.id!!,
|
|
1027
|
-
|
|
1028
|
-
error: 'No event listener for request-dl',
|
|
1029
|
-
},
|
|
758
|
+
requestDLResult.data,
|
|
1030
759
|
requestDLEvent
|
|
1031
760
|
);
|
|
1032
761
|
break;
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
requestDLEvent
|
|
1039
|
-
);
|
|
1040
|
-
const requestDLResult =
|
|
1041
|
-
await this.waitForEventToRespond(requestDLEvent);
|
|
1042
|
-
if (requestDLEvent.failed) {
|
|
1043
|
-
this.respondToMessage(message.id!!, undefined, requestDLEvent);
|
|
762
|
+
case 'catalog':
|
|
763
|
+
await this.handleEventWithResponseNoInput<CatalogResponse>(
|
|
764
|
+
message,
|
|
765
|
+
(event) => this.eventEmitter.emit('catalog', event)
|
|
766
|
+
);
|
|
1044
767
|
break;
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
) {
|
|
1050
|
-
throw new Error(
|
|
1051
|
-
'Request DL event did not return a valid result. Please ensure that the event does not resolve with another `request` download type.'
|
|
768
|
+
case 'task-run': {
|
|
769
|
+
let taskRunEvent = new EventResponse<void>(
|
|
770
|
+
(screen, name, description) =>
|
|
771
|
+
this.userInputAsked(screen, name, description)
|
|
1052
772
|
);
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
// Check for taskName: first from args directly (from SearchResult), then from manifest.__taskName (for ActionOption)
|
|
1073
|
-
const taskName =
|
|
1074
|
-
message.args.taskName && typeof message.args.taskName === 'string'
|
|
1075
|
-
? message.args.taskName
|
|
1076
|
-
: message.args.manifest &&
|
|
1077
|
-
typeof message.args.manifest === 'object'
|
|
1078
|
-
? (message.args.manifest as Record<string, unknown>).__taskName
|
|
1079
|
-
: undefined;
|
|
1080
|
-
|
|
1081
|
-
if (
|
|
1082
|
-
taskName &&
|
|
1083
|
-
typeof taskName === 'string' &&
|
|
1084
|
-
this.addon.hasTaskHandler(taskName)
|
|
1085
|
-
) {
|
|
1086
|
-
// Use the registered task handler
|
|
1087
|
-
const handler = this.addon.getTaskHandler(taskName)!;
|
|
1088
|
-
const task = new Task(taskRunEvent);
|
|
1089
|
-
try {
|
|
773
|
+
const args = message.args as AddonTaskRunEventArgs;
|
|
774
|
+
|
|
775
|
+
// Check for taskName: first from args directly (from SearchResult), then from manifest.__taskName (for ActionOption)
|
|
776
|
+
const taskName =
|
|
777
|
+
args.taskName && typeof args.taskName === 'string'
|
|
778
|
+
? args.taskName
|
|
779
|
+
: args.manifest && typeof args.manifest === 'object'
|
|
780
|
+
? args.manifest.__taskName
|
|
781
|
+
: undefined;
|
|
782
|
+
|
|
783
|
+
if (
|
|
784
|
+
taskName &&
|
|
785
|
+
typeof taskName === 'string' &&
|
|
786
|
+
this.addon.hasTaskHandler(taskName)
|
|
787
|
+
) {
|
|
788
|
+
// Use the registered task handler
|
|
789
|
+
const handler = this.addon.getTaskHandler(taskName)!;
|
|
790
|
+
const task = new Task(taskRunEvent);
|
|
1090
791
|
const interval = setInterval(() => {
|
|
1091
792
|
if (taskRunEvent.resolved) {
|
|
1092
793
|
clearInterval(interval);
|
|
@@ -1094,48 +795,55 @@ class OGIAddonWSListener {
|
|
|
1094
795
|
}
|
|
1095
796
|
this.send('defer-update', {
|
|
1096
797
|
logs: taskRunEvent.logs,
|
|
1097
|
-
deferID:
|
|
798
|
+
deferID: args.deferID ?? '',
|
|
1098
799
|
progress: taskRunEvent.progress,
|
|
1099
800
|
failed: taskRunEvent.failed,
|
|
1100
|
-
} as
|
|
801
|
+
} as AddonClientToServerEventArgs['defer-update']);
|
|
1101
802
|
}, 100);
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
803
|
+
try {
|
|
804
|
+
const result = handler(task, {
|
|
805
|
+
manifest: args.manifest || {},
|
|
806
|
+
downloadPath: args.downloadPath || '',
|
|
807
|
+
name: args.name || '',
|
|
808
|
+
libraryInfo: args.libraryInfo,
|
|
809
|
+
});
|
|
810
|
+
// If handler returns a promise, wait for it
|
|
811
|
+
if (result instanceof Promise) {
|
|
812
|
+
await result;
|
|
813
|
+
}
|
|
814
|
+
} catch (error) {
|
|
815
|
+
taskRunEvent.fail(
|
|
816
|
+
error instanceof Error ? error.message : String(error)
|
|
817
|
+
);
|
|
818
|
+
} finally {
|
|
819
|
+
clearInterval(interval);
|
|
1111
820
|
}
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
} catch (error) {
|
|
821
|
+
} else {
|
|
822
|
+
// No handler found - fail the task
|
|
1115
823
|
taskRunEvent.fail(
|
|
1116
|
-
|
|
824
|
+
taskName
|
|
825
|
+
? `No task handler registered for task name: ${taskName}`
|
|
826
|
+
: 'No task name provided'
|
|
1117
827
|
);
|
|
1118
828
|
}
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
829
|
+
|
|
830
|
+
const taskRunResult =
|
|
831
|
+
await this.waitForEventToRespond(taskRunEvent);
|
|
832
|
+
this.respondToMessage(
|
|
833
|
+
message.id!!,
|
|
834
|
+
taskRunResult.data,
|
|
835
|
+
taskRunEvent
|
|
1125
836
|
);
|
|
837
|
+
break;
|
|
1126
838
|
}
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
839
|
+
case 'launch-app':
|
|
840
|
+
await this.handleEventWithResponse<void>(message, (event) =>
|
|
841
|
+
this.eventEmitter.emit('launch-app', message.args, event)
|
|
842
|
+
);
|
|
843
|
+
break;
|
|
1131
844
|
}
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
this.eventEmitter.emit('launch-app', message.args, event)
|
|
1135
|
-
);
|
|
1136
|
-
break;
|
|
1137
|
-
}
|
|
1138
|
-
});
|
|
845
|
+
});
|
|
846
|
+
}
|
|
1139
847
|
}
|
|
1140
848
|
|
|
1141
849
|
private waitForEventToRespond<T>(
|
|
@@ -1171,12 +879,12 @@ class OGIAddonWSListener {
|
|
|
1171
879
|
* If options.requireListener is set and that event has no listeners, responds with options.noListenerError and returns.
|
|
1172
880
|
*/
|
|
1173
881
|
private async handleEventWithResponse<T>(
|
|
1174
|
-
message:
|
|
882
|
+
message: AddonServerToClientWebsocketMessage,
|
|
1175
883
|
emit: (event: EventResponse<T>) => void,
|
|
1176
884
|
options?: { requireListener: string; noListenerError: string }
|
|
1177
885
|
): Promise<void> {
|
|
1178
886
|
const event = new EventResponse<T>((screen, name, description) =>
|
|
1179
|
-
this.userInputAsked(screen, name, description
|
|
887
|
+
this.userInputAsked(screen, name, description)
|
|
1180
888
|
);
|
|
1181
889
|
if (
|
|
1182
890
|
options &&
|
|
@@ -1198,7 +906,7 @@ class OGIAddonWSListener {
|
|
|
1198
906
|
* Same as handleEventWithResponse but for events that don't need userInputAsked (e.g. catalog).
|
|
1199
907
|
*/
|
|
1200
908
|
private async handleEventWithResponseNoInput<T>(
|
|
1201
|
-
message:
|
|
909
|
+
message: AddonServerToClientWebsocketMessage,
|
|
1202
910
|
emit: (event: EventResponse<T>) => void
|
|
1203
911
|
): Promise<void> {
|
|
1204
912
|
const event = new EventResponse<T>();
|
|
@@ -1212,49 +920,41 @@ class OGIAddonWSListener {
|
|
|
1212
920
|
response: any,
|
|
1213
921
|
originalEvent: EventResponse<any> | undefined
|
|
1214
922
|
) {
|
|
1215
|
-
this.
|
|
1216
|
-
|
|
923
|
+
void this.transport.send(
|
|
924
|
+
{
|
|
1217
925
|
event: 'response',
|
|
1218
926
|
id: messageID,
|
|
1219
927
|
args: response,
|
|
1220
928
|
statusError: originalEvent ? originalEvent.failed : undefined,
|
|
1221
|
-
}
|
|
929
|
+
} as AddonClientToServerWebsocketMessage,
|
|
930
|
+
{ expectResponse: false }
|
|
1222
931
|
);
|
|
1223
932
|
console.log('dispatched response to ' + messageID);
|
|
1224
933
|
}
|
|
1225
934
|
|
|
1226
|
-
public
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
if (message.id === messageID) {
|
|
1237
|
-
resolve(message.args);
|
|
1238
|
-
} else {
|
|
1239
|
-
this.socket.once('message', waiter);
|
|
1240
|
-
}
|
|
1241
|
-
};
|
|
1242
|
-
this.socket.once('message', waiter);
|
|
1243
|
-
});
|
|
935
|
+
public async requestResponse<T>(
|
|
936
|
+
event: AddonClientToServerEventName,
|
|
937
|
+
args: AddonClientToServerEventArgs[AddonClientToServerEventName]
|
|
938
|
+
): Promise<T> {
|
|
939
|
+
const response = await this.transport.send(
|
|
940
|
+
{ event, args } as AddonClientToServerWebsocketMessage,
|
|
941
|
+
{ expectResponse: true }
|
|
942
|
+
);
|
|
943
|
+
return response.args as T;
|
|
1244
944
|
}
|
|
1245
945
|
|
|
1246
946
|
public send(
|
|
1247
|
-
event:
|
|
1248
|
-
args:
|
|
947
|
+
event: AddonClientToServerEventName,
|
|
948
|
+
args: AddonClientToServerEventArgs[AddonClientToServerEventName]
|
|
1249
949
|
): string {
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
JSON.stringify({
|
|
950
|
+
const id = randomMessageId();
|
|
951
|
+
void this.transport.send(
|
|
952
|
+
{
|
|
1254
953
|
event,
|
|
1255
954
|
args,
|
|
1256
955
|
id,
|
|
1257
|
-
}
|
|
956
|
+
} as AddonClientToServerWebsocketMessage,
|
|
957
|
+
{ expectResponse: false }
|
|
1258
958
|
);
|
|
1259
959
|
return id;
|
|
1260
960
|
}
|