ogi-addon 0.5.0 → 1.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/EventResponse.cjs +19 -0
- package/build/EventResponse.cjs.map +1 -1
- package/build/EventResponse.d.cts +20 -1
- package/build/EventResponse.d.ts +20 -1
- package/build/EventResponse.js +19 -0
- package/build/EventResponse.js.map +1 -1
- package/build/SearchEngine.cjs.map +1 -1
- package/build/SearchEngine.d.cts +1 -1
- package/build/SearchEngine.d.ts +1 -1
- package/build/config/Configuration.cjs +71 -0
- package/build/config/Configuration.cjs.map +1 -1
- package/build/config/Configuration.js +71 -0
- package/build/config/Configuration.js.map +1 -1
- package/build/config/ConfigurationBuilder.cjs +71 -0
- package/build/config/ConfigurationBuilder.cjs.map +1 -1
- package/build/config/ConfigurationBuilder.d.cts +71 -0
- package/build/config/ConfigurationBuilder.d.ts +71 -0
- package/build/config/ConfigurationBuilder.js +71 -0
- package/build/config/ConfigurationBuilder.js.map +1 -1
- package/build/main.cjs +171 -5
- package/build/main.cjs.map +1 -1
- package/build/main.d.cts +76 -3
- package/build/main.d.ts +76 -3
- package/build/main.js +169 -4
- package/build/main.js.map +1 -1
- package/package.json +1 -1
- package/schema.json +31 -0
- package/src/EventResponse.ts +20 -1
- package/src/SearchEngine.ts +1 -1
- package/src/config/ConfigurationBuilder.ts +73 -1
- package/src/main.ts +97 -7
- package/tsconfig.json +2 -1
|
@@ -24,6 +24,12 @@ export function isBooleanOption(option: ConfigurationOption): option is BooleanO
|
|
|
24
24
|
|
|
25
25
|
export class ConfigurationBuilder {
|
|
26
26
|
private options: ConfigurationOption[] = [];
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Add a number option to the configuration builder and return the builder for chaining. You must provide a name, display name, and description for the option.
|
|
30
|
+
* @param option { (option: NumberOption) => NumberOption }
|
|
31
|
+
* @returns
|
|
32
|
+
*/
|
|
27
33
|
public addNumberOption(option: (option: NumberOption) => NumberOption): ConfigurationBuilder {
|
|
28
34
|
let newOption = new NumberOption();
|
|
29
35
|
newOption = option(newOption);
|
|
@@ -31,6 +37,10 @@ export class ConfigurationBuilder {
|
|
|
31
37
|
return this;
|
|
32
38
|
}
|
|
33
39
|
|
|
40
|
+
/**
|
|
41
|
+
* Add a string option to the configuration builder and return the builder for chaining. You must provide a name, display name, and description for the option.
|
|
42
|
+
* @param option { (option: StringOption) => StringOption }
|
|
43
|
+
*/
|
|
34
44
|
public addStringOption(option: (option: StringOption) => StringOption) {
|
|
35
45
|
let newOption = new StringOption();
|
|
36
46
|
newOption = option(newOption);
|
|
@@ -38,6 +48,10 @@ export class ConfigurationBuilder {
|
|
|
38
48
|
return this;
|
|
39
49
|
}
|
|
40
50
|
|
|
51
|
+
/**
|
|
52
|
+
* Add a boolean option to the configuration builder and return the builder for chaining. You must provide a name, display name, and description for the option.
|
|
53
|
+
* @param option { (option: BooleanOption) => BooleanOption }
|
|
54
|
+
*/
|
|
41
55
|
public addBooleanOption(option: (option: BooleanOption) => BooleanOption) {
|
|
42
56
|
let newOption = new BooleanOption();
|
|
43
57
|
newOption = option(newOption);
|
|
@@ -74,22 +88,39 @@ export class ConfigurationOption {
|
|
|
74
88
|
public description: string = '';
|
|
75
89
|
public type: ConfigurationOptionType = 'unset'
|
|
76
90
|
|
|
91
|
+
/**
|
|
92
|
+
* Set the name of the option. **REQUIRED**
|
|
93
|
+
* @param name {string} The name of the option. This is used to reference the option in the configuration file.
|
|
94
|
+
*/
|
|
77
95
|
setName(name: string) {
|
|
78
96
|
this.name = name;
|
|
79
97
|
return this;
|
|
80
98
|
}
|
|
81
99
|
|
|
100
|
+
/**
|
|
101
|
+
* Set the display name of the option. This is used to show the user a human readable version of what the option is. **REQUIRED**
|
|
102
|
+
* @param displayName {string} The display name of the option.
|
|
103
|
+
* @returns
|
|
104
|
+
*/
|
|
82
105
|
setDisplayName(displayName: string) {
|
|
83
106
|
this.displayName = displayName;
|
|
84
107
|
return this;
|
|
85
108
|
}
|
|
86
109
|
|
|
110
|
+
/**
|
|
111
|
+
* Set the description of the option. This is to show the user a brief description of what this option does. **REQUIRED**
|
|
112
|
+
* @param description {string} The description of the option.
|
|
113
|
+
* @returns
|
|
114
|
+
*/
|
|
87
115
|
setDescription(description: string) {
|
|
88
116
|
this.description = description;
|
|
89
117
|
return this;
|
|
90
118
|
}
|
|
91
119
|
|
|
92
|
-
|
|
120
|
+
/**
|
|
121
|
+
* Validation code for the option. This is called when the user provides input to the option. If the validation fails, the user will be prompted to provide input again.
|
|
122
|
+
* @param input {unknown} The input to validate
|
|
123
|
+
*/
|
|
93
124
|
validate(input: unknown): [ boolean, string ] {
|
|
94
125
|
throw new Error('Validation code not implemented. Value: ' + input)
|
|
95
126
|
};
|
|
@@ -103,26 +134,46 @@ export class StringOption extends ConfigurationOption {
|
|
|
103
134
|
public inputType: 'text' | 'file' | 'password' | 'folder' = 'text';
|
|
104
135
|
public type: ConfigurationOptionType = 'string'
|
|
105
136
|
|
|
137
|
+
/**
|
|
138
|
+
* Set the allowed values for the string. If the array is empty, any value is allowed. When provided, the client will act like this option is a dropdown.
|
|
139
|
+
* @param allowedValues {string[]} An array of allowed values for the string. If the array is empty, any value is allowed.
|
|
140
|
+
*/
|
|
106
141
|
setAllowedValues(allowedValues: string[]): this {
|
|
107
142
|
this.allowedValues = allowedValues;
|
|
108
143
|
return this;
|
|
109
144
|
}
|
|
110
145
|
|
|
146
|
+
/**
|
|
147
|
+
* Set the default value for the string. This value will be used if the user does not provide a value. **HIGHLY RECOMMENDED**
|
|
148
|
+
* @param defaultValue {string} The default value for the string.
|
|
149
|
+
*/
|
|
111
150
|
setDefaultValue(defaultValue: string): this {
|
|
112
151
|
this.defaultValue = defaultValue;
|
|
113
152
|
return this;
|
|
114
153
|
}
|
|
115
154
|
|
|
155
|
+
/**
|
|
156
|
+
* Set the minimum text length for the string. If the user provides a string that is less than this value, the validation will fail.
|
|
157
|
+
* @param minTextLength {number} The minimum text length for the string.
|
|
158
|
+
*/
|
|
116
159
|
setMinTextLength(minTextLength: number): this {
|
|
117
160
|
this.minTextLength = minTextLength;
|
|
118
161
|
return this;
|
|
119
162
|
}
|
|
120
163
|
|
|
164
|
+
/**
|
|
165
|
+
* Set the maximum text length for the string. If the user provides a string that is greater than this value, the validation will fail.
|
|
166
|
+
* @param maxTextLength {number} The maximum text length for the string.
|
|
167
|
+
*/
|
|
121
168
|
setMaxTextLength(maxTextLength: number): this {
|
|
122
169
|
this.maxTextLength = maxTextLength;
|
|
123
170
|
return this;
|
|
124
171
|
}
|
|
125
172
|
|
|
173
|
+
/**
|
|
174
|
+
* Set the input type for the string. This will change how the client renders the input.
|
|
175
|
+
* @param inputType {'text' | 'file' | 'password' | 'folder'} The input type for the string.
|
|
176
|
+
*/
|
|
126
177
|
setInputType(inputType: 'text' | 'file' | 'password' | 'folder'): this {
|
|
127
178
|
this.inputType = inputType;
|
|
128
179
|
return this;
|
|
@@ -148,21 +199,38 @@ export class NumberOption extends ConfigurationOption {
|
|
|
148
199
|
public defaultValue: number = 0;
|
|
149
200
|
public type: ConfigurationOptionType = 'number'
|
|
150
201
|
public inputType: 'range' | 'number' = 'number';
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Set the minimum value for the number. If the user provides a number that is less than this value, the validation will fail.
|
|
205
|
+
* @param min {number} The minimum value for the number.
|
|
206
|
+
*/
|
|
151
207
|
setMin(min: number): this {
|
|
152
208
|
this.min = min;
|
|
153
209
|
return this;
|
|
154
210
|
}
|
|
155
211
|
|
|
212
|
+
/**
|
|
213
|
+
* Set the input type for the number. This will change how the client renders the input.
|
|
214
|
+
* @param type {'range' | 'number'} The input type for the number.
|
|
215
|
+
*/
|
|
156
216
|
setInputType(type: 'range' | 'number'): this {
|
|
157
217
|
this.inputType = type;
|
|
158
218
|
return this;
|
|
159
219
|
}
|
|
160
220
|
|
|
221
|
+
/**
|
|
222
|
+
* Set the maximum value for the number. If the user provides a number that is greater than this value, the validation will fail.
|
|
223
|
+
* @param max {number} The maximum value for the number.
|
|
224
|
+
*/
|
|
161
225
|
setMax(max: number): this {
|
|
162
226
|
this.max = max;
|
|
163
227
|
return this
|
|
164
228
|
}
|
|
165
229
|
|
|
230
|
+
/**
|
|
231
|
+
* Set the default value for the number. This value will be used if the user does not provide a value. **HIGHLY RECOMMENDED**
|
|
232
|
+
* @param defaultValue {number} The default value for the number.
|
|
233
|
+
*/
|
|
166
234
|
setDefaultValue(defaultValue: number): this {
|
|
167
235
|
this.defaultValue = defaultValue;
|
|
168
236
|
return this;
|
|
@@ -184,6 +252,10 @@ export class BooleanOption extends ConfigurationOption {
|
|
|
184
252
|
public type: ConfigurationOptionType = 'boolean'
|
|
185
253
|
public defaultValue: boolean = false;
|
|
186
254
|
|
|
255
|
+
/**
|
|
256
|
+
* Set the default value for the boolean. This value will be used if the user does not provide a value. **HIGHLY RECOMMENDED**
|
|
257
|
+
* @param defaultValue {boolean} The default value for the boolean.
|
|
258
|
+
*/
|
|
187
259
|
setDefaultValue(defaultValue: boolean): this {
|
|
188
260
|
this.defaultValue = defaultValue;
|
|
189
261
|
return this;
|
package/src/main.ts
CHANGED
|
@@ -5,13 +5,14 @@ import { Configuration } from './config/Configuration';
|
|
|
5
5
|
import EventResponse from './EventResponse';
|
|
6
6
|
import { SearchResult } from './SearchEngine';
|
|
7
7
|
|
|
8
|
-
export type OGIAddonEvent = 'connect' | 'disconnect' | 'configure' | 'authenticate' | 'search' | 'setup' | 'library-search' | 'game-details';
|
|
8
|
+
export type OGIAddonEvent = 'connect' | 'disconnect' | 'configure' | 'authenticate' | 'search' | 'setup' | 'library-search' | 'game-details' | 'exit' | 'request-dl';
|
|
9
9
|
export type OGIAddonClientSentEvent = 'response' | 'authenticate' | 'configure' | 'defer-update' | 'notification' | 'input-asked';
|
|
10
10
|
|
|
11
|
-
export type OGIAddonServerSentEvent = 'authenticate' | 'configure' | 'config-update' | 'search' | 'setup' | 'response' | 'library-search' | 'game-details';
|
|
11
|
+
export type OGIAddonServerSentEvent = 'authenticate' | 'configure' | 'config-update' | 'search' | 'setup' | 'response' | 'library-search' | 'game-details' | 'request-dl';
|
|
12
12
|
export { ConfigurationBuilder, Configuration, EventResponse, SearchResult };
|
|
13
13
|
const defaultPort = 7654;
|
|
14
|
-
|
|
14
|
+
import pjson from '../package.json';
|
|
15
|
+
export const version = pjson.version;
|
|
15
16
|
|
|
16
17
|
export interface ClientSentEventTypes {
|
|
17
18
|
response: any;
|
|
@@ -32,12 +33,51 @@ export type BasicLibraryInfo = {
|
|
|
32
33
|
}
|
|
33
34
|
|
|
34
35
|
export interface EventListenerTypes {
|
|
36
|
+
/**
|
|
37
|
+
* This event is emitted when the addon connects to the OGI Addon Server. Addon does not need to resolve anything.
|
|
38
|
+
* @param socket
|
|
39
|
+
* @returns
|
|
40
|
+
*/
|
|
35
41
|
connect: (socket: ws) => void;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* 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.
|
|
45
|
+
* @param reason
|
|
46
|
+
* @returns
|
|
47
|
+
*/
|
|
36
48
|
disconnect: (reason: string) => void;
|
|
49
|
+
/**
|
|
50
|
+
* 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)
|
|
51
|
+
* @param config
|
|
52
|
+
* @returns
|
|
53
|
+
*/
|
|
37
54
|
configure: (config: ConfigurationBuilder) => ConfigurationBuilder;
|
|
55
|
+
/**
|
|
56
|
+
* This event is called when the client provides a response to any event. This should be treated as middleware.
|
|
57
|
+
* @param response
|
|
58
|
+
* @returns
|
|
59
|
+
*/
|
|
38
60
|
response: (response: any) => void;
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* This event is called when the client requests for the addon to authenticate itself. You don't need to provide any info.
|
|
64
|
+
* @param config
|
|
65
|
+
* @returns
|
|
66
|
+
*/
|
|
39
67
|
authenticate: (config: any) => void;
|
|
68
|
+
/**
|
|
69
|
+
* 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)
|
|
70
|
+
* @param query
|
|
71
|
+
* @param event
|
|
72
|
+
* @returns
|
|
73
|
+
*/
|
|
40
74
|
search: (query: { type: 'steamapp' | 'internal', text: string }, event: EventResponse<SearchResult[]>) => void;
|
|
75
|
+
/**
|
|
76
|
+
* 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)
|
|
77
|
+
* @param data
|
|
78
|
+
* @param event
|
|
79
|
+
* @returns
|
|
80
|
+
*/
|
|
41
81
|
setup: (
|
|
42
82
|
data: {
|
|
43
83
|
path: string,
|
|
@@ -52,8 +92,17 @@ export interface EventListenerTypes {
|
|
|
52
92
|
storefront: 'steam' | 'internal'
|
|
53
93
|
}, event: EventResponse<LibraryInfo>
|
|
54
94
|
) => void;
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* This event is emitted when the client requires for a search to be performed. Input is the search query.
|
|
98
|
+
* @param query
|
|
99
|
+
* @param event
|
|
100
|
+
* @returns
|
|
101
|
+
*/
|
|
55
102
|
'library-search': (query: string, event: EventResponse<BasicLibraryInfo[]>) => void;
|
|
56
103
|
'game-details': (appID: number, event: EventResponse<StoreData>) => void;
|
|
104
|
+
exit: () => void;
|
|
105
|
+
'request-dl': (appID: number, info: SearchResult, event: EventResponse<SearchResult>) => void;
|
|
57
106
|
}
|
|
58
107
|
|
|
59
108
|
export interface StoreData {
|
|
@@ -87,6 +136,22 @@ export interface OGIAddonConfiguration {
|
|
|
87
136
|
author: string;
|
|
88
137
|
repository: string;
|
|
89
138
|
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* 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.
|
|
142
|
+
* @example
|
|
143
|
+
* ```typescript
|
|
144
|
+
* const addon = new OGIAddon({
|
|
145
|
+
* name: 'Test Addon',
|
|
146
|
+
* id: 'test-addon',
|
|
147
|
+
* description: 'A test addon',
|
|
148
|
+
* version: '1.0.0',
|
|
149
|
+
* author: 'OGI Developers',
|
|
150
|
+
* repository: ''
|
|
151
|
+
* });
|
|
152
|
+
* ```
|
|
153
|
+
*
|
|
154
|
+
*/
|
|
90
155
|
export default class OGIAddon {
|
|
91
156
|
public eventEmitter = new events.EventEmitter();
|
|
92
157
|
public addonWSListener: OGIAddonWSListener;
|
|
@@ -98,6 +163,11 @@ export default class OGIAddon {
|
|
|
98
163
|
this.addonWSListener = new OGIAddonWSListener(this, this.eventEmitter);
|
|
99
164
|
}
|
|
100
165
|
|
|
166
|
+
/**
|
|
167
|
+
* Register an event listener for the addon. (See EventListenerTypes)
|
|
168
|
+
* @param event {OGIAddonEvent}
|
|
169
|
+
* @param listener {EventListenerTypes[OGIAddonEvent]}
|
|
170
|
+
*/
|
|
101
171
|
public on<T extends OGIAddonEvent>(event: T, listener: EventListenerTypes[T]) {
|
|
102
172
|
this.eventEmitter.on(event, listener);
|
|
103
173
|
}
|
|
@@ -106,11 +176,18 @@ export default class OGIAddon {
|
|
|
106
176
|
this.eventEmitter.emit(event, ...args);
|
|
107
177
|
}
|
|
108
178
|
|
|
179
|
+
/**
|
|
180
|
+
* Notify the client using a notification. Provide the type of notification, the message, and an ID.
|
|
181
|
+
* @param notification {Notification}
|
|
182
|
+
*/
|
|
109
183
|
public notify(notification: Notification) {
|
|
110
184
|
this.addonWSListener.send('notification', [ notification ]);
|
|
111
185
|
}
|
|
112
186
|
}
|
|
113
187
|
|
|
188
|
+
/**
|
|
189
|
+
* Library Info is the metadata for a library entry after setting up a game.
|
|
190
|
+
*/
|
|
114
191
|
export interface LibraryInfo {
|
|
115
192
|
name: string;
|
|
116
193
|
version: string;
|
|
@@ -182,7 +259,8 @@ class OGIAddonWSListener {
|
|
|
182
259
|
}
|
|
183
260
|
this.eventEmitter.emit('disconnect', reason);
|
|
184
261
|
console.log("Disconnected from OGI Addon Server")
|
|
185
|
-
console.
|
|
262
|
+
console.error(reason.toString())
|
|
263
|
+
this.eventEmitter.emit('exit');
|
|
186
264
|
this.socket.close();
|
|
187
265
|
});
|
|
188
266
|
|
|
@@ -224,7 +302,6 @@ class OGIAddonWSListener {
|
|
|
224
302
|
let searchResultEvent = new EventResponse<SearchResult[]>((screen, name, description) => this.userInputAsked(screen, name, description, this.socket));
|
|
225
303
|
this.eventEmitter.emit('search', message.args, searchResultEvent);
|
|
226
304
|
const searchResult = await this.waitForEventToRespond(searchResultEvent);
|
|
227
|
-
console.log(searchResult.data)
|
|
228
305
|
this.respondToMessage(message.id!!, searchResult.data);
|
|
229
306
|
break
|
|
230
307
|
case 'setup':
|
|
@@ -264,8 +341,21 @@ class OGIAddonWSListener {
|
|
|
264
341
|
const gameDetailsResult = await this.waitForEventToRespond(gameDetailsEvent);
|
|
265
342
|
this.respondToMessage(message.id!!, gameDetailsResult.data);
|
|
266
343
|
break
|
|
267
|
-
|
|
268
|
-
|
|
344
|
+
case 'request-dl':
|
|
345
|
+
let requestDLEvent = new EventResponse<SearchResult>((screen, name, description) => this.userInputAsked(screen, name, description, this.socket));
|
|
346
|
+
if (this.eventEmitter.listenerCount('request-dl') === 0) {
|
|
347
|
+
this.respondToMessage(message.id!!, { error: 'No event listener for request-dl' });
|
|
348
|
+
break;
|
|
349
|
+
}
|
|
350
|
+
this.eventEmitter.emit('request-dl', message.args.appID, message.args.info, requestDLEvent);
|
|
351
|
+
const requestDLResult = await this.waitForEventToRespond(requestDLEvent);
|
|
352
|
+
if (requestDLEvent.data === null || requestDLEvent.data?.downloadType === 'request') {
|
|
353
|
+
throw new Error('Request DL event did not return a valid result. Please ensure that the event does not resolve with another `request` download type.');
|
|
354
|
+
}
|
|
355
|
+
this.respondToMessage(message.id!!, requestDLResult.data);
|
|
356
|
+
break
|
|
357
|
+
}
|
|
358
|
+
});
|
|
269
359
|
}
|
|
270
360
|
|
|
271
361
|
private waitForEventToRespond<T>(event: EventResponse<T>): Promise<EventResponse<T>> {
|
package/tsconfig.json
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
"ESNext",
|
|
6
6
|
"DOM"
|
|
7
7
|
], // specifies which default set of type definitions to use ("DOM", "ES6", etc)
|
|
8
|
-
"removeComments":
|
|
8
|
+
"removeComments": false, // Strips all comments from TypeScript files when converting into JavaScript- you rarely read compiled code so this saves space
|
|
9
9
|
"target": "ESNext", // Target environment. Most modern browsers support ES6, but you may want to set it to newer or older. (defaults to ES3)
|
|
10
10
|
"declaration": true,
|
|
11
11
|
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
"baseUrl": "./", // Lets you set a base directory to resolve non-absolute module names.
|
|
14
14
|
"esModuleInterop": true, // fixes some issues TS originally had with the ES6 spec where TypeScript treats CommonJS/AMD/UMD modules similar to ES6 module
|
|
15
15
|
"moduleResolution": "node", // Pretty much always node for modern JS. Other option is "classic"
|
|
16
|
+
"resolveJsonModule": true,
|
|
16
17
|
"paths": {}, // A series of entries which re-map imports to lookup locations relative to the baseUrl
|
|
17
18
|
|
|
18
19
|
// Source Map
|