ogi-addon 2.3.1 → 2.4.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 +52 -26
- package/build/main.cjs.map +1 -1
- package/build/main.d.cts +138 -3
- package/build/main.d.mts +138 -3
- package/build/main.mjs +52 -26
- package/build/main.mjs.map +1 -1
- package/package.json +1 -1
- package/src/main.ts +165 -85
package/src/main.ts
CHANGED
|
@@ -7,6 +7,9 @@ import EventResponse from './EventResponse';
|
|
|
7
7
|
import type { SearchResult } from './SearchEngine';
|
|
8
8
|
import Fuse, { IFuseOptions } from 'fuse.js';
|
|
9
9
|
|
|
10
|
+
/**
|
|
11
|
+
* Exposed events that the programmer can use to listen to and emit events.
|
|
12
|
+
*/
|
|
10
13
|
export type OGIAddonEvent =
|
|
11
14
|
| 'connect'
|
|
12
15
|
| 'disconnect'
|
|
@@ -19,8 +22,12 @@ export type OGIAddonEvent =
|
|
|
19
22
|
| 'exit'
|
|
20
23
|
| 'check-for-updates'
|
|
21
24
|
| 'request-dl'
|
|
22
|
-
| 'catalog'
|
|
25
|
+
| 'catalog'
|
|
26
|
+
| 'launch-app';
|
|
23
27
|
|
|
28
|
+
/**
|
|
29
|
+
* The events that the client can send to the server and are handled by the server.
|
|
30
|
+
*/
|
|
24
31
|
export type OGIAddonClientSentEvent =
|
|
25
32
|
| 'response'
|
|
26
33
|
| 'authenticate'
|
|
@@ -33,10 +40,15 @@ export type OGIAddonClientSentEvent =
|
|
|
33
40
|
| 'flag'
|
|
34
41
|
| 'task-update';
|
|
35
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
|
+
*/
|
|
36
47
|
export type OGIAddonServerSentEvent =
|
|
37
48
|
| 'authenticate'
|
|
38
49
|
| 'configure'
|
|
39
50
|
| 'config-update'
|
|
51
|
+
| 'launch-app'
|
|
40
52
|
| 'search'
|
|
41
53
|
| 'setup'
|
|
42
54
|
| 'response'
|
|
@@ -122,7 +134,16 @@ export interface CatalogWithCarousel {
|
|
|
122
134
|
carousel?: Record<string, CatalogCarouselItem> | CatalogCarouselItem[];
|
|
123
135
|
}
|
|
124
136
|
|
|
125
|
-
export type CatalogResponse =
|
|
137
|
+
export type CatalogResponse =
|
|
138
|
+
| Record<string, CatalogSection>
|
|
139
|
+
| CatalogWithCarousel;
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* UMU ID format: 'steam:${number}' or 'umu:${number}'
|
|
143
|
+
* - steam:${number} → maps to umu-${number} for Steam games
|
|
144
|
+
* - umu:${number} → maps to umu-${number} for non-Steam games
|
|
145
|
+
*/
|
|
146
|
+
export type UmuId = `steam:${number}` | `umu:${number}`;
|
|
126
147
|
|
|
127
148
|
export type SetupEventResponse = Omit<
|
|
128
149
|
LibraryInfo,
|
|
@@ -138,6 +159,35 @@ export type SetupEventResponse = Omit<
|
|
|
138
159
|
name: string;
|
|
139
160
|
path: string;
|
|
140
161
|
}[];
|
|
162
|
+
/**
|
|
163
|
+
* UMU Proton integration configuration
|
|
164
|
+
*/
|
|
165
|
+
umu?: {
|
|
166
|
+
/**
|
|
167
|
+
* UMU ID for the game. Format: 'steam:${number}' or 'umu:${number}'
|
|
168
|
+
* - steam:${number} → maps to umu-${number} for Steam games
|
|
169
|
+
* - umu:${number} → maps to umu-${number} for non-Steam games
|
|
170
|
+
*/
|
|
171
|
+
umuId: UmuId;
|
|
172
|
+
/**
|
|
173
|
+
* Optional DLL overrides. These are relative to the game's cwd.
|
|
174
|
+
* System automatically prepends cwd and sets WINEDLLOVERRIDES="dll=n,b"
|
|
175
|
+
*/
|
|
176
|
+
dllOverrides?: string[];
|
|
177
|
+
/**
|
|
178
|
+
* Optional Proton version to use (e.g., 'GE-Proton9-5', 'GE-Proton')
|
|
179
|
+
* If not specified, uses latest UMU-Proton
|
|
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
|
+
};
|
|
141
191
|
};
|
|
142
192
|
|
|
143
193
|
export interface EventListenerTypes {
|
|
@@ -272,12 +322,7 @@ export interface EventListenerTypes {
|
|
|
272
322
|
* @param event
|
|
273
323
|
* @returns
|
|
274
324
|
*/
|
|
275
|
-
catalog: (
|
|
276
|
-
event: Omit<
|
|
277
|
-
EventResponse<CatalogResponse>,
|
|
278
|
-
'askForInput'
|
|
279
|
-
>
|
|
280
|
-
) => void;
|
|
325
|
+
catalog: (event: Omit<EventResponse<CatalogResponse>, 'askForInput'>) => void;
|
|
281
326
|
|
|
282
327
|
/**
|
|
283
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.
|
|
@@ -297,6 +342,17 @@ export interface EventListenerTypes {
|
|
|
297
342
|
}
|
|
298
343
|
>
|
|
299
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;
|
|
300
356
|
}
|
|
301
357
|
|
|
302
358
|
export interface StoreData {
|
|
@@ -835,6 +891,36 @@ export const ZodLibraryInfo = z.object({
|
|
|
835
891
|
addonsource: z.string(),
|
|
836
892
|
coverImage: z.string(),
|
|
837
893
|
titleImage: z.string().optional(),
|
|
894
|
+
/**
|
|
895
|
+
* UMU Proton integration configuration (Linux only)
|
|
896
|
+
*/
|
|
897
|
+
umu: z
|
|
898
|
+
.object({
|
|
899
|
+
umuId: z
|
|
900
|
+
.string()
|
|
901
|
+
.regex(/^(steam|umu):\d+$/, 'Must be in format steam:{number} or umu:{number}'),
|
|
902
|
+
dllOverrides: z.array(z.string()).optional(),
|
|
903
|
+
protonVersion: z.string().optional(),
|
|
904
|
+
store: z.string().optional(),
|
|
905
|
+
winePrefixPath: z.string().optional(),
|
|
906
|
+
steamShortcutId: z.number().optional(),
|
|
907
|
+
})
|
|
908
|
+
.optional(),
|
|
909
|
+
/**
|
|
910
|
+
* Legacy mode flag for games using old Steam/flatpak wine system
|
|
911
|
+
*/
|
|
912
|
+
legacyMode: z.boolean().optional(),
|
|
913
|
+
/**
|
|
914
|
+
* Redistributables to install (for backward compatibility)
|
|
915
|
+
*/
|
|
916
|
+
redistributables: z
|
|
917
|
+
.array(
|
|
918
|
+
z.object({
|
|
919
|
+
name: z.string(),
|
|
920
|
+
path: z.string(),
|
|
921
|
+
})
|
|
922
|
+
)
|
|
923
|
+
.optional(),
|
|
838
924
|
});
|
|
839
925
|
export type LibraryInfo = z.infer<typeof ZodLibraryInfo>;
|
|
840
926
|
interface Notification {
|
|
@@ -963,6 +1049,9 @@ class OGIAddonWSListener {
|
|
|
963
1049
|
return await this.waitForResponseFromServer<U>(id);
|
|
964
1050
|
}
|
|
965
1051
|
|
|
1052
|
+
/**
|
|
1053
|
+
* Registers the message receiver for the socket. This is used to receive messages from the server and handle them.
|
|
1054
|
+
*/
|
|
966
1055
|
private registerMessageReceiver() {
|
|
967
1056
|
this.socket.on('message', async (data: string) => {
|
|
968
1057
|
const message: WebsocketMessageServer = JSON.parse(data);
|
|
@@ -983,17 +1072,8 @@ class OGIAddonWSListener {
|
|
|
983
1072
|
}
|
|
984
1073
|
break;
|
|
985
1074
|
case 'search':
|
|
986
|
-
|
|
987
|
-
(
|
|
988
|
-
this.userInputAsked(screen, name, description, this.socket)
|
|
989
|
-
);
|
|
990
|
-
this.eventEmitter.emit('search', message.args, searchResultEvent);
|
|
991
|
-
const searchResult =
|
|
992
|
-
await this.waitForEventToRespond(searchResultEvent);
|
|
993
|
-
this.respondToMessage(
|
|
994
|
-
message.id!!,
|
|
995
|
-
searchResult.data,
|
|
996
|
-
searchResultEvent
|
|
1075
|
+
await this.handleEventWithResponse<SearchResult[]>(message, (event) =>
|
|
1076
|
+
this.eventEmitter.emit('search', message.args, event)
|
|
997
1077
|
);
|
|
998
1078
|
break;
|
|
999
1079
|
case 'setup': {
|
|
@@ -1019,74 +1099,28 @@ class OGIAddonWSListener {
|
|
|
1019
1099
|
break;
|
|
1020
1100
|
}
|
|
1021
1101
|
case 'library-search':
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
this.eventEmitter.emit(
|
|
1027
|
-
'library-search',
|
|
1028
|
-
message.args,
|
|
1029
|
-
librarySearchEvent
|
|
1030
|
-
);
|
|
1031
|
-
const librarySearchResult =
|
|
1032
|
-
await this.waitForEventToRespond(librarySearchEvent);
|
|
1033
|
-
this.respondToMessage(
|
|
1034
|
-
message.id!!,
|
|
1035
|
-
librarySearchResult.data,
|
|
1036
|
-
librarySearchEvent
|
|
1102
|
+
await this.handleEventWithResponse<BasicLibraryInfo[]>(
|
|
1103
|
+
message,
|
|
1104
|
+
(event) =>
|
|
1105
|
+
this.eventEmitter.emit('library-search', message.args, event)
|
|
1037
1106
|
);
|
|
1038
1107
|
break;
|
|
1039
1108
|
case 'game-details':
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
error: 'No event listener for game-details',
|
|
1049
|
-
},
|
|
1050
|
-
gameDetailsEvent
|
|
1051
|
-
);
|
|
1052
|
-
break;
|
|
1053
|
-
}
|
|
1054
|
-
this.eventEmitter.emit(
|
|
1055
|
-
'game-details',
|
|
1056
|
-
message.args,
|
|
1057
|
-
gameDetailsEvent
|
|
1058
|
-
);
|
|
1059
|
-
const gameDetailsResult =
|
|
1060
|
-
await this.waitForEventToRespond(gameDetailsEvent);
|
|
1061
|
-
this.respondToMessage(
|
|
1062
|
-
message.id!!,
|
|
1063
|
-
gameDetailsResult.data,
|
|
1064
|
-
gameDetailsEvent
|
|
1109
|
+
await this.handleEventWithResponse<StoreData | undefined>(
|
|
1110
|
+
message,
|
|
1111
|
+
(event) =>
|
|
1112
|
+
this.eventEmitter.emit('game-details', message.args, event),
|
|
1113
|
+
{
|
|
1114
|
+
requireListener: 'game-details',
|
|
1115
|
+
noListenerError: 'No event listener for game-details',
|
|
1116
|
+
}
|
|
1065
1117
|
);
|
|
1066
1118
|
break;
|
|
1067
1119
|
case 'check-for-updates':
|
|
1068
|
-
|
|
1069
|
-
| {
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
}
|
|
1073
|
-
| {
|
|
1074
|
-
available: false;
|
|
1075
|
-
}
|
|
1076
|
-
>((screen, name, description) =>
|
|
1077
|
-
this.userInputAsked(screen, name, description, this.socket)
|
|
1078
|
-
);
|
|
1079
|
-
this.eventEmitter.emit(
|
|
1080
|
-
'check-for-updates',
|
|
1081
|
-
message.args,
|
|
1082
|
-
checkForUpdatesEvent
|
|
1083
|
-
);
|
|
1084
|
-
const checkForUpdatesResult =
|
|
1085
|
-
await this.waitForEventToRespond(checkForUpdatesEvent);
|
|
1086
|
-
this.respondToMessage(
|
|
1087
|
-
message.id!!,
|
|
1088
|
-
checkForUpdatesResult.data,
|
|
1089
|
-
checkForUpdatesEvent
|
|
1120
|
+
await this.handleEventWithResponse<
|
|
1121
|
+
{ available: true; version: string } | { available: false }
|
|
1122
|
+
>(message, (event) =>
|
|
1123
|
+
this.eventEmitter.emit('check-for-updates', message.args, event)
|
|
1090
1124
|
);
|
|
1091
1125
|
break;
|
|
1092
1126
|
case 'request-dl':
|
|
@@ -1131,10 +1165,10 @@ class OGIAddonWSListener {
|
|
|
1131
1165
|
);
|
|
1132
1166
|
break;
|
|
1133
1167
|
case 'catalog':
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1168
|
+
await this.handleEventWithResponseNoInput<CatalogResponse>(
|
|
1169
|
+
message,
|
|
1170
|
+
(event) => this.eventEmitter.emit('catalog', event)
|
|
1171
|
+
);
|
|
1138
1172
|
break;
|
|
1139
1173
|
case 'task-run': {
|
|
1140
1174
|
let taskRunEvent = new EventResponse<void>(
|
|
@@ -1202,6 +1236,11 @@ class OGIAddonWSListener {
|
|
|
1202
1236
|
this.respondToMessage(message.id!!, taskRunResult.data, taskRunEvent);
|
|
1203
1237
|
break;
|
|
1204
1238
|
}
|
|
1239
|
+
case 'launch-app':
|
|
1240
|
+
await this.handleEventWithResponse<void>(message, (event) =>
|
|
1241
|
+
this.eventEmitter.emit('launch-app', message.args, event)
|
|
1242
|
+
);
|
|
1243
|
+
break;
|
|
1205
1244
|
}
|
|
1206
1245
|
});
|
|
1207
1246
|
}
|
|
@@ -1234,6 +1273,47 @@ class OGIAddonWSListener {
|
|
|
1234
1273
|
});
|
|
1235
1274
|
}
|
|
1236
1275
|
|
|
1276
|
+
/**
|
|
1277
|
+
* Common flow for events that use EventResponse with userInputAsked: create event, emit via callback, wait, respond.
|
|
1278
|
+
* If options.requireListener is set and that event has no listeners, responds with options.noListenerError and returns.
|
|
1279
|
+
*/
|
|
1280
|
+
private async handleEventWithResponse<T>(
|
|
1281
|
+
message: WebsocketMessageServer,
|
|
1282
|
+
emit: (event: EventResponse<T>) => void,
|
|
1283
|
+
options?: { requireListener: string; noListenerError: string }
|
|
1284
|
+
): Promise<void> {
|
|
1285
|
+
const event = new EventResponse<T>((screen, name, description) =>
|
|
1286
|
+
this.userInputAsked(screen, name, description, this.socket)
|
|
1287
|
+
);
|
|
1288
|
+
if (
|
|
1289
|
+
options &&
|
|
1290
|
+
this.eventEmitter.listenerCount(options.requireListener) === 0
|
|
1291
|
+
) {
|
|
1292
|
+
this.respondToMessage(
|
|
1293
|
+
message.id!!,
|
|
1294
|
+
{ error: options.noListenerError },
|
|
1295
|
+
event
|
|
1296
|
+
);
|
|
1297
|
+
return;
|
|
1298
|
+
}
|
|
1299
|
+
emit(event);
|
|
1300
|
+
const result = await this.waitForEventToRespond(event);
|
|
1301
|
+
this.respondToMessage(message.id!!, result.data, event);
|
|
1302
|
+
}
|
|
1303
|
+
|
|
1304
|
+
/**
|
|
1305
|
+
* Same as handleEventWithResponse but for events that don't need userInputAsked (e.g. catalog).
|
|
1306
|
+
*/
|
|
1307
|
+
private async handleEventWithResponseNoInput<T>(
|
|
1308
|
+
message: WebsocketMessageServer,
|
|
1309
|
+
emit: (event: EventResponse<T>) => void
|
|
1310
|
+
): Promise<void> {
|
|
1311
|
+
const event = new EventResponse<T>();
|
|
1312
|
+
emit(event);
|
|
1313
|
+
const result = await this.waitForEventToRespond(event);
|
|
1314
|
+
this.respondToMessage(message.id!!, result.data, event);
|
|
1315
|
+
}
|
|
1316
|
+
|
|
1237
1317
|
public respondToMessage(
|
|
1238
1318
|
messageID: string,
|
|
1239
1319
|
response: any,
|