@lemoncloud/chatic-sockets-lib 0.3.3 → 0.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/dist/client-socket-v2/auth-controller.d.ts +111 -0
- package/dist/client-socket-v2/auth-controller.js +340 -0
- package/dist/client-socket-v2/create-client-socket-v2.js +9 -2
- package/dist/client-socket-v2/gateways/auth-gateway.d.ts +4 -1
- package/dist/client-socket-v2/gateways/auth-gateway.js +3 -0
- package/dist/client-socket-v2/gateways/channel-gateway.d.ts +1 -1
- package/dist/client-socket-v2/index.d.ts +1 -0
- package/dist/client-socket-v2/index.js +1 -0
- package/dist/client-socket-v2/types.d.ts +3 -0
- package/dist/lib/auth/contracts.d.ts +54 -0
- package/dist/lib/auth/contracts.js +7 -0
- package/dist/lib/auth/types.d.ts +58 -7
- package/package.json +1 -1
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import type { AuthUpdateState, AuthTokenView } from '../lib/auth/contracts';
|
|
2
|
+
import type { ClientSocketV2, SharedTimerScheduler } from './types';
|
|
3
|
+
/** Auth state surfaced to the app: server states plus the client-terminal 'expired'. */
|
|
4
|
+
export declare type AuthControllerState = AuthUpdateState | 'expired';
|
|
5
|
+
/** App-provided signing callback (stateless); the SDK injects the current token, the app returns the lemon hmac signature. */
|
|
6
|
+
export declare type AuthSignCallback = (token: string, ctx?: {
|
|
7
|
+
target?: string;
|
|
8
|
+
}) => Promise<{
|
|
9
|
+
signature: string;
|
|
10
|
+
current: string;
|
|
11
|
+
}>;
|
|
12
|
+
/** Registration input for `register()`. */
|
|
13
|
+
export interface AuthRegisterOptions {
|
|
14
|
+
token: string;
|
|
15
|
+
authId: string;
|
|
16
|
+
sign: AuthSignCallback;
|
|
17
|
+
}
|
|
18
|
+
/** Per-call switch hooks. */
|
|
19
|
+
export interface AuthSwitchHandlers {
|
|
20
|
+
onSuccess?(res: AuthTokenView): void;
|
|
21
|
+
onError?(err: AuthSwitchError): void;
|
|
22
|
+
}
|
|
23
|
+
/** Typed error capturing the switch failure phase. */
|
|
24
|
+
export declare class AuthSwitchError extends Error {
|
|
25
|
+
readonly phase: 'not-connected' | 'sign' | 'server';
|
|
26
|
+
readonly cause?: unknown;
|
|
27
|
+
readonly serverResponse?: AuthTokenView;
|
|
28
|
+
constructor(opts: {
|
|
29
|
+
phase: AuthSwitchError['phase'];
|
|
30
|
+
cause?: unknown;
|
|
31
|
+
serverResponse?: AuthTokenView;
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
export interface AuthControllerOptionsPartial {
|
|
35
|
+
refreshIntervalMs?: number;
|
|
36
|
+
minBackoffMs?: number;
|
|
37
|
+
maxBackoffMs?: number;
|
|
38
|
+
backoffFactor?: number;
|
|
39
|
+
maxFailures?: number;
|
|
40
|
+
validatingTimeoutMs?: number;
|
|
41
|
+
now?: () => number;
|
|
42
|
+
}
|
|
43
|
+
export interface AuthControllerOptions extends AuthControllerOptionsPartial {
|
|
44
|
+
client: ClientSocketV2;
|
|
45
|
+
timerScheduler?: SharedTimerScheduler;
|
|
46
|
+
}
|
|
47
|
+
export interface AuthController {
|
|
48
|
+
readonly state: AuthControllerState;
|
|
49
|
+
/** Current token, for the app's HTTP Authorization header. */
|
|
50
|
+
readonly token: string;
|
|
51
|
+
register(opts: AuthRegisterOptions): void;
|
|
52
|
+
/** Resolves once authenticated (immediately if already); rejects if auth expires. */
|
|
53
|
+
ready(): Promise<void>;
|
|
54
|
+
switch(target: string, handlers?: AuthSwitchHandlers): Promise<AuthTokenView>;
|
|
55
|
+
/** Clear local auth state and stop the scheduler. */
|
|
56
|
+
logout(): Promise<void>;
|
|
57
|
+
onAuthState(listener: (state: AuthControllerState) => void): () => void;
|
|
58
|
+
/** Fires on every refresh/switch success (including SDK periodic refresh) with the full payload. */
|
|
59
|
+
onTokenRefresh(listener: (view: AuthTokenView) => void): () => void;
|
|
60
|
+
}
|
|
61
|
+
export declare class AuthControllerImpl implements AuthController {
|
|
62
|
+
private readonly options;
|
|
63
|
+
private readonly refreshIntervalMs;
|
|
64
|
+
private readonly minBackoffMs;
|
|
65
|
+
private readonly maxBackoffMs;
|
|
66
|
+
private readonly backoffFactor;
|
|
67
|
+
private readonly maxFailures;
|
|
68
|
+
private readonly validatingTimeoutMs;
|
|
69
|
+
private readonly now;
|
|
70
|
+
private readonly timerScheduler?;
|
|
71
|
+
private readonly gateway;
|
|
72
|
+
private readonly unsubs;
|
|
73
|
+
private readonly stateListeners;
|
|
74
|
+
private readonly tokenListeners;
|
|
75
|
+
private _state;
|
|
76
|
+
private epoch;
|
|
77
|
+
private failures;
|
|
78
|
+
private _token;
|
|
79
|
+
private authId;
|
|
80
|
+
private signCallback?;
|
|
81
|
+
private active;
|
|
82
|
+
constructor(options: AuthControllerOptions);
|
|
83
|
+
get state(): AuthControllerState;
|
|
84
|
+
get token(): string;
|
|
85
|
+
/** Register token, authId, and sign callback. Idempotent; resumes when 'expired'. */
|
|
86
|
+
register: (opts: AuthRegisterOptions) => void;
|
|
87
|
+
/** Resolves once authenticated (immediately if already); rejects if auth expires. */
|
|
88
|
+
ready: () => Promise<void>;
|
|
89
|
+
/** Switch to another site. */
|
|
90
|
+
switch: (target: string, handlers?: AuthSwitchHandlers) => Promise<AuthTokenView>;
|
|
91
|
+
/** Clear local auth state and stop the scheduler. Best-effort server notify (server-side logout is a stub). */
|
|
92
|
+
logout: () => Promise<void>;
|
|
93
|
+
onAuthState: (listener: (state: AuthControllerState) => void) => (() => void);
|
|
94
|
+
onTokenRefresh: (listener: (view: AuthTokenView) => void) => (() => void);
|
|
95
|
+
private emitToken;
|
|
96
|
+
start: () => void;
|
|
97
|
+
stop: () => void;
|
|
98
|
+
destroy: () => void;
|
|
99
|
+
private sendUpdate;
|
|
100
|
+
private runRefresh;
|
|
101
|
+
private handleAuthResponse;
|
|
102
|
+
private handleFailed;
|
|
103
|
+
private runReauth;
|
|
104
|
+
private computeBackoffDelay;
|
|
105
|
+
private scheduleRefresh;
|
|
106
|
+
private rearmRefreshTimer;
|
|
107
|
+
private schedule;
|
|
108
|
+
private clearTimer;
|
|
109
|
+
private clearTimers;
|
|
110
|
+
private setState;
|
|
111
|
+
}
|
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.AuthControllerImpl = exports.AuthSwitchError = void 0;
|
|
13
|
+
const auth_gateway_1 = require("./gateways/auth-gateway");
|
|
14
|
+
/** Typed error capturing the switch failure phase. */
|
|
15
|
+
class AuthSwitchError extends Error {
|
|
16
|
+
constructor(opts) {
|
|
17
|
+
super(`auth.switch failed: ${opts.phase}`);
|
|
18
|
+
this.phase = opts.phase;
|
|
19
|
+
this.cause = opts.cause;
|
|
20
|
+
this.serverResponse = opts.serverResponse;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
exports.AuthSwitchError = AuthSwitchError;
|
|
24
|
+
class AuthControllerImpl {
|
|
25
|
+
constructor(options) {
|
|
26
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
27
|
+
this.options = options;
|
|
28
|
+
this.unsubs = [];
|
|
29
|
+
this.stateListeners = new Set();
|
|
30
|
+
this.tokenListeners = new Set();
|
|
31
|
+
this._state = '';
|
|
32
|
+
this.epoch = 0;
|
|
33
|
+
this.failures = 0;
|
|
34
|
+
this._token = '';
|
|
35
|
+
this.authId = '';
|
|
36
|
+
this.active = false;
|
|
37
|
+
/** Register token, authId, and sign callback. Idempotent; resumes when 'expired'. */
|
|
38
|
+
this.register = (opts) => {
|
|
39
|
+
this._token = opts.token;
|
|
40
|
+
this.authId = opts.authId;
|
|
41
|
+
this.signCallback = opts.sign;
|
|
42
|
+
if (this._state === 'expired') {
|
|
43
|
+
this.failures = 0;
|
|
44
|
+
this.active = true;
|
|
45
|
+
this.setState('pending');
|
|
46
|
+
if (this.options.client.state === 'connected') {
|
|
47
|
+
this.epoch++;
|
|
48
|
+
void this.sendUpdate();
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
/** Resolves once authenticated (immediately if already); rejects if auth expires. */
|
|
53
|
+
this.ready = () => {
|
|
54
|
+
if (this._state === 'authenticated')
|
|
55
|
+
return Promise.resolve();
|
|
56
|
+
return new Promise((resolve, reject) => {
|
|
57
|
+
const off = this.onAuthState(s => {
|
|
58
|
+
if (s === 'authenticated') {
|
|
59
|
+
off();
|
|
60
|
+
resolve();
|
|
61
|
+
}
|
|
62
|
+
else if (s === 'expired') {
|
|
63
|
+
off();
|
|
64
|
+
reject(new Error(`401 UNAUTHORIZED - auth expired`));
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
};
|
|
69
|
+
/** Switch to another site. */
|
|
70
|
+
this.switch = (target, handlers) => __awaiter(this, void 0, void 0, function* () {
|
|
71
|
+
var _h, _j, _k, _l, _m, _o, _p, _q;
|
|
72
|
+
if (this.options.client.state !== 'connected') {
|
|
73
|
+
const err = new AuthSwitchError({ phase: 'not-connected' });
|
|
74
|
+
(_h = handlers === null || handlers === void 0 ? void 0 : handlers.onError) === null || _h === void 0 ? void 0 : _h.call(handlers, err);
|
|
75
|
+
throw err;
|
|
76
|
+
}
|
|
77
|
+
this.epoch++;
|
|
78
|
+
const myEpoch = this.epoch;
|
|
79
|
+
/** suspend periodic refresh to avoid an epoch race with switch */
|
|
80
|
+
this.clearTimer('auth:refresh');
|
|
81
|
+
let signResult;
|
|
82
|
+
try {
|
|
83
|
+
signResult = (_k = (yield ((_j = this.signCallback) === null || _j === void 0 ? void 0 : _j.call(this, this._token, { target })))) !== null && _k !== void 0 ? _k : { signature: '', current: '' };
|
|
84
|
+
}
|
|
85
|
+
catch (cause) {
|
|
86
|
+
const err = new AuthSwitchError({ phase: 'sign', cause });
|
|
87
|
+
(_l = handlers === null || handlers === void 0 ? void 0 : handlers.onError) === null || _l === void 0 ? void 0 : _l.call(handlers, err);
|
|
88
|
+
this.rearmRefreshTimer();
|
|
89
|
+
throw err;
|
|
90
|
+
}
|
|
91
|
+
try {
|
|
92
|
+
const res = yield this.gateway.switch({
|
|
93
|
+
current: signResult.current,
|
|
94
|
+
signature: signResult.signature,
|
|
95
|
+
authId: this.authId,
|
|
96
|
+
target,
|
|
97
|
+
});
|
|
98
|
+
const view = res;
|
|
99
|
+
if (myEpoch !== this.epoch)
|
|
100
|
+
return view;
|
|
101
|
+
this._token = (_o = (_m = view === null || view === void 0 ? void 0 : view.Token) === null || _m === void 0 ? void 0 : _m.identityToken) !== null && _o !== void 0 ? _o : this._token;
|
|
102
|
+
this.setState('authenticated');
|
|
103
|
+
this.emitToken(view);
|
|
104
|
+
(_p = handlers === null || handlers === void 0 ? void 0 : handlers.onSuccess) === null || _p === void 0 ? void 0 : _p.call(handlers, view);
|
|
105
|
+
return view;
|
|
106
|
+
}
|
|
107
|
+
catch (e) {
|
|
108
|
+
if (e instanceof AuthSwitchError)
|
|
109
|
+
throw e;
|
|
110
|
+
const err = new AuthSwitchError({ phase: 'server', cause: e });
|
|
111
|
+
(_q = handlers === null || handlers === void 0 ? void 0 : handlers.onError) === null || _q === void 0 ? void 0 : _q.call(handlers, err);
|
|
112
|
+
throw err;
|
|
113
|
+
}
|
|
114
|
+
finally {
|
|
115
|
+
this.rearmRefreshTimer();
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
/** Clear local auth state and stop the scheduler. Best-effort server notify (server-side logout is a stub). */
|
|
119
|
+
this.logout = () => __awaiter(this, void 0, void 0, function* () {
|
|
120
|
+
yield this.gateway.logout({}).catch(() => undefined);
|
|
121
|
+
this.stop();
|
|
122
|
+
this._token = '';
|
|
123
|
+
this.authId = '';
|
|
124
|
+
this.signCallback = undefined;
|
|
125
|
+
this.setState('');
|
|
126
|
+
});
|
|
127
|
+
this.onAuthState = (listener) => {
|
|
128
|
+
this.stateListeners.add(listener);
|
|
129
|
+
return () => this.stateListeners.delete(listener);
|
|
130
|
+
};
|
|
131
|
+
this.onTokenRefresh = (listener) => {
|
|
132
|
+
this.tokenListeners.add(listener);
|
|
133
|
+
return () => this.tokenListeners.delete(listener);
|
|
134
|
+
};
|
|
135
|
+
this.emitToken = (view) => {
|
|
136
|
+
this.tokenListeners.forEach(l => l(view));
|
|
137
|
+
};
|
|
138
|
+
this.start = () => {
|
|
139
|
+
this.active = true;
|
|
140
|
+
if (this.options.client.state === 'connected' && this._token) {
|
|
141
|
+
this.epoch++;
|
|
142
|
+
void this.sendUpdate();
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
this.stop = () => {
|
|
146
|
+
this.active = false;
|
|
147
|
+
this.clearTimers();
|
|
148
|
+
};
|
|
149
|
+
this.destroy = () => {
|
|
150
|
+
this.stop();
|
|
151
|
+
this.unsubs.splice(0).forEach(unsub => unsub());
|
|
152
|
+
this.stateListeners.clear();
|
|
153
|
+
this.tokenListeners.clear();
|
|
154
|
+
};
|
|
155
|
+
this.sendUpdate = () => __awaiter(this, void 0, void 0, function* () {
|
|
156
|
+
var _r;
|
|
157
|
+
if (!this._token)
|
|
158
|
+
return;
|
|
159
|
+
const myEpoch = this.epoch;
|
|
160
|
+
this.setState('pending');
|
|
161
|
+
try {
|
|
162
|
+
const res = yield this.gateway.update({ token: this._token });
|
|
163
|
+
if (myEpoch !== this.epoch)
|
|
164
|
+
return;
|
|
165
|
+
this.handleAuthResponse((_r = res === null || res === void 0 ? void 0 : res.state) !== null && _r !== void 0 ? _r : 'failed');
|
|
166
|
+
}
|
|
167
|
+
catch (_s) {
|
|
168
|
+
if (myEpoch !== this.epoch)
|
|
169
|
+
return;
|
|
170
|
+
this.handleFailed();
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
this.runRefresh = () => __awaiter(this, void 0, void 0, function* () {
|
|
174
|
+
var _t, _u;
|
|
175
|
+
if (!this.signCallback)
|
|
176
|
+
return;
|
|
177
|
+
this.epoch++;
|
|
178
|
+
const myEpoch = this.epoch;
|
|
179
|
+
let signResult;
|
|
180
|
+
try {
|
|
181
|
+
signResult = yield this.signCallback(this._token);
|
|
182
|
+
}
|
|
183
|
+
catch (_v) {
|
|
184
|
+
if (myEpoch !== this.epoch)
|
|
185
|
+
return;
|
|
186
|
+
this.handleFailed();
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
try {
|
|
190
|
+
const res = yield this.gateway.refresh({
|
|
191
|
+
current: signResult.current,
|
|
192
|
+
signature: signResult.signature,
|
|
193
|
+
authId: this.authId,
|
|
194
|
+
});
|
|
195
|
+
if (myEpoch !== this.epoch)
|
|
196
|
+
return;
|
|
197
|
+
const view = res;
|
|
198
|
+
this._token = (_u = (_t = view === null || view === void 0 ? void 0 : view.Token) === null || _t === void 0 ? void 0 : _t.identityToken) !== null && _u !== void 0 ? _u : this._token;
|
|
199
|
+
this.failures = 0;
|
|
200
|
+
this.setState('authenticated');
|
|
201
|
+
this.scheduleRefresh();
|
|
202
|
+
this.emitToken(view);
|
|
203
|
+
}
|
|
204
|
+
catch (_w) {
|
|
205
|
+
if (myEpoch !== this.epoch)
|
|
206
|
+
return;
|
|
207
|
+
this.handleFailed();
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
this.handleAuthResponse = (state) => {
|
|
211
|
+
if (state === 'authenticated') {
|
|
212
|
+
this.failures = 0;
|
|
213
|
+
this.setState('authenticated');
|
|
214
|
+
this.scheduleRefresh();
|
|
215
|
+
}
|
|
216
|
+
else if (state === 'validating') {
|
|
217
|
+
this.setState('validating');
|
|
218
|
+
this.schedule('auth:validating', this.validatingTimeoutMs, () => {
|
|
219
|
+
if (this._state === 'validating')
|
|
220
|
+
this.handleFailed();
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
else {
|
|
224
|
+
this.handleFailed();
|
|
225
|
+
}
|
|
226
|
+
};
|
|
227
|
+
this.handleFailed = () => {
|
|
228
|
+
this.failures++;
|
|
229
|
+
this.setState('failed');
|
|
230
|
+
this.clearTimer('auth:refresh');
|
|
231
|
+
this.clearTimer('auth:validating');
|
|
232
|
+
if (this.failures > this.maxFailures) {
|
|
233
|
+
this.setState('expired');
|
|
234
|
+
this.active = false;
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
const delay = this.computeBackoffDelay(this.failures - 1);
|
|
238
|
+
this.schedule('auth:reauth', delay, () => void this.runReauth());
|
|
239
|
+
};
|
|
240
|
+
this.runReauth = () => __awaiter(this, void 0, void 0, function* () {
|
|
241
|
+
var _x, _y;
|
|
242
|
+
if (!this.signCallback)
|
|
243
|
+
return;
|
|
244
|
+
this.epoch++;
|
|
245
|
+
const myEpoch = this.epoch;
|
|
246
|
+
let signResult;
|
|
247
|
+
try {
|
|
248
|
+
signResult = yield this.signCallback(this._token);
|
|
249
|
+
}
|
|
250
|
+
catch (_z) {
|
|
251
|
+
if (myEpoch !== this.epoch)
|
|
252
|
+
return;
|
|
253
|
+
this.handleFailed();
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
try {
|
|
257
|
+
const res = yield this.gateway.refresh({
|
|
258
|
+
current: signResult.current,
|
|
259
|
+
signature: signResult.signature,
|
|
260
|
+
authId: this.authId,
|
|
261
|
+
});
|
|
262
|
+
if (myEpoch !== this.epoch)
|
|
263
|
+
return;
|
|
264
|
+
const view = res;
|
|
265
|
+
this._token = (_y = (_x = view === null || view === void 0 ? void 0 : view.Token) === null || _x === void 0 ? void 0 : _x.identityToken) !== null && _y !== void 0 ? _y : this._token;
|
|
266
|
+
this.failures = 0;
|
|
267
|
+
this.setState('authenticated');
|
|
268
|
+
this.scheduleRefresh();
|
|
269
|
+
this.emitToken(view);
|
|
270
|
+
}
|
|
271
|
+
catch (_0) {
|
|
272
|
+
if (myEpoch !== this.epoch)
|
|
273
|
+
return;
|
|
274
|
+
this.handleFailed();
|
|
275
|
+
}
|
|
276
|
+
});
|
|
277
|
+
this.computeBackoffDelay = (attempt) => {
|
|
278
|
+
const expDelay = Math.min(this.minBackoffMs * Math.pow(this.backoffFactor, attempt), this.maxBackoffMs);
|
|
279
|
+
const jitter = expDelay * (Math.random() * 2 - 1) * 0.3;
|
|
280
|
+
return Math.max(this.minBackoffMs, Math.min(this.maxBackoffMs, expDelay + jitter));
|
|
281
|
+
};
|
|
282
|
+
this.scheduleRefresh = () => {
|
|
283
|
+
this.schedule('auth:refresh', this.refreshIntervalMs, () => void this.runRefresh());
|
|
284
|
+
};
|
|
285
|
+
this.rearmRefreshTimer = () => {
|
|
286
|
+
if (this._state === 'authenticated')
|
|
287
|
+
this.scheduleRefresh();
|
|
288
|
+
};
|
|
289
|
+
this.schedule = (key, delayMs, task) => {
|
|
290
|
+
if (this.timerScheduler) {
|
|
291
|
+
this.timerScheduler.schedule(key, delayMs, task);
|
|
292
|
+
}
|
|
293
|
+
else {
|
|
294
|
+
setTimeout(task, delayMs);
|
|
295
|
+
}
|
|
296
|
+
};
|
|
297
|
+
this.clearTimer = (key) => {
|
|
298
|
+
var _a;
|
|
299
|
+
(_a = this.timerScheduler) === null || _a === void 0 ? void 0 : _a.cancel(key);
|
|
300
|
+
};
|
|
301
|
+
this.clearTimers = () => {
|
|
302
|
+
this.clearTimer('auth:refresh');
|
|
303
|
+
this.clearTimer('auth:reauth');
|
|
304
|
+
this.clearTimer('auth:validating');
|
|
305
|
+
};
|
|
306
|
+
this.setState = (next) => {
|
|
307
|
+
if (this._state === next)
|
|
308
|
+
return;
|
|
309
|
+
this._state = next;
|
|
310
|
+
this.stateListeners.forEach(l => l(next));
|
|
311
|
+
};
|
|
312
|
+
this.refreshIntervalMs = (_a = options.refreshIntervalMs) !== null && _a !== void 0 ? _a : 1800000;
|
|
313
|
+
this.minBackoffMs = (_b = options.minBackoffMs) !== null && _b !== void 0 ? _b : 1000;
|
|
314
|
+
this.maxBackoffMs = (_c = options.maxBackoffMs) !== null && _c !== void 0 ? _c : 30000;
|
|
315
|
+
this.backoffFactor = (_d = options.backoffFactor) !== null && _d !== void 0 ? _d : 2;
|
|
316
|
+
this.maxFailures = (_e = options.maxFailures) !== null && _e !== void 0 ? _e : 5;
|
|
317
|
+
this.validatingTimeoutMs = (_f = options.validatingTimeoutMs) !== null && _f !== void 0 ? _f : 15000;
|
|
318
|
+
this.now = (_g = options.now) !== null && _g !== void 0 ? _g : (() => Date.now());
|
|
319
|
+
this.timerScheduler = options.timerScheduler;
|
|
320
|
+
this.gateway = (0, auth_gateway_1.createAuthGateway)(options.client);
|
|
321
|
+
this.unsubs.push(options.client.onState(event => {
|
|
322
|
+
if (!this.active)
|
|
323
|
+
return;
|
|
324
|
+
if (event.next === 'connected') {
|
|
325
|
+
this.epoch++;
|
|
326
|
+
void this.sendUpdate();
|
|
327
|
+
}
|
|
328
|
+
if (event.next === 'closed' || event.next === 'closing') {
|
|
329
|
+
this.clearTimers();
|
|
330
|
+
}
|
|
331
|
+
}));
|
|
332
|
+
}
|
|
333
|
+
get state() {
|
|
334
|
+
return this._state;
|
|
335
|
+
}
|
|
336
|
+
get token() {
|
|
337
|
+
return this._token;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
exports.AuthControllerImpl = AuthControllerImpl;
|
|
@@ -17,6 +17,7 @@ const pending_request_store_1 = require("./pending-request-store");
|
|
|
17
17
|
const reconnect_controller_1 = require("./reconnect-controller");
|
|
18
18
|
const shared_timer_scheduler_1 = require("./shared-timer-scheduler");
|
|
19
19
|
const socket_transport_1 = require("./socket-transport");
|
|
20
|
+
const auth_controller_1 = require("./auth-controller");
|
|
20
21
|
const buildMid = () => `m-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
|
|
21
22
|
const parseMessage = (raw) => {
|
|
22
23
|
const text = `${raw !== null && raw !== void 0 ? raw : ''}`.trim();
|
|
@@ -136,10 +137,11 @@ class ClientSocketV2Impl {
|
|
|
136
137
|
};
|
|
137
138
|
this.onType = (type, listener) => this.router.onType(type, listener);
|
|
138
139
|
this.destroy = () => {
|
|
139
|
-
var _a, _b;
|
|
140
|
-
/** 순서 invariant: reconnect.destroy → keepAlive.destroy → transport.disconnect (그 close 이벤트를 reconnect가 못 듣게 하기 위함) → 나머지 정리 */
|
|
140
|
+
var _a, _b, _c;
|
|
141
|
+
/** 순서 invariant: reconnect.destroy → keepAlive.destroy → auth.destroy → transport.disconnect (그 close 이벤트를 reconnect가 못 듣게 하기 위함) → 나머지 정리 */
|
|
141
142
|
(_a = this.reconnect) === null || _a === void 0 ? void 0 : _a.destroy();
|
|
142
143
|
(_b = this.keepAlive) === null || _b === void 0 ? void 0 : _b.destroy();
|
|
144
|
+
(_c = this.auth) === null || _c === void 0 ? void 0 : _c.destroy();
|
|
143
145
|
void this.transport.disconnect().catch(() => undefined);
|
|
144
146
|
this.transportUnsubs.splice(0).forEach(unsub => unsub());
|
|
145
147
|
this.rejectQueuedRequests(new Error(`499 CLIENT CLOSED REQUEST - client destroyed`));
|
|
@@ -283,6 +285,11 @@ class ClientSocketV2Impl {
|
|
|
283
285
|
if (options.reconnect !== false) {
|
|
284
286
|
this.reconnect = new reconnect_controller_1.AutoReconnectController(Object.assign({ client: this, timerScheduler: this.timerScheduler }, (options.reconnect || {})));
|
|
285
287
|
}
|
|
288
|
+
if (options.auth !== false) {
|
|
289
|
+
const auth = new auth_controller_1.AuthControllerImpl(Object.assign({ client: this, timerScheduler: this.timerScheduler }, (options.auth || {})));
|
|
290
|
+
this.auth = auth;
|
|
291
|
+
auth.start();
|
|
292
|
+
}
|
|
286
293
|
}
|
|
287
294
|
get state() {
|
|
288
295
|
return this._state;
|
|
@@ -1,6 +1,9 @@
|
|
|
1
|
-
import type { AuthUpdateInput, AuthUpdateResponse } from '../../lib/auth/types';
|
|
1
|
+
import type { AuthRefreshInput, AuthRefreshResponse, AuthSwitchInput, AuthSwitchResponse, AuthLogoutInput, AuthLogoutResponse, AuthUpdateInput, AuthUpdateResponse } from '../../lib/auth/types';
|
|
2
2
|
import type { ClientSocketV2 } from '../types';
|
|
3
3
|
export interface AuthGateway {
|
|
4
4
|
update(data: AuthUpdateInput): Promise<AuthUpdateResponse>;
|
|
5
|
+
refresh(data: AuthRefreshInput): Promise<AuthRefreshResponse>;
|
|
6
|
+
switch(data: AuthSwitchInput): Promise<AuthSwitchResponse>;
|
|
7
|
+
logout(data?: AuthLogoutInput): Promise<AuthLogoutResponse>;
|
|
5
8
|
}
|
|
6
9
|
export declare const createAuthGateway: (client: ClientSocketV2) => AuthGateway;
|
|
@@ -6,6 +6,9 @@ const createAuthGateway = (client) => {
|
|
|
6
6
|
const gateway = (0, create_domain_gateway_1.createDomainGateway)('auth', client);
|
|
7
7
|
return {
|
|
8
8
|
update: data => gateway.request('update', data),
|
|
9
|
+
refresh: data => gateway.request('refresh', data),
|
|
10
|
+
switch: data => gateway.request('switch', data),
|
|
11
|
+
logout: data => gateway.request('logout', data !== null && data !== void 0 ? data : {}),
|
|
9
12
|
};
|
|
10
13
|
};
|
|
11
14
|
exports.createAuthGateway = createAuthGateway;
|
|
@@ -24,7 +24,7 @@ export interface ChannelGateway {
|
|
|
24
24
|
listUser<T = unknown>(data: ChannelListUserInput): Promise<T>;
|
|
25
25
|
/** $socials.ChannelView */
|
|
26
26
|
invite<T = unknown>(data: ChannelInviteInput): Promise<T>;
|
|
27
|
-
/**
|
|
27
|
+
/** @deprecated moved to the join domain. Use `createJoinGateway(client).update()`. */
|
|
28
28
|
updateJoin<T = unknown>(data: ChannelUpdateJoinInput): Promise<T>;
|
|
29
29
|
/** $socials.UnreadsSummaryView */
|
|
30
30
|
unreads<T = unknown>(data?: ChannelUnreadsInput | null): Promise<T>;
|
|
@@ -27,6 +27,7 @@ export * from './gateways/create-domain-gateway';
|
|
|
27
27
|
export * from './gateways/device-gateway';
|
|
28
28
|
export * from './gateways/channel-gateway';
|
|
29
29
|
export * from './gateways/auth-gateway';
|
|
30
|
+
export * from './auth-controller';
|
|
30
31
|
export * from './gateways/chat-gateway';
|
|
31
32
|
export * from './gateways/join-gateway';
|
|
32
33
|
export * from './gateways/cloud-gateway';
|
|
@@ -44,6 +44,7 @@ __exportStar(require("./gateways/create-domain-gateway"), exports);
|
|
|
44
44
|
__exportStar(require("./gateways/device-gateway"), exports);
|
|
45
45
|
__exportStar(require("./gateways/channel-gateway"), exports);
|
|
46
46
|
__exportStar(require("./gateways/auth-gateway"), exports);
|
|
47
|
+
__exportStar(require("./auth-controller"), exports);
|
|
47
48
|
__exportStar(require("./gateways/chat-gateway"), exports);
|
|
48
49
|
__exportStar(require("./gateways/join-gateway"), exports);
|
|
49
50
|
__exportStar(require("./gateways/cloud-gateway"), exports);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { ResolveSocketPacketType, SocketMessage, SocketPacketInputType, SocketPacketRequestData, SocketPacketResponseData, SocketPacketType } from '../lib/types';
|
|
2
2
|
import type { DeviceBootstrapInput } from '../lib/device/types';
|
|
3
|
+
import type { AuthController, AuthControllerOptionsPartial } from './auth-controller';
|
|
3
4
|
export declare type ClientSocketState = 'idle' | 'connecting' | 'connected' | 'closing' | 'closed';
|
|
4
5
|
export interface SocketLike {
|
|
5
6
|
readonly readyState: number;
|
|
@@ -81,6 +82,7 @@ export interface ClientSocketOptions {
|
|
|
81
82
|
maxPendingRequests?: number;
|
|
82
83
|
keepAlive?: false | KeepAliveLoopOptionsPartial;
|
|
83
84
|
reconnect?: false | AutoReconnectOptionsPartial;
|
|
85
|
+
auth?: false | AuthControllerOptionsPartial;
|
|
84
86
|
createMid?: () => string;
|
|
85
87
|
now?: () => number;
|
|
86
88
|
}
|
|
@@ -103,6 +105,7 @@ export interface ClientSocketV2 {
|
|
|
103
105
|
readonly timerScheduler?: SharedTimerScheduler;
|
|
104
106
|
readonly keepAlive?: KeepAliveLoopControl;
|
|
105
107
|
readonly reconnect?: ReconnectController;
|
|
108
|
+
readonly auth?: AuthController;
|
|
106
109
|
connect(): Promise<void>;
|
|
107
110
|
disconnect(code?: number, reason?: string): Promise<void>;
|
|
108
111
|
send<TType extends SocketPacketInputType>(type: TType, data?: SocketPacketRequestData<ResolveSocketPacketType<TType>>): void;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `lib/auth/contracts.ts`
|
|
3
|
+
* - client-safe shared auth contracts.
|
|
4
|
+
* - keep this file free from server-only runtime dependencies.
|
|
5
|
+
*/
|
|
6
|
+
export declare type AuthUpdateState = '' | 'pending' | 'validating' | 'authenticated' | 'failed' | 'disconnected';
|
|
7
|
+
export interface AuthUserHead {
|
|
8
|
+
/** id of user */
|
|
9
|
+
id?: string;
|
|
10
|
+
/** name of user */
|
|
11
|
+
name?: string;
|
|
12
|
+
}
|
|
13
|
+
/** AWS Cognito temporary credential. */
|
|
14
|
+
export interface AuthCredential {
|
|
15
|
+
AccessKeyId?: string;
|
|
16
|
+
SecretKey?: string;
|
|
17
|
+
SessionToken?: string;
|
|
18
|
+
/** ISO expiration timestamp */
|
|
19
|
+
Expiration?: string;
|
|
20
|
+
}
|
|
21
|
+
/** issued identity token bundle. */
|
|
22
|
+
export interface AuthToken {
|
|
23
|
+
authId?: string;
|
|
24
|
+
accountId?: string;
|
|
25
|
+
identityId?: string;
|
|
26
|
+
identityPoolId?: string;
|
|
27
|
+
/** JWT identity token — SSoT used for HTTP `Authorization` headers */
|
|
28
|
+
identityToken?: string;
|
|
29
|
+
credential?: AuthCredential;
|
|
30
|
+
}
|
|
31
|
+
/** auth identity record (user/site binding) embedded in the token view. */
|
|
32
|
+
export interface AuthRecord {
|
|
33
|
+
id: string;
|
|
34
|
+
accountId?: string;
|
|
35
|
+
userId?: string;
|
|
36
|
+
siteId?: string;
|
|
37
|
+
refreshedAt?: number;
|
|
38
|
+
}
|
|
39
|
+
/** client-safe view of the refresh/switch token payload (mirrors backend `UserTokenView`). */
|
|
40
|
+
export interface AuthTokenView {
|
|
41
|
+
/** user id */
|
|
42
|
+
id: string;
|
|
43
|
+
accountId?: string;
|
|
44
|
+
/** role of the user, e.g. `guest` */
|
|
45
|
+
userRole?: string;
|
|
46
|
+
/** status of the user, e.g. `active` */
|
|
47
|
+
userStatus?: string;
|
|
48
|
+
/** issued token bundle; `Token.identityToken` is the SSoT */
|
|
49
|
+
Token?: AuthToken;
|
|
50
|
+
/** auth identity record */
|
|
51
|
+
$auth?: AuthRecord;
|
|
52
|
+
/** cloud id */
|
|
53
|
+
cloudId?: string;
|
|
54
|
+
}
|
package/dist/lib/auth/types.d.ts
CHANGED
|
@@ -1,13 +1,10 @@
|
|
|
1
|
-
import type { InferSocketError, InferSocketRequest, InferSocketResponse
|
|
2
|
-
|
|
1
|
+
import type { InferSocketError, InferSocketRequest, InferSocketResponse } from '../types';
|
|
2
|
+
import type { SocketErrorMessage, SocketRequestMessage, SocketResponseMessage } from '../types';
|
|
3
|
+
import { AuthUpdateState, AuthUserHead } from './contracts';
|
|
3
4
|
export interface AuthUpdateRequestData {
|
|
4
5
|
token?: string;
|
|
5
6
|
dryRun?: boolean;
|
|
6
7
|
}
|
|
7
|
-
export interface AuthUpdateMemberHead {
|
|
8
|
-
id?: string;
|
|
9
|
-
name?: string;
|
|
10
|
-
}
|
|
11
8
|
export interface AuthUpdateResponseData {
|
|
12
9
|
connId?: string;
|
|
13
10
|
deviceId?: string;
|
|
@@ -16,7 +13,25 @@ export interface AuthUpdateResponseData {
|
|
|
16
13
|
state?: AuthUpdateState;
|
|
17
14
|
stateAt?: number;
|
|
18
15
|
error?: string;
|
|
19
|
-
member$?:
|
|
16
|
+
member$?: AuthUserHead;
|
|
17
|
+
}
|
|
18
|
+
export interface AuthRefreshRequestData {
|
|
19
|
+
/** ISO timestamp used to compute the signature */
|
|
20
|
+
current: string;
|
|
21
|
+
/** app-computed proof signature (lemon hmac) */
|
|
22
|
+
signature: string;
|
|
23
|
+
/** backend auth-id; used as `/oauth/{authId}/refresh` path */
|
|
24
|
+
authId: string;
|
|
25
|
+
/** (internal) userAgent the signature */
|
|
26
|
+
userAgent?: string;
|
|
27
|
+
}
|
|
28
|
+
export interface AuthSwitchRequestData extends AuthRefreshRequestData {
|
|
29
|
+
/** 전환할 사이트 ID */
|
|
30
|
+
target: string;
|
|
31
|
+
}
|
|
32
|
+
export interface AuthLogoutRequestData {
|
|
33
|
+
/** (internal) auth-id */
|
|
34
|
+
authId?: string;
|
|
20
35
|
}
|
|
21
36
|
declare module '../types' {
|
|
22
37
|
interface SocketPacketRegistry {
|
|
@@ -25,6 +40,21 @@ declare module '../types' {
|
|
|
25
40
|
response: AuthUpdateResponseData;
|
|
26
41
|
error: null;
|
|
27
42
|
};
|
|
43
|
+
'auth.refresh': {
|
|
44
|
+
request: AuthRefreshRequestData;
|
|
45
|
+
response: unknown;
|
|
46
|
+
error: null;
|
|
47
|
+
};
|
|
48
|
+
'auth.switch': {
|
|
49
|
+
request: AuthSwitchRequestData;
|
|
50
|
+
response: unknown;
|
|
51
|
+
error: null;
|
|
52
|
+
};
|
|
53
|
+
'auth.logout': {
|
|
54
|
+
request: AuthLogoutRequestData;
|
|
55
|
+
response: never;
|
|
56
|
+
error: null;
|
|
57
|
+
};
|
|
28
58
|
}
|
|
29
59
|
}
|
|
30
60
|
export declare type AuthUpdateType = 'auth.update';
|
|
@@ -34,3 +64,24 @@ export declare type AuthUpdateErrorData = InferSocketError<AuthUpdateType>;
|
|
|
34
64
|
export declare type AuthUpdateRequestMessage = SocketRequestMessage<AuthUpdateType>;
|
|
35
65
|
export declare type AuthUpdateResponseMessage = SocketResponseMessage<AuthUpdateType>;
|
|
36
66
|
export declare type AuthUpdateErrorMessage = SocketErrorMessage<AuthUpdateType>;
|
|
67
|
+
export declare type AuthRefreshType = 'auth.refresh';
|
|
68
|
+
export declare type AuthRefreshInput = InferSocketRequest<AuthRefreshType>;
|
|
69
|
+
export declare type AuthRefreshResponse = InferSocketResponse<AuthRefreshType>;
|
|
70
|
+
export declare type AuthRefreshErrorData = InferSocketError<AuthRefreshType>;
|
|
71
|
+
export declare type AuthRefreshRequestMessage = SocketRequestMessage<AuthRefreshType>;
|
|
72
|
+
export declare type AuthRefreshResponseMessage = SocketResponseMessage<AuthRefreshType>;
|
|
73
|
+
export declare type AuthRefreshErrorMessage = SocketErrorMessage<AuthRefreshType>;
|
|
74
|
+
export declare type AuthSwitchType = 'auth.switch';
|
|
75
|
+
export declare type AuthSwitchInput = InferSocketRequest<AuthSwitchType>;
|
|
76
|
+
export declare type AuthSwitchResponse = InferSocketResponse<AuthSwitchType>;
|
|
77
|
+
export declare type AuthSwitchErrorData = InferSocketError<AuthSwitchType>;
|
|
78
|
+
export declare type AuthSwitchRequestMessage = SocketRequestMessage<AuthSwitchType>;
|
|
79
|
+
export declare type AuthSwitchResponseMessage = SocketResponseMessage<AuthSwitchType>;
|
|
80
|
+
export declare type AuthSwitchErrorMessage = SocketErrorMessage<AuthSwitchType>;
|
|
81
|
+
export declare type AuthLogoutType = 'auth.logout';
|
|
82
|
+
export declare type AuthLogoutInput = InferSocketRequest<AuthLogoutType>;
|
|
83
|
+
export declare type AuthLogoutResponse = InferSocketResponse<AuthLogoutType>;
|
|
84
|
+
export declare type AuthLogoutErrorData = InferSocketError<AuthLogoutType>;
|
|
85
|
+
export declare type AuthLogoutRequestMessage = SocketRequestMessage<AuthLogoutType>;
|
|
86
|
+
export declare type AuthLogoutResponseMessage = SocketResponseMessage<AuthLogoutType>;
|
|
87
|
+
export declare type AuthLogoutErrorMessage = SocketErrorMessage<AuthLogoutType>;
|
package/package.json
CHANGED