@oxyhq/core 2.4.0 → 3.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/dist/cjs/.tsbuildinfo +1 -1
- package/dist/cjs/OxyServices.js +1 -1
- package/dist/cjs/mixins/OxyServices.applications.js +212 -0
- package/dist/cjs/mixins/OxyServices.auth.js +4 -4
- package/dist/cjs/mixins/OxyServices.fedcm.js +52 -2
- package/dist/cjs/mixins/index.js +2 -2
- package/dist/cjs/utils/coldBoot.js +66 -17
- package/dist/esm/.tsbuildinfo +1 -1
- package/dist/esm/OxyServices.js +1 -1
- package/dist/esm/mixins/OxyServices.applications.js +209 -0
- package/dist/esm/mixins/OxyServices.auth.js +4 -4
- package/dist/esm/mixins/OxyServices.fedcm.js +52 -2
- package/dist/esm/mixins/index.js +2 -2
- package/dist/esm/utils/coldBoot.js +66 -17
- package/dist/types/.tsbuildinfo +1 -1
- package/dist/types/OxyServices.d.ts +1 -1
- package/dist/types/index.d.ts +1 -0
- package/dist/types/mixins/OxyServices.applications.d.ts +317 -0
- package/dist/types/mixins/OxyServices.auth.d.ts +4 -4
- package/dist/types/mixins/OxyServices.fedcm.d.ts +1 -0
- package/dist/types/mixins/OxyServices.utility.d.ts +1 -1
- package/dist/types/mixins/index.d.ts +2 -2
- package/dist/types/utils/coldBoot.d.ts +26 -0
- package/package.json +1 -1
- package/src/OxyServices.ts +1 -1
- package/src/index.ts +29 -0
- package/src/mixins/OxyServices.applications.ts +511 -0
- package/src/mixins/OxyServices.auth.ts +4 -4
- package/src/mixins/OxyServices.fedcm.ts +56 -2
- package/src/mixins/OxyServices.utility.ts +1 -1
- package/src/mixins/__tests__/fedcm.test.ts +52 -0
- package/src/mixins/index.ts +3 -3
- package/src/utils/__tests__/coldBoot.test.ts +150 -0
- package/src/utils/coldBoot.ts +96 -17
- package/src/mixins/OxyServices.developer.ts +0 -114
package/src/mixins/index.ts
CHANGED
|
@@ -17,7 +17,7 @@ import { OxyServicesLanguageMixin } from './OxyServices.language';
|
|
|
17
17
|
import { OxyServicesPaymentMixin } from './OxyServices.payment';
|
|
18
18
|
import { OxyServicesKarmaMixin } from './OxyServices.karma';
|
|
19
19
|
import { OxyServicesAssetsMixin } from './OxyServices.assets';
|
|
20
|
-
import {
|
|
20
|
+
import { OxyServicesApplicationsMixin } from './OxyServices.applications';
|
|
21
21
|
import { OxyServicesLocationMixin } from './OxyServices.location';
|
|
22
22
|
import { OxyServicesAnalyticsMixin } from './OxyServices.analytics';
|
|
23
23
|
import { OxyServicesDevicesMixin } from './OxyServices.devices';
|
|
@@ -50,7 +50,7 @@ type AllMixinInstances =
|
|
|
50
50
|
& InstanceType<ReturnType<typeof OxyServicesPaymentMixin<typeof OxyServicesBase>>>
|
|
51
51
|
& InstanceType<ReturnType<typeof OxyServicesKarmaMixin<typeof OxyServicesBase>>>
|
|
52
52
|
& InstanceType<ReturnType<typeof OxyServicesAssetsMixin<typeof OxyServicesBase>>>
|
|
53
|
-
& InstanceType<ReturnType<typeof
|
|
53
|
+
& InstanceType<ReturnType<typeof OxyServicesApplicationsMixin<typeof OxyServicesBase>>>
|
|
54
54
|
& InstanceType<ReturnType<typeof OxyServicesLocationMixin<typeof OxyServicesBase>>>
|
|
55
55
|
& InstanceType<ReturnType<typeof OxyServicesAnalyticsMixin<typeof OxyServicesBase>>>
|
|
56
56
|
& InstanceType<ReturnType<typeof OxyServicesDevicesMixin<typeof OxyServicesBase>>>
|
|
@@ -114,7 +114,7 @@ const MIXIN_PIPELINE: MixinFunction[] = [
|
|
|
114
114
|
OxyServicesPaymentMixin,
|
|
115
115
|
OxyServicesKarmaMixin,
|
|
116
116
|
OxyServicesAssetsMixin,
|
|
117
|
-
|
|
117
|
+
OxyServicesApplicationsMixin,
|
|
118
118
|
OxyServicesLocationMixin,
|
|
119
119
|
OxyServicesAnalyticsMixin,
|
|
120
120
|
OxyServicesDevicesMixin,
|
|
@@ -223,4 +223,154 @@ describe('runColdBoot', () => {
|
|
|
223
223
|
session: { userId: 'u-ok' },
|
|
224
224
|
});
|
|
225
225
|
});
|
|
226
|
+
|
|
227
|
+
describe('overall deadline (defense-in-depth)', () => {
|
|
228
|
+
beforeEach(() => {
|
|
229
|
+
jest.useFakeTimers();
|
|
230
|
+
});
|
|
231
|
+
afterEach(() => {
|
|
232
|
+
jest.runOnlyPendingTimers();
|
|
233
|
+
jest.useRealTimers();
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Reproduces the production hang: a step whose `run()` promise NEVER
|
|
238
|
+
* settles (the FedCM-silent `navigator.credentials.get` that ignored its
|
|
239
|
+
* abort signal). WITHOUT a deadline the whole `runColdBoot` promise hangs
|
|
240
|
+
* forever and the terminal step never runs.
|
|
241
|
+
*/
|
|
242
|
+
it('hangs forever when a step never settles and no deadline is set', async () => {
|
|
243
|
+
const terminalRan = jest.fn();
|
|
244
|
+
let settled = false;
|
|
245
|
+
|
|
246
|
+
const outcomePromise = runColdBoot<TestSession>({
|
|
247
|
+
steps: [
|
|
248
|
+
{
|
|
249
|
+
id: 'never-settles',
|
|
250
|
+
// Never resolves or rejects — models the hung FedCM credential get.
|
|
251
|
+
run: () => new Promise<ColdBootStepResult<TestSession>>(() => {}),
|
|
252
|
+
},
|
|
253
|
+
{
|
|
254
|
+
id: 'terminal',
|
|
255
|
+
run: async (): Promise<ColdBootStepResult<TestSession>> => {
|
|
256
|
+
terminalRan();
|
|
257
|
+
return { kind: 'skip' };
|
|
258
|
+
},
|
|
259
|
+
},
|
|
260
|
+
],
|
|
261
|
+
}).then((o) => {
|
|
262
|
+
settled = true;
|
|
263
|
+
return o;
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
// Advance well past any reasonable budget; nothing can unblock it.
|
|
267
|
+
await jest.advanceTimersByTimeAsync(120000);
|
|
268
|
+
|
|
269
|
+
expect(settled).toBe(false);
|
|
270
|
+
expect(terminalRan).not.toHaveBeenCalled();
|
|
271
|
+
|
|
272
|
+
// Avoid a dangling unhandled promise in the test runner.
|
|
273
|
+
void outcomePromise;
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* With `overallDeadlineMs` set, the non-settling step is abandoned at the
|
|
278
|
+
* deadline, the runner CONTINUES to the terminal step (so the cross-domain
|
|
279
|
+
* `/sso` bounce equivalent still fires), and the whole boot settles to
|
|
280
|
+
* `unauthenticated` within the bounded budget.
|
|
281
|
+
*/
|
|
282
|
+
it('abandons a non-settling step at the deadline and still runs the terminal step', async () => {
|
|
283
|
+
const terminalRan = jest.fn();
|
|
284
|
+
const onStepDeadline = jest.fn();
|
|
285
|
+
|
|
286
|
+
const outcomePromise = runColdBoot<TestSession>({
|
|
287
|
+
overallDeadlineMs: 5000,
|
|
288
|
+
onStepDeadline,
|
|
289
|
+
steps: [
|
|
290
|
+
{
|
|
291
|
+
id: 'never-settles',
|
|
292
|
+
run: () => new Promise<ColdBootStepResult<TestSession>>(() => {}),
|
|
293
|
+
},
|
|
294
|
+
{
|
|
295
|
+
id: 'terminal',
|
|
296
|
+
run: async (): Promise<ColdBootStepResult<TestSession>> => {
|
|
297
|
+
terminalRan();
|
|
298
|
+
return { kind: 'skip' };
|
|
299
|
+
},
|
|
300
|
+
},
|
|
301
|
+
],
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
await jest.advanceTimersByTimeAsync(5000);
|
|
305
|
+
const outcome = await outcomePromise;
|
|
306
|
+
|
|
307
|
+
expect(onStepDeadline).toHaveBeenCalledWith('never-settles');
|
|
308
|
+
expect(terminalRan).toHaveBeenCalledTimes(1);
|
|
309
|
+
expect(outcome).toEqual({ kind: 'unauthenticated' });
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* The terminal step's synchronous side effect (the real `sso-bounce`
|
|
314
|
+
* navigates BEFORE its first await) must still execute when the deadline
|
|
315
|
+
* trips on an earlier step — the cross-domain fallback is preserved.
|
|
316
|
+
*/
|
|
317
|
+
it('lets the terminal step fire its synchronous side effect after the deadline trips', async () => {
|
|
318
|
+
const bounced = jest.fn();
|
|
319
|
+
|
|
320
|
+
const outcomePromise = runColdBoot<TestSession>({
|
|
321
|
+
overallDeadlineMs: 3000,
|
|
322
|
+
steps: [
|
|
323
|
+
{
|
|
324
|
+
id: 'never-settles',
|
|
325
|
+
run: () => new Promise<ColdBootStepResult<TestSession>>(() => {}),
|
|
326
|
+
},
|
|
327
|
+
{
|
|
328
|
+
id: 'sso-bounce',
|
|
329
|
+
run: async (): Promise<ColdBootStepResult<TestSession>> => {
|
|
330
|
+
// Synchronous navigation side effect, exactly like the real bounce.
|
|
331
|
+
bounced();
|
|
332
|
+
return { kind: 'skip' };
|
|
333
|
+
},
|
|
334
|
+
},
|
|
335
|
+
],
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
await jest.advanceTimersByTimeAsync(3000);
|
|
339
|
+
await outcomePromise;
|
|
340
|
+
|
|
341
|
+
expect(bounced).toHaveBeenCalledTimes(1);
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* A healthy step that settles BEFORE the deadline still wins and
|
|
346
|
+
* short-circuits — the deadline never alters the happy path.
|
|
347
|
+
*/
|
|
348
|
+
it('a step that settles before the deadline wins and short-circuits', async () => {
|
|
349
|
+
const laterRan = jest.fn();
|
|
350
|
+
|
|
351
|
+
const outcomePromise = runColdBoot<TestSession>({
|
|
352
|
+
overallDeadlineMs: 10000,
|
|
353
|
+
steps: [
|
|
354
|
+
{
|
|
355
|
+
id: 'fast-winner',
|
|
356
|
+
run: async (): Promise<ColdBootStepResult<TestSession>> => {
|
|
357
|
+
await Promise.resolve();
|
|
358
|
+
return { kind: 'session', session: { userId: 'u-fast' } };
|
|
359
|
+
},
|
|
360
|
+
},
|
|
361
|
+
sessionStep('later', 'u-later', laterRan),
|
|
362
|
+
],
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
await jest.advanceTimersByTimeAsync(0);
|
|
366
|
+
const outcome = await outcomePromise;
|
|
367
|
+
|
|
368
|
+
expect(outcome).toEqual({
|
|
369
|
+
kind: 'session',
|
|
370
|
+
via: 'fast-winner',
|
|
371
|
+
session: { userId: 'u-fast' },
|
|
372
|
+
});
|
|
373
|
+
expect(laterRan).not.toHaveBeenCalled();
|
|
374
|
+
});
|
|
375
|
+
});
|
|
226
376
|
});
|
package/src/utils/coldBoot.ts
CHANGED
|
@@ -73,6 +73,16 @@ export type ColdBootOutcome<S> =
|
|
|
73
73
|
| { readonly kind: 'session'; readonly via: string; readonly session: S }
|
|
74
74
|
| { readonly kind: 'unauthenticated' };
|
|
75
75
|
|
|
76
|
+
/**
|
|
77
|
+
* The unique sentinel a step's `run()` resolves to (via the internal race)
|
|
78
|
+
* when the overall cold-boot deadline expires before that step settled. It is
|
|
79
|
+
* NOT a {@link ColdBootStepResult} — the runner detects it by identity and
|
|
80
|
+
* treats it as "this step did not settle in time; move on".
|
|
81
|
+
*
|
|
82
|
+
* @internal
|
|
83
|
+
*/
|
|
84
|
+
const DEADLINE_EXPIRED: unique symbol = Symbol('coldBoot.deadlineExpired');
|
|
85
|
+
|
|
76
86
|
/**
|
|
77
87
|
* Options for {@link runColdBoot}.
|
|
78
88
|
*/
|
|
@@ -85,6 +95,32 @@ export interface RunColdBootOptions<S> {
|
|
|
85
95
|
* the runner does not guard against an observer that itself throws.
|
|
86
96
|
*/
|
|
87
97
|
readonly onStepError?: (id: string, error: unknown) => void;
|
|
98
|
+
/**
|
|
99
|
+
* Optional HARD overall deadline (ms) for the entire ordered step loop —
|
|
100
|
+
* defense-in-depth so a single non-settling step can NEVER hang the whole
|
|
101
|
+
* cold boot forever.
|
|
102
|
+
*
|
|
103
|
+
* Each step's `run()` is raced against the SHARED remaining time. If a step
|
|
104
|
+
* fails to settle before the deadline, the runner abandons the await for that
|
|
105
|
+
* step (reporting it via `onStepDeadline`) and CONTINUES to the next step,
|
|
106
|
+
* each now racing against an already-expired deadline. This is deliberate:
|
|
107
|
+
* the runner keeps iterating so the TERMINAL step (e.g. the `/sso` bounce,
|
|
108
|
+
* whose `run()` performs its side effect synchronously before its first
|
|
109
|
+
* `await`) still gets to fire. A step that has nothing to contribute after
|
|
110
|
+
* the deadline simply doesn't settle and is skipped in turn.
|
|
111
|
+
*
|
|
112
|
+
* Per-step timeouts inside `run()` remain the first line of defense and
|
|
113
|
+
* should keep every step well under this budget on a healthy load; this only
|
|
114
|
+
* trips when one of them regresses (the production FedCM-silent hang). When
|
|
115
|
+
* omitted there is NO overall deadline (unchanged legacy behaviour).
|
|
116
|
+
*/
|
|
117
|
+
readonly overallDeadlineMs?: number;
|
|
118
|
+
/**
|
|
119
|
+
* Optional observer invoked once per step that was abandoned because the
|
|
120
|
+
* overall deadline expired before it settled. Receives the step `id`. Must
|
|
121
|
+
* not throw.
|
|
122
|
+
*/
|
|
123
|
+
readonly onStepDeadline?: (id: string) => void;
|
|
88
124
|
}
|
|
89
125
|
|
|
90
126
|
/**
|
|
@@ -105,32 +141,75 @@ export interface RunColdBootOptions<S> {
|
|
|
105
141
|
export async function runColdBoot<S>(
|
|
106
142
|
options: RunColdBootOptions<S>
|
|
107
143
|
): Promise<ColdBootOutcome<S>> {
|
|
108
|
-
const { steps, onStepError } = options;
|
|
144
|
+
const { steps, onStepError, overallDeadlineMs, onStepDeadline } = options;
|
|
109
145
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
146
|
+
// Arm the optional overall deadline. The budget is SHARED across the whole
|
|
147
|
+
// loop (not reset per step): a single timer resolves a reusable
|
|
148
|
+
// `DEADLINE_EXPIRED` sentinel that every per-step race can observe. Once it
|
|
149
|
+
// fires, later steps race against an already-resolved promise and so never
|
|
150
|
+
// block, yet the loop keeps iterating so the terminal step still fires.
|
|
151
|
+
const deadlineMs =
|
|
152
|
+
typeof overallDeadlineMs === 'number' &&
|
|
153
|
+
Number.isFinite(overallDeadlineMs) &&
|
|
154
|
+
overallDeadlineMs > 0
|
|
155
|
+
? overallDeadlineMs
|
|
156
|
+
: null;
|
|
157
|
+
|
|
158
|
+
let deadlineTimer: ReturnType<typeof setTimeout> | undefined;
|
|
159
|
+
let deadlinePromise: Promise<typeof DEADLINE_EXPIRED> | undefined;
|
|
160
|
+
if (deadlineMs !== null) {
|
|
161
|
+
deadlinePromise = new Promise<typeof DEADLINE_EXPIRED>((resolve) => {
|
|
162
|
+
deadlineTimer = setTimeout(() => resolve(DEADLINE_EXPIRED), deadlineMs);
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
try {
|
|
167
|
+
for (const step of steps) {
|
|
168
|
+
if (step.enabled) {
|
|
169
|
+
let isEnabled: boolean;
|
|
170
|
+
try {
|
|
171
|
+
isEnabled = step.enabled();
|
|
172
|
+
} catch (error) {
|
|
173
|
+
onStepError?.(step.id, error);
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
176
|
+
if (!isEnabled) continue;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
let result: ColdBootStepResult<S> | typeof DEADLINE_EXPIRED;
|
|
113
180
|
try {
|
|
114
|
-
|
|
181
|
+
// Without a deadline: legacy behaviour — await the step directly.
|
|
182
|
+
// With a deadline: race the step against the shared deadline. The
|
|
183
|
+
// step's `run()` still STARTS synchronously up to its first `await`
|
|
184
|
+
// (so a terminal step's synchronous navigation side effect always
|
|
185
|
+
// executes), but a non-settling step can no longer block the loop —
|
|
186
|
+
// the race resolves with the sentinel and we move on.
|
|
187
|
+
result = deadlinePromise
|
|
188
|
+
? await Promise.race([step.run(), deadlinePromise])
|
|
189
|
+
: await step.run();
|
|
115
190
|
} catch (error) {
|
|
116
191
|
onStepError?.(step.id, error);
|
|
117
192
|
continue;
|
|
118
193
|
}
|
|
119
|
-
if (!isEnabled) continue;
|
|
120
|
-
}
|
|
121
194
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
195
|
+
if (result === DEADLINE_EXPIRED) {
|
|
196
|
+
// The deadline tripped before this step settled. Abandon the await and
|
|
197
|
+
// continue: subsequent steps race against the already-resolved deadline
|
|
198
|
+
// (so they cannot block), which lets a terminal side-effect step still
|
|
199
|
+
// run while guaranteeing the loop terminates promptly.
|
|
200
|
+
onStepDeadline?.(step.id);
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
if (result.kind === 'session') {
|
|
205
|
+
return { kind: 'session', via: step.id, session: result.session };
|
|
206
|
+
}
|
|
128
207
|
}
|
|
129
208
|
|
|
130
|
-
|
|
131
|
-
|
|
209
|
+
return { kind: 'unauthenticated' };
|
|
210
|
+
} finally {
|
|
211
|
+
if (deadlineTimer !== undefined) {
|
|
212
|
+
clearTimeout(deadlineTimer);
|
|
132
213
|
}
|
|
133
214
|
}
|
|
134
|
-
|
|
135
|
-
return { kind: 'unauthenticated' };
|
|
136
215
|
}
|
|
@@ -1,114 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Developer API Methods Mixin
|
|
3
|
-
*
|
|
4
|
-
* Provides methods for managing developer applications and API keys
|
|
5
|
-
*/
|
|
6
|
-
import type { OxyServicesBase } from '../OxyServices.base';
|
|
7
|
-
import { CACHE_TIMES } from './mixinHelpers';
|
|
8
|
-
|
|
9
|
-
export function OxyServicesDeveloperMixin<T extends typeof OxyServicesBase>(Base: T) {
|
|
10
|
-
return class extends Base {
|
|
11
|
-
constructor(...args: any[]) {
|
|
12
|
-
super(...(args as [any]));
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Get developer apps for the current user
|
|
17
|
-
* @returns Array of developer apps
|
|
18
|
-
*/
|
|
19
|
-
async getDeveloperApps(): Promise<any[]> {
|
|
20
|
-
try {
|
|
21
|
-
const res = await this.makeRequest<{ apps?: any[] }>('GET', '/developer/apps', undefined, {
|
|
22
|
-
cache: true,
|
|
23
|
-
cacheTTL: CACHE_TIMES.MEDIUM,
|
|
24
|
-
});
|
|
25
|
-
return res.apps || [];
|
|
26
|
-
} catch (error) {
|
|
27
|
-
throw this.handleError(error);
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Create a new developer app
|
|
33
|
-
* @param data - Developer app configuration
|
|
34
|
-
* @returns Created developer app
|
|
35
|
-
*/
|
|
36
|
-
async createDeveloperApp(data: {
|
|
37
|
-
name: string;
|
|
38
|
-
description?: string;
|
|
39
|
-
webhookUrl: string;
|
|
40
|
-
devWebhookUrl?: string;
|
|
41
|
-
scopes?: string[];
|
|
42
|
-
}): Promise<any> {
|
|
43
|
-
try {
|
|
44
|
-
const res = await this.makeRequest<{ app: any }>('POST', '/developer/apps', data, { cache: false });
|
|
45
|
-
return res.app;
|
|
46
|
-
} catch (error) {
|
|
47
|
-
throw this.handleError(error);
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* Get a specific developer app
|
|
53
|
-
*/
|
|
54
|
-
async getDeveloperApp(appId: string): Promise<any> {
|
|
55
|
-
try {
|
|
56
|
-
const res = await this.makeRequest<{ app: any }>('GET', `/developer/apps/${appId}`, undefined, {
|
|
57
|
-
cache: true,
|
|
58
|
-
cacheTTL: CACHE_TIMES.LONG,
|
|
59
|
-
});
|
|
60
|
-
return res.app;
|
|
61
|
-
} catch (error) {
|
|
62
|
-
throw this.handleError(error);
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* Update a developer app
|
|
68
|
-
* @param appId - The developer app ID
|
|
69
|
-
* @param data - Updated app configuration
|
|
70
|
-
* @returns Updated developer app
|
|
71
|
-
*/
|
|
72
|
-
async updateDeveloperApp(appId: string, data: {
|
|
73
|
-
name?: string;
|
|
74
|
-
description?: string;
|
|
75
|
-
webhookUrl?: string;
|
|
76
|
-
devWebhookUrl?: string;
|
|
77
|
-
scopes?: string[];
|
|
78
|
-
}): Promise<any> {
|
|
79
|
-
try {
|
|
80
|
-
const res = await this.makeRequest<{ app: any }>('PATCH', `/developer/apps/${appId}`, data, { cache: false });
|
|
81
|
-
return res.app;
|
|
82
|
-
} catch (error) {
|
|
83
|
-
throw this.handleError(error);
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* Regenerate API secret for a developer app
|
|
89
|
-
* @param appId - The developer app ID
|
|
90
|
-
* @returns App with new secret
|
|
91
|
-
*/
|
|
92
|
-
async regenerateDeveloperAppSecret(appId: string): Promise<any> {
|
|
93
|
-
try {
|
|
94
|
-
return await this.makeRequest('POST', `/developer/apps/${appId}/regenerate-secret`, undefined, { cache: false });
|
|
95
|
-
} catch (error) {
|
|
96
|
-
throw this.handleError(error);
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
/**
|
|
101
|
-
* Delete a developer app
|
|
102
|
-
* @param appId - The developer app ID
|
|
103
|
-
* @returns Deletion result
|
|
104
|
-
*/
|
|
105
|
-
async deleteDeveloperApp(appId: string): Promise<any> {
|
|
106
|
-
try {
|
|
107
|
-
return await this.makeRequest('DELETE', `/developer/apps/${appId}`, undefined, { cache: false });
|
|
108
|
-
} catch (error) {
|
|
109
|
-
throw this.handleError(error);
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
};
|
|
113
|
-
}
|
|
114
|
-
|