@lemoncloud/chatic-sockets-lib 0.4.0 → 0.4.1
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/client-socket-v2/auth-controller.d.ts +2 -2
- package/dist/client-socket-v2/auth-controller.js +10 -6
- package/dist/client-socket-v2/plans/channel-sync-plan.d.ts +1 -0
- package/dist/client-socket-v2/plans/channel-sync-plan.js +1 -0
- package/dist/client-socket-v2/plans/chat-sync-plan.d.ts +1 -0
- package/dist/client-socket-v2/plans/chat-sync-plan.js +1 -0
- package/dist/client-socket-v2/plans/device-sync-plan.d.ts +1 -0
- package/dist/client-socket-v2/plans/device-sync-plan.js +1 -0
- package/dist/client-socket-v2/plans/join-sync-plan.d.ts +1 -0
- package/dist/client-socket-v2/plans/join-sync-plan.js +1 -0
- package/dist/client-socket-v2/plans/place-sync-plan.d.ts +1 -0
- package/dist/client-socket-v2/plans/place-sync-plan.js +1 -0
- package/dist/client-socket-v2/plans/profile-sync-plan.d.ts +1 -0
- package/dist/client-socket-v2/plans/profile-sync-plan.js +1 -0
- package/dist/client-socket-v2/sync-scheduler.d.ts +5 -0
- package/dist/client-socket-v2/sync-scheduler.js +51 -11
- package/dist/client-socket-v2/types.d.ts +1 -0
- package/dist/lib/auth/contracts.d.ts +11 -0
- package/dist/lib/auth/types.d.ts +3 -1
- package/package.json +1 -1
|
@@ -82,13 +82,13 @@ export declare class AuthControllerImpl implements AuthController {
|
|
|
82
82
|
constructor(options: AuthControllerOptions);
|
|
83
83
|
get state(): AuthControllerState;
|
|
84
84
|
get token(): string;
|
|
85
|
-
/** Register token, authId, and sign callback. Idempotent; resumes when
|
|
85
|
+
/** Register token, authId, and sign callback. Idempotent; resumes auth when inactive (after logout or expiry). */
|
|
86
86
|
register: (opts: AuthRegisterOptions) => void;
|
|
87
87
|
/** Resolves once authenticated (immediately if already); rejects if auth expires. */
|
|
88
88
|
ready: () => Promise<void>;
|
|
89
89
|
/** Switch to another site. */
|
|
90
90
|
switch: (target: string, handlers?: AuthSwitchHandlers) => Promise<AuthTokenView>;
|
|
91
|
-
/** Clear local auth state and stop the scheduler. Best-effort server notify (
|
|
91
|
+
/** Clear local auth state and stop the scheduler. Best-effort server notify (revokes the backend session). */
|
|
92
92
|
logout: () => Promise<void>;
|
|
93
93
|
onAuthState: (listener: (state: AuthControllerState) => void) => (() => void);
|
|
94
94
|
onTokenRefresh: (listener: (view: AuthTokenView) => void) => (() => void);
|
|
@@ -34,12 +34,12 @@ class AuthControllerImpl {
|
|
|
34
34
|
this._token = '';
|
|
35
35
|
this.authId = '';
|
|
36
36
|
this.active = false;
|
|
37
|
-
/** Register token, authId, and sign callback. Idempotent; resumes when
|
|
37
|
+
/** Register token, authId, and sign callback. Idempotent; resumes auth when inactive (after logout or expiry). */
|
|
38
38
|
this.register = (opts) => {
|
|
39
39
|
this._token = opts.token;
|
|
40
40
|
this.authId = opts.authId;
|
|
41
41
|
this.signCallback = opts.sign;
|
|
42
|
-
if (this.
|
|
42
|
+
if (!this.active) {
|
|
43
43
|
this.failures = 0;
|
|
44
44
|
this.active = true;
|
|
45
45
|
this.setState('pending');
|
|
@@ -59,9 +59,9 @@ class AuthControllerImpl {
|
|
|
59
59
|
off();
|
|
60
60
|
resolve();
|
|
61
61
|
}
|
|
62
|
-
else if (s === 'expired') {
|
|
62
|
+
else if (s === 'expired' || s === '') {
|
|
63
63
|
off();
|
|
64
|
-
reject(new Error(`401 UNAUTHORIZED - auth expired`));
|
|
64
|
+
reject(new Error(`401 UNAUTHORIZED - auth ${s === 'expired' ? 'expired' : 'logged out'}`));
|
|
65
65
|
}
|
|
66
66
|
});
|
|
67
67
|
});
|
|
@@ -115,14 +115,18 @@ class AuthControllerImpl {
|
|
|
115
115
|
this.rearmRefreshTimer();
|
|
116
116
|
}
|
|
117
117
|
});
|
|
118
|
-
/** Clear local auth state and stop the scheduler. Best-effort server notify (
|
|
118
|
+
/** Clear local auth state and stop the scheduler. Best-effort server notify (revokes the backend session). */
|
|
119
119
|
this.logout = () => __awaiter(this, void 0, void 0, function* () {
|
|
120
|
-
|
|
120
|
+
/** bump epoch so a late in-flight refresh/switch/reauth response can't resurrect auth after logout */
|
|
121
|
+
this.epoch++;
|
|
121
122
|
this.stop();
|
|
122
123
|
this._token = '';
|
|
123
124
|
this.authId = '';
|
|
124
125
|
this.signCallback = undefined;
|
|
125
126
|
this.setState('');
|
|
127
|
+
if (this.options.client.state === 'connected') {
|
|
128
|
+
yield this.gateway.logout({}).catch(() => undefined);
|
|
129
|
+
}
|
|
126
130
|
});
|
|
127
131
|
this.onAuthState = (listener) => {
|
|
128
132
|
this.stateListeners.add(listener);
|
|
@@ -24,6 +24,7 @@ export interface ChannelSyncPlanOptions<TView extends SyncableView = SyncableVie
|
|
|
24
24
|
export declare class ChannelSyncPlan<TView extends SyncableView = SyncableView> implements DomainSyncPlan<ChannelSyncTarget> {
|
|
25
25
|
private readonly options;
|
|
26
26
|
readonly domain = "channel";
|
|
27
|
+
readonly requiresAuth = true;
|
|
27
28
|
readonly idleBackoff: SyncBackoffOptions;
|
|
28
29
|
constructor(options?: ChannelSyncPlanOptions<TView>);
|
|
29
30
|
supports: (target: SyncTargetDescriptor) => target is ChannelSyncTarget;
|
|
@@ -20,6 +20,7 @@ class ChannelSyncPlan {
|
|
|
20
20
|
var _a;
|
|
21
21
|
this.options = options;
|
|
22
22
|
this.domain = 'channel';
|
|
23
|
+
this.requiresAuth = true;
|
|
23
24
|
this.supports = (target) => (target === null || target === void 0 ? void 0 : target.type) === 'channel';
|
|
24
25
|
this.getKey = (target) => { var _a; return `channel:${(_a = target === null || target === void 0 ? void 0 : target.id) !== null && _a !== void 0 ? _a : ''}`; };
|
|
25
26
|
this.getIntervalMs = (target) => { var _a, _b; return (_b = (_a = target.intervalMs) !== null && _a !== void 0 ? _a : this.options.intervalMs) !== null && _b !== void 0 ? _b : 2000; };
|
|
@@ -41,6 +41,7 @@ export interface ChatSyncPlanOptions<TMessage extends ChatMessageLike = ChatMess
|
|
|
41
41
|
export declare class ChatSyncPlan<TMessage extends ChatMessageLike = ChatMessageLike> implements DomainSyncPlan<ChatSyncTarget> {
|
|
42
42
|
private readonly options;
|
|
43
43
|
readonly domain = "chat";
|
|
44
|
+
readonly requiresAuth = true;
|
|
44
45
|
readonly idleBackoff: SyncBackoffOptions;
|
|
45
46
|
private readonly chains;
|
|
46
47
|
constructor(options?: ChatSyncPlanOptions<TMessage>);
|
|
@@ -22,6 +22,7 @@ class ChatSyncPlan {
|
|
|
22
22
|
var _a;
|
|
23
23
|
this.options = options;
|
|
24
24
|
this.domain = 'chat';
|
|
25
|
+
this.requiresAuth = true;
|
|
25
26
|
this.chains = new Map();
|
|
26
27
|
this.supports = (target) => (target === null || target === void 0 ? void 0 : target.type) === 'chat';
|
|
27
28
|
this.getKey = (target) => { var _a; return `chat:${(_a = target === null || target === void 0 ? void 0 : target.id) !== null && _a !== void 0 ? _a : ''}`; };
|
|
@@ -21,6 +21,7 @@ export interface DeviceSyncPlanOptions {
|
|
|
21
21
|
export declare class DeviceSyncPlan implements DomainSyncPlan<DeviceSyncTarget> {
|
|
22
22
|
private readonly options;
|
|
23
23
|
readonly domain = "device";
|
|
24
|
+
readonly requiresAuth = false;
|
|
24
25
|
readonly failurePolicy: SyncFailurePolicy;
|
|
25
26
|
readonly idleBackoff: SyncBackoffOptions;
|
|
26
27
|
constructor(options?: DeviceSyncPlanOptions);
|
|
@@ -15,6 +15,7 @@ class DeviceSyncPlan {
|
|
|
15
15
|
var _a, _b;
|
|
16
16
|
this.options = options;
|
|
17
17
|
this.domain = 'device';
|
|
18
|
+
this.requiresAuth = false;
|
|
18
19
|
this.supports = (target) => (target === null || target === void 0 ? void 0 : target.type) === 'device';
|
|
19
20
|
this.getKey = (target) => { var _a; return `device:${(_a = target === null || target === void 0 ? void 0 : target.id) !== null && _a !== void 0 ? _a : 'current'}`; };
|
|
20
21
|
this.getIntervalMs = (target) => { var _a, _b; return (_b = (_a = target.intervalMs) !== null && _a !== void 0 ? _a : this.options.intervalMs) !== null && _b !== void 0 ? _b : 2000; };
|
|
@@ -24,6 +24,7 @@ export interface JoinSyncPlanOptions<TView extends SyncableView = SyncableView>
|
|
|
24
24
|
export declare class JoinSyncPlan<TView extends SyncableView = SyncableView> implements DomainSyncPlan<JoinSyncTarget> {
|
|
25
25
|
private readonly options;
|
|
26
26
|
readonly domain = "join";
|
|
27
|
+
readonly requiresAuth = true;
|
|
27
28
|
readonly idleBackoff: SyncBackoffOptions;
|
|
28
29
|
constructor(options?: JoinSyncPlanOptions<TView>);
|
|
29
30
|
supports: (target: SyncTargetDescriptor) => target is JoinSyncTarget;
|
|
@@ -20,6 +20,7 @@ class JoinSyncPlan {
|
|
|
20
20
|
var _a;
|
|
21
21
|
this.options = options;
|
|
22
22
|
this.domain = 'join';
|
|
23
|
+
this.requiresAuth = true;
|
|
23
24
|
this.supports = (target) => (target === null || target === void 0 ? void 0 : target.type) === 'join';
|
|
24
25
|
this.getKey = (target) => { var _a; return `join:${(_a = target === null || target === void 0 ? void 0 : target.id) !== null && _a !== void 0 ? _a : ''}`; };
|
|
25
26
|
// Default poll interval (10s); the scheduler backs this off x2 up to 60s while idle.
|
|
@@ -24,6 +24,7 @@ export interface PlaceSyncPlanOptions<TView extends SyncableView = SyncableView>
|
|
|
24
24
|
export declare class PlaceSyncPlan<TView extends SyncableView = SyncableView> implements DomainSyncPlan<PlaceSyncTarget> {
|
|
25
25
|
private readonly options;
|
|
26
26
|
readonly domain = "place";
|
|
27
|
+
readonly requiresAuth = true;
|
|
27
28
|
readonly idleBackoff: SyncBackoffOptions;
|
|
28
29
|
constructor(options?: PlaceSyncPlanOptions<TView>);
|
|
29
30
|
supports: (target: SyncTargetDescriptor) => target is PlaceSyncTarget;
|
|
@@ -20,6 +20,7 @@ class PlaceSyncPlan {
|
|
|
20
20
|
var _a;
|
|
21
21
|
this.options = options;
|
|
22
22
|
this.domain = 'place';
|
|
23
|
+
this.requiresAuth = true;
|
|
23
24
|
this.supports = (target) => (target === null || target === void 0 ? void 0 : target.type) === 'place';
|
|
24
25
|
this.getKey = (target) => { var _a; return `place:${(_a = target === null || target === void 0 ? void 0 : target.id) !== null && _a !== void 0 ? _a : ''}`; };
|
|
25
26
|
this.getIntervalMs = (target) => { var _a, _b; return (_b = (_a = target.intervalMs) !== null && _a !== void 0 ? _a : this.options.intervalMs) !== null && _b !== void 0 ? _b : 2000; };
|
|
@@ -24,6 +24,7 @@ export interface ProfileSyncPlanOptions<TView extends SyncableView = SyncableVie
|
|
|
24
24
|
export declare class ProfileSyncPlan<TView extends SyncableView = SyncableView> implements DomainSyncPlan<ProfileSyncTarget> {
|
|
25
25
|
private readonly options;
|
|
26
26
|
readonly domain = "profile";
|
|
27
|
+
readonly requiresAuth = true;
|
|
27
28
|
readonly idleBackoff: SyncBackoffOptions;
|
|
28
29
|
constructor(options?: ProfileSyncPlanOptions<TView>);
|
|
29
30
|
supports: (target: SyncTargetDescriptor) => target is ProfileSyncTarget;
|
|
@@ -20,6 +20,7 @@ class ProfileSyncPlan {
|
|
|
20
20
|
var _a;
|
|
21
21
|
this.options = options;
|
|
22
22
|
this.domain = 'profile';
|
|
23
|
+
this.requiresAuth = true;
|
|
23
24
|
this.supports = (target) => (target === null || target === void 0 ? void 0 : target.type) === 'profile';
|
|
24
25
|
this.getKey = (target) => { var _a; return `profile:${(_a = target === null || target === void 0 ? void 0 : target.id) !== null && _a !== void 0 ? _a : ''}`; };
|
|
25
26
|
this.getIntervalMs = (target) => { var _a, _b; return (_b = (_a = target.intervalMs) !== null && _a !== void 0 ? _a : this.options.intervalMs) !== null && _b !== void 0 ? _b : 2000; };
|
|
@@ -27,6 +27,7 @@ export declare class DomainSyncScheduler implements SyncScheduler {
|
|
|
27
27
|
private readonly random;
|
|
28
28
|
private readonly unsubs;
|
|
29
29
|
constructor(options: SyncSchedulerOptions);
|
|
30
|
+
private isEntryAuthReady;
|
|
30
31
|
start: (target: SyncTargetDescriptor) => void;
|
|
31
32
|
stop: (target: SyncTargetDescriptor) => void;
|
|
32
33
|
stopAll: () => void;
|
|
@@ -36,7 +37,11 @@ export declare class DomainSyncScheduler implements SyncScheduler {
|
|
|
36
37
|
private resolvePlan;
|
|
37
38
|
private findEntry;
|
|
38
39
|
private buildContext;
|
|
40
|
+
/** run the connect-time hook (may poll the server, e.g. chat catch-up) then fresh-start the poll loop. */
|
|
41
|
+
private activateEntry;
|
|
39
42
|
private handleConnected;
|
|
43
|
+
private resumeAuthGated;
|
|
44
|
+
private pauseAuthGated;
|
|
40
45
|
private stopAllTimers;
|
|
41
46
|
private clearTimer;
|
|
42
47
|
private scheduleNow;
|
|
@@ -24,6 +24,12 @@ class DomainSyncScheduler {
|
|
|
24
24
|
this.targets = new Map();
|
|
25
25
|
this.snapshots = new Map();
|
|
26
26
|
this.unsubs = [];
|
|
27
|
+
this.isEntryAuthReady = (entry) => {
|
|
28
|
+
if (!entry.plan.requiresAuth)
|
|
29
|
+
return true;
|
|
30
|
+
const auth = this.options.client.auth;
|
|
31
|
+
return !!auth && auth.state === 'authenticated';
|
|
32
|
+
};
|
|
27
33
|
this.start = (target) => {
|
|
28
34
|
const plan = this.resolvePlan(target);
|
|
29
35
|
const key = plan.getKey(target);
|
|
@@ -32,8 +38,9 @@ class DomainSyncScheduler {
|
|
|
32
38
|
entry.target = Object.assign(Object.assign({}, entry.target), target);
|
|
33
39
|
return;
|
|
34
40
|
}
|
|
35
|
-
|
|
36
|
-
|
|
41
|
+
const created = { target, plan };
|
|
42
|
+
this.targets.set(key, created);
|
|
43
|
+
if (this.options.client.state === 'connected' && this.isEntryAuthReady(created))
|
|
37
44
|
this.scheduleNow(key);
|
|
38
45
|
};
|
|
39
46
|
this.stop = (target) => {
|
|
@@ -104,18 +111,40 @@ class DomainSyncScheduler {
|
|
|
104
111
|
}),
|
|
105
112
|
stop: target => this.stop(target),
|
|
106
113
|
});
|
|
107
|
-
|
|
114
|
+
/** run the connect-time hook (may poll the server, e.g. chat catch-up) then fresh-start the poll loop. */
|
|
115
|
+
this.activateEntry = (key, entry, ctx) => __awaiter(this, void 0, void 0, function* () {
|
|
108
116
|
var _k, _l;
|
|
117
|
+
yield ((_l = (_k = entry.plan).onConnected) === null || _l === void 0 ? void 0 : _l.call(_k, entry.target, ctx));
|
|
118
|
+
entry.failures = 0;
|
|
119
|
+
entry.goneStreak = 0;
|
|
120
|
+
entry.idleStreak = 0;
|
|
121
|
+
this.scheduleNow(key);
|
|
122
|
+
});
|
|
123
|
+
this.handleConnected = () => __awaiter(this, void 0, void 0, function* () {
|
|
109
124
|
const ctx = this.buildContext();
|
|
110
125
|
for (const [key, entry] of this.targets.entries()) {
|
|
111
|
-
//
|
|
112
|
-
|
|
113
|
-
entry
|
|
114
|
-
|
|
115
|
-
yield
|
|
116
|
-
this.scheduleNow(key);
|
|
126
|
+
// gated (requiresAuth) entries activate on `authenticated` instead — see resumeAuthGated.
|
|
127
|
+
// onConnected itself may poll the server (chat catch-up), so it must be gated too, not just run().
|
|
128
|
+
if (!this.isEntryAuthReady(entry))
|
|
129
|
+
continue;
|
|
130
|
+
yield this.activateEntry(key, entry, ctx);
|
|
117
131
|
}
|
|
118
132
|
});
|
|
133
|
+
this.resumeAuthGated = () => __awaiter(this, void 0, void 0, function* () {
|
|
134
|
+
if (this.options.client.state !== 'connected')
|
|
135
|
+
return;
|
|
136
|
+
const ctx = this.buildContext();
|
|
137
|
+
for (const [key, entry] of this.targets.entries()) {
|
|
138
|
+
if (entry.plan.requiresAuth)
|
|
139
|
+
yield this.activateEntry(key, entry, ctx);
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
this.pauseAuthGated = () => {
|
|
143
|
+
this.targets.forEach(entry => {
|
|
144
|
+
if (entry.plan.requiresAuth)
|
|
145
|
+
this.clearTimer(entry);
|
|
146
|
+
});
|
|
147
|
+
};
|
|
119
148
|
this.stopAllTimers = () => {
|
|
120
149
|
this.targets.forEach(entry => this.clearTimer(entry));
|
|
121
150
|
};
|
|
@@ -143,7 +172,7 @@ class DomainSyncScheduler {
|
|
|
143
172
|
if (!entry)
|
|
144
173
|
return;
|
|
145
174
|
this.clearTimer(entry);
|
|
146
|
-
if (this.options.client.state !== 'connected')
|
|
175
|
+
if (this.options.client.state !== 'connected' || !this.isEntryAuthReady(entry))
|
|
147
176
|
return;
|
|
148
177
|
const baseMs = (_d = (_a = entry.target.intervalMs) !== null && _a !== void 0 ? _a : (_c = (_b = entry.plan).getIntervalMs) === null || _c === void 0 ? void 0 : _c.call(_b, entry.target)) !== null && _d !== void 0 ? _d : 5000;
|
|
149
178
|
const intervalMs = this.computeInterval(entry, baseMs);
|
|
@@ -170,7 +199,7 @@ class DomainSyncScheduler {
|
|
|
170
199
|
this.toTimerKey = (key) => `${this.timerPrefix}${key}`;
|
|
171
200
|
this.runEntry = (key) => __awaiter(this, void 0, void 0, function* () {
|
|
172
201
|
const entry = this.targets.get(key);
|
|
173
|
-
if (!entry || this.options.client.state !== 'connected')
|
|
202
|
+
if (!entry || this.options.client.state !== 'connected' || !this.isEntryAuthReady(entry))
|
|
174
203
|
return;
|
|
175
204
|
if (entry.inFlight)
|
|
176
205
|
return;
|
|
@@ -236,6 +265,8 @@ class DomainSyncScheduler {
|
|
|
236
265
|
return;
|
|
237
266
|
if (!type.startsWith(`${entry.target.type}.`))
|
|
238
267
|
return;
|
|
268
|
+
if (!this.isEntryAuthReady(entry))
|
|
269
|
+
return;
|
|
239
270
|
// nudge resets idle backoff (best-effort)
|
|
240
271
|
entry.idleStreak = 0;
|
|
241
272
|
yield entry.plan.onTrigger(entry.target, message, ctx).catch(() => undefined);
|
|
@@ -261,6 +292,15 @@ class DomainSyncScheduler {
|
|
|
261
292
|
this.unsubs.push(options.client.onMessage(({ message }) => {
|
|
262
293
|
void this.handleTrigger(message);
|
|
263
294
|
}));
|
|
295
|
+
const auth = options.client.auth;
|
|
296
|
+
if (auth) {
|
|
297
|
+
this.unsubs.push(auth.onAuthState(state => {
|
|
298
|
+
if (state === 'authenticated')
|
|
299
|
+
void this.resumeAuthGated();
|
|
300
|
+
else
|
|
301
|
+
this.pauseAuthGated();
|
|
302
|
+
}));
|
|
303
|
+
}
|
|
264
304
|
}
|
|
265
305
|
}
|
|
266
306
|
exports.DomainSyncScheduler = DomainSyncScheduler;
|
|
@@ -166,6 +166,7 @@ export interface DomainSyncContext {
|
|
|
166
166
|
}
|
|
167
167
|
export interface DomainSyncPlan<TTarget extends SyncTargetDescriptor = SyncTargetDescriptor> {
|
|
168
168
|
readonly domain: string;
|
|
169
|
+
readonly requiresAuth?: boolean;
|
|
169
170
|
readonly failurePolicy?: SyncFailurePolicy;
|
|
170
171
|
readonly idleBackoff?: SyncBackoffOptions;
|
|
171
172
|
supports(target: SyncTargetDescriptor): target is TTarget;
|
|
@@ -52,3 +52,14 @@ export interface AuthTokenView {
|
|
|
52
52
|
/** cloud id */
|
|
53
53
|
cloudId?: string;
|
|
54
54
|
}
|
|
55
|
+
/** client-safe view of the logout result (curated from backend `UserLogoutResult`). */
|
|
56
|
+
export interface AuthLogoutView {
|
|
57
|
+
/** logged-out user id */
|
|
58
|
+
id?: string;
|
|
59
|
+
/** revoked auth-id */
|
|
60
|
+
authId?: string;
|
|
61
|
+
/** revoke flag (1 when revoked) */
|
|
62
|
+
revoked?: number;
|
|
63
|
+
/** revoke completion time (epoch ms) */
|
|
64
|
+
revokedAt?: number;
|
|
65
|
+
}
|
package/dist/lib/auth/types.d.ts
CHANGED
|
@@ -32,6 +32,8 @@ export interface AuthSwitchRequestData extends AuthRefreshRequestData {
|
|
|
32
32
|
export interface AuthLogoutRequestData {
|
|
33
33
|
/** (internal) auth-id */
|
|
34
34
|
authId?: string;
|
|
35
|
+
/** (internal) userAgent the signature */
|
|
36
|
+
userAgent?: string;
|
|
35
37
|
}
|
|
36
38
|
declare module '../types' {
|
|
37
39
|
interface SocketPacketRegistry {
|
|
@@ -52,7 +54,7 @@ declare module '../types' {
|
|
|
52
54
|
};
|
|
53
55
|
'auth.logout': {
|
|
54
56
|
request: AuthLogoutRequestData;
|
|
55
|
-
response:
|
|
57
|
+
response: unknown;
|
|
56
58
|
error: null;
|
|
57
59
|
};
|
|
58
60
|
}
|
package/package.json
CHANGED