@glideidentity/glide-be-sdk-node 2.0.1 → 2.1.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.
@@ -0,0 +1,112 @@
1
+ /** Cookie name prefix for device binding codes. Append the session key for the full name. */
2
+ export declare const BINDING_COOKIE_PREFIX = "_glide_bind_";
3
+ /** Cookie max-age in seconds. Set to 5 minutes — sufficient for the carrier
4
+ * authentication round-trip (typically under 2 minutes). The cookie auto-expires
5
+ * after this period, so explicit clearing is optional. */
6
+ export declare const BINDING_COOKIE_MAX_AGE = 300;
7
+ /**
8
+ * Returns the full cookie name for a given session key.
9
+ *
10
+ * Using a session-scoped name (`_glide_bind_{sessionKey}`) ensures that
11
+ * parallel `prepare()` calls from the same browser don't overwrite each
12
+ * other's cookies — each session gets its own cookie.
13
+ */
14
+ export declare function getBindingCookieName(sessionKey: string): string;
15
+ /**
16
+ * Regex matching a valid 64-character hex string (case-insensitive).
17
+ *
18
+ * Accepts both lowercase and uppercase hex so that external inputs (URL
19
+ * fragments, cookie values) still pass validation even if a proxy or
20
+ * browser extension uppercases them. Internal code normalises to
21
+ * lowercase before hashing or sending to the aggregator.
22
+ */
23
+ export declare const HEX_64: RegExp;
24
+ /**
25
+ * Asserts that the given value is a valid 64-character hex string.
26
+ * @throws {Error} If the value does not match.
27
+ */
28
+ export declare function assertValidFeCode(feCode: string): void;
29
+ /**
30
+ * Generates a cryptographically random device binding code.
31
+ * @returns 64-character lowercase hex string (32 random bytes).
32
+ */
33
+ export declare function generateFeCode(): string;
34
+ /**
35
+ * Computes the SHA-256 hash of a device binding code.
36
+ * @param feCode - The 64-character hex binding code (case-insensitive input, normalised to lowercase).
37
+ * @returns 64-character lowercase hex SHA-256 hash.
38
+ */
39
+ export declare function computeFeHash(feCode: string): string;
40
+ export interface BindingCookieOptions {
41
+ /** Cookie domain. Must match the completion redirect URL domain. */
42
+ domain?: string;
43
+ /** Cookie path. Defaults to '/'. */
44
+ path?: string;
45
+ /**
46
+ * Whether to set the Secure flag. Defaults to `true`.
47
+ *
48
+ * Set to `false` only for local development over plain HTTP.
49
+ * In production this MUST remain `true`.
50
+ */
51
+ secure?: boolean;
52
+ }
53
+ /**
54
+ * Builds the Set-Cookie header value to store the device binding code.
55
+ *
56
+ * @param feCode - The binding code to store (64-char hex, normalised to lowercase).
57
+ * @param sessionKey - The session key — used in the cookie name to avoid collisions between parallel sessions.
58
+ * @param options - Optional domain/path overrides.
59
+ * @returns A complete Set-Cookie header value string.
60
+ */
61
+ export declare function buildSetBindingCookieHeader(feCode: string, sessionKey: string, options?: BindingCookieOptions): string;
62
+ /**
63
+ * Builds the Set-Cookie header value to clear the device binding cookie.
64
+ *
65
+ * @param sessionKey - The session key — identifies which session's cookie to clear.
66
+ * @param options - Optional domain/path (must match the original cookie).
67
+ * @returns A complete Set-Cookie header value string that expires the cookie.
68
+ */
69
+ export declare function buildClearBindingCookieHeader(sessionKey: string, options?: BindingCookieOptions): string;
70
+ /**
71
+ * Parses the device binding code from a raw Cookie header string for a specific session.
72
+ *
73
+ * Returns `undefined` if the cookie is not present or the value is not
74
+ * a valid 64-character hex string. When found, the value is normalised
75
+ * to lowercase for deterministic hashing downstream.
76
+ *
77
+ * @param cookieHeader - The raw `Cookie` header value (e.g., "foo=bar; _glide_bind_abc123=...").
78
+ * @param sessionKey - The session key that identifies the cookie to look for.
79
+ * @returns The fe_code value (lowercase), or undefined if the cookie is not present or invalid.
80
+ */
81
+ export declare function parseBindingCookie(cookieHeader: string | undefined, sessionKey: string): string | undefined;
82
+ /**
83
+ * Builds Set-Cookie headers that expire all previous device binding cookies
84
+ * found in the request.
85
+ *
86
+ * Only one Link flow can be active per browser at a time (App Clip is a
87
+ * single native process), so stale cookies from abandoned flows must be
88
+ * cleared to prevent the old redirect from completing with an outdated
89
+ * binding code.
90
+ *
91
+ * @param cookieHeader - The raw `Cookie` request header.
92
+ * @param options - Cookie options (secure, domain, path) — must match the original set call.
93
+ * @returns Array of Set-Cookie header values that expire stale binding cookies.
94
+ */
95
+ export declare function clearStaleBindingCookies(cookieHeader: string | undefined, options?: BindingCookieOptions): string[];
96
+ /**
97
+ * Returns the HTML for the completion redirect page.
98
+ *
99
+ * This page extracts `agg_code` and `session_key` from the URL fragment,
100
+ * POSTs them to the developer's backend (which auto-includes the
101
+ * `_glide_bind_{sessionKey}` HttpOnly cookie), and displays the result.
102
+ *
103
+ * On successful completion, a **localStorage signal** is written
104
+ * (`glide_signal_{session_key}`) so the FE SDK in the original tab can
105
+ * detect completion via `StorageEvent` without polling. The signal is
106
+ * automatically cleaned up after 5 seconds.
107
+ *
108
+ * @param completeEndpoint - The developer backend endpoint to POST to (e.g., "/api/glide/complete").
109
+ * @returns Complete HTML string ready to serve.
110
+ */
111
+ export declare function getCompletionPageHtml(completeEndpoint: string): string;
112
+ //# sourceMappingURL=device-binding.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"device-binding.d.ts","sourceRoot":"","sources":["../src/device-binding.ts"],"names":[],"mappings":"AAMA,6FAA6F;AAC7F,eAAO,MAAM,qBAAqB,iBAAiB,CAAC;AAEpD;;2DAE2D;AAC3D,eAAO,MAAM,sBAAsB,MAAM,CAAC;AAE1C;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAK/D;AAMD;;;;;;;GAOG;AACH,eAAO,MAAM,MAAM,QAAsB,CAAC;AAE1C;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAItD;AAMD;;;GAGG;AACH,wBAAgB,cAAc,IAAI,MAAM,CAEvC;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAIpD;AAMD,MAAM,WAAW,oBAAoB;IACjC,oEAAoE;IACpE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,oCAAoC;IACpC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;;;;OAKG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;CACpB;AAeD;;;;;;;GAOG;AACH,wBAAgB,2BAA2B,CAAC,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,oBAAoB,GAAG,MAAM,CAoBtH;AAED;;;;;;GAMG;AACH,wBAAgB,6BAA6B,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,oBAAoB,GAAG,MAAM,CAoBxG;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,kBAAkB,CAAC,YAAY,EAAE,MAAM,GAAG,SAAS,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAU3G;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,wBAAwB,CAAC,YAAY,EAAE,MAAM,GAAG,SAAS,EAAE,OAAO,CAAC,EAAE,oBAAoB,GAAG,MAAM,EAAE,CAmBnH;AAMD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,qBAAqB,CAAC,gBAAgB,EAAE,MAAM,GAAG,MAAM,CAsJtE"}
@@ -0,0 +1,372 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.HEX_64 = exports.BINDING_COOKIE_MAX_AGE = exports.BINDING_COOKIE_PREFIX = void 0;
4
+ exports.getBindingCookieName = getBindingCookieName;
5
+ exports.assertValidFeCode = assertValidFeCode;
6
+ exports.generateFeCode = generateFeCode;
7
+ exports.computeFeHash = computeFeHash;
8
+ exports.buildSetBindingCookieHeader = buildSetBindingCookieHeader;
9
+ exports.buildClearBindingCookieHeader = buildClearBindingCookieHeader;
10
+ exports.parseBindingCookie = parseBindingCookie;
11
+ exports.clearStaleBindingCookies = clearStaleBindingCookies;
12
+ exports.getCompletionPageHtml = getCompletionPageHtml;
13
+ const crypto_1 = require("crypto");
14
+ // ---------------------------------------------------------------------------
15
+ // Constants
16
+ // ---------------------------------------------------------------------------
17
+ /** Cookie name prefix for device binding codes. Append the session key for the full name. */
18
+ exports.BINDING_COOKIE_PREFIX = '_glide_bind_';
19
+ /** Cookie max-age in seconds. Set to 5 minutes — sufficient for the carrier
20
+ * authentication round-trip (typically under 2 minutes). The cookie auto-expires
21
+ * after this period, so explicit clearing is optional. */
22
+ exports.BINDING_COOKIE_MAX_AGE = 300;
23
+ /**
24
+ * Returns the full cookie name for a given session key.
25
+ *
26
+ * Using a session-scoped name (`_glide_bind_{sessionKey}`) ensures that
27
+ * parallel `prepare()` calls from the same browser don't overwrite each
28
+ * other's cookies — each session gets its own cookie.
29
+ */
30
+ function getBindingCookieName(sessionKey) {
31
+ if (!sessionKey || UNSAFE_COOKIE_ATTR.test(sessionKey) || sessionKey.includes('=')) {
32
+ throw new Error('Invalid sessionKey for cookie name: must not be empty or contain ";", "=", "\\r", or "\\n"');
33
+ }
34
+ return `${exports.BINDING_COOKIE_PREFIX}${sessionKey}`;
35
+ }
36
+ // ---------------------------------------------------------------------------
37
+ // Shared validation
38
+ // ---------------------------------------------------------------------------
39
+ /**
40
+ * Regex matching a valid 64-character hex string (case-insensitive).
41
+ *
42
+ * Accepts both lowercase and uppercase hex so that external inputs (URL
43
+ * fragments, cookie values) still pass validation even if a proxy or
44
+ * browser extension uppercases them. Internal code normalises to
45
+ * lowercase before hashing or sending to the aggregator.
46
+ */
47
+ exports.HEX_64 = /^[0-9a-fA-F]{64}$/;
48
+ /**
49
+ * Asserts that the given value is a valid 64-character hex string.
50
+ * @throws {Error} If the value does not match.
51
+ */
52
+ function assertValidFeCode(feCode) {
53
+ if (!exports.HEX_64.test(feCode)) {
54
+ throw new Error('feCode must be a 64-character hex string');
55
+ }
56
+ }
57
+ // ---------------------------------------------------------------------------
58
+ // Crypto utilities
59
+ // ---------------------------------------------------------------------------
60
+ /**
61
+ * Generates a cryptographically random device binding code.
62
+ * @returns 64-character lowercase hex string (32 random bytes).
63
+ */
64
+ function generateFeCode() {
65
+ return (0, crypto_1.randomBytes)(32).toString('hex');
66
+ }
67
+ /**
68
+ * Computes the SHA-256 hash of a device binding code.
69
+ * @param feCode - The 64-character hex binding code (case-insensitive input, normalised to lowercase).
70
+ * @returns 64-character lowercase hex SHA-256 hash.
71
+ */
72
+ function computeFeHash(feCode) {
73
+ assertValidFeCode(feCode);
74
+ // Normalise to lowercase so the hash is deterministic regardless of input case.
75
+ return (0, crypto_1.createHash)('sha256').update(feCode.toLowerCase()).digest('hex');
76
+ }
77
+ /** Characters that must not appear in cookie attribute values (prevents header/cookie injection). */
78
+ const UNSAFE_COOKIE_ATTR = /[;\r\n]/;
79
+ function validateCookieAttr(name, value) {
80
+ if (value === undefined)
81
+ return;
82
+ if (UNSAFE_COOKIE_ATTR.test(value)) {
83
+ throw new Error(`Invalid cookie ${name}: must not contain ';', '\\r', or '\\n'`);
84
+ }
85
+ if (name === 'path' && (value.length === 0 || !value.startsWith('/'))) {
86
+ throw new Error(`Invalid cookie path: must start with '/'`);
87
+ }
88
+ }
89
+ /**
90
+ * Builds the Set-Cookie header value to store the device binding code.
91
+ *
92
+ * @param feCode - The binding code to store (64-char hex, normalised to lowercase).
93
+ * @param sessionKey - The session key — used in the cookie name to avoid collisions between parallel sessions.
94
+ * @param options - Optional domain/path overrides.
95
+ * @returns A complete Set-Cookie header value string.
96
+ */
97
+ function buildSetBindingCookieHeader(feCode, sessionKey, options) {
98
+ assertValidFeCode(feCode);
99
+ validateCookieAttr('domain', options?.domain);
100
+ validateCookieAttr('path', options?.path);
101
+ const secure = options?.secure ?? true;
102
+ const cookieName = getBindingCookieName(sessionKey);
103
+ const parts = [
104
+ `${cookieName}=${feCode.toLowerCase()}`,
105
+ 'HttpOnly',
106
+ 'SameSite=Lax',
107
+ `Path=${options?.path ?? '/'}`,
108
+ `Max-Age=${exports.BINDING_COOKIE_MAX_AGE}`,
109
+ ];
110
+ if (secure) {
111
+ parts.push('Secure');
112
+ }
113
+ if (options?.domain) {
114
+ parts.push(`Domain=${options.domain}`);
115
+ }
116
+ return parts.join('; ');
117
+ }
118
+ /**
119
+ * Builds the Set-Cookie header value to clear the device binding cookie.
120
+ *
121
+ * @param sessionKey - The session key — identifies which session's cookie to clear.
122
+ * @param options - Optional domain/path (must match the original cookie).
123
+ * @returns A complete Set-Cookie header value string that expires the cookie.
124
+ */
125
+ function buildClearBindingCookieHeader(sessionKey, options) {
126
+ validateCookieAttr('domain', options?.domain);
127
+ validateCookieAttr('path', options?.path);
128
+ const secure = options?.secure ?? true;
129
+ const cookieName = getBindingCookieName(sessionKey);
130
+ const parts = [
131
+ `${cookieName}=`,
132
+ 'HttpOnly',
133
+ 'SameSite=Lax',
134
+ `Path=${options?.path ?? '/'}`,
135
+ 'Max-Age=0',
136
+ 'Expires=Thu, 01 Jan 1970 00:00:00 GMT',
137
+ ];
138
+ if (secure) {
139
+ parts.push('Secure');
140
+ }
141
+ if (options?.domain) {
142
+ parts.push(`Domain=${options.domain}`);
143
+ }
144
+ return parts.join('; ');
145
+ }
146
+ /**
147
+ * Parses the device binding code from a raw Cookie header string for a specific session.
148
+ *
149
+ * Returns `undefined` if the cookie is not present or the value is not
150
+ * a valid 64-character hex string. When found, the value is normalised
151
+ * to lowercase for deterministic hashing downstream.
152
+ *
153
+ * @param cookieHeader - The raw `Cookie` header value (e.g., "foo=bar; _glide_bind_abc123=...").
154
+ * @param sessionKey - The session key that identifies the cookie to look for.
155
+ * @returns The fe_code value (lowercase), or undefined if the cookie is not present or invalid.
156
+ */
157
+ function parseBindingCookie(cookieHeader, sessionKey) {
158
+ if (!cookieHeader)
159
+ return undefined;
160
+ const cookieName = getBindingCookieName(sessionKey);
161
+ const prefix = `${cookieName}=`;
162
+ const match = cookieHeader.split(';')
163
+ .map(c => c.trim())
164
+ .find(c => c.startsWith(prefix));
165
+ if (!match)
166
+ return undefined;
167
+ const value = match.substring(prefix.length);
168
+ return exports.HEX_64.test(value) ? value.toLowerCase() : undefined;
169
+ }
170
+ /**
171
+ * Builds Set-Cookie headers that expire all previous device binding cookies
172
+ * found in the request.
173
+ *
174
+ * Only one Link flow can be active per browser at a time (App Clip is a
175
+ * single native process), so stale cookies from abandoned flows must be
176
+ * cleared to prevent the old redirect from completing with an outdated
177
+ * binding code.
178
+ *
179
+ * @param cookieHeader - The raw `Cookie` request header.
180
+ * @param options - Cookie options (secure, domain, path) — must match the original set call.
181
+ * @returns Array of Set-Cookie header values that expire stale binding cookies.
182
+ */
183
+ function clearStaleBindingCookies(cookieHeader, options) {
184
+ if (!cookieHeader)
185
+ return [];
186
+ const headers = [];
187
+ for (const pair of cookieHeader.split(';')) {
188
+ const name = pair.trim().split('=')[0];
189
+ if (name.startsWith(exports.BINDING_COOKIE_PREFIX)) {
190
+ const sessionSuffix = name.substring(exports.BINDING_COOKIE_PREFIX.length);
191
+ if (!sessionSuffix)
192
+ continue; // Skip empty suffix (malformed cookie name)
193
+ try {
194
+ headers.push(buildClearBindingCookieHeader(sessionSuffix, options));
195
+ }
196
+ catch (_) {
197
+ // Malformed session suffix (injection chars) — skip silently.
198
+ // Option validation errors (domain/path) also caught here, but since
199
+ // options are caller-provided and identical for every iteration,
200
+ // an invalid option means ALL iterations would throw anyway.
201
+ }
202
+ }
203
+ }
204
+ return headers;
205
+ }
206
+ // ---------------------------------------------------------------------------
207
+ // Completion redirect page
208
+ // ---------------------------------------------------------------------------
209
+ /**
210
+ * Returns the HTML for the completion redirect page.
211
+ *
212
+ * This page extracts `agg_code` and `session_key` from the URL fragment,
213
+ * POSTs them to the developer's backend (which auto-includes the
214
+ * `_glide_bind_{sessionKey}` HttpOnly cookie), and displays the result.
215
+ *
216
+ * On successful completion, a **localStorage signal** is written
217
+ * (`glide_signal_{session_key}`) so the FE SDK in the original tab can
218
+ * detect completion via `StorageEvent` without polling. The signal is
219
+ * automatically cleaned up after 5 seconds.
220
+ *
221
+ * @param completeEndpoint - The developer backend endpoint to POST to (e.g., "/api/glide/complete").
222
+ * @returns Complete HTML string ready to serve.
223
+ */
224
+ function getCompletionPageHtml(completeEndpoint) {
225
+ if (!completeEndpoint.startsWith('/') || completeEndpoint.startsWith('//')) {
226
+ throw new Error('completeEndpoint must be a relative path starting with "/" (e.g., "/api/glide/complete"). ' +
227
+ 'Cross-origin and protocol-relative endpoints (e.g., "//evil.com/...") are not supported because ' +
228
+ 'the page relies on credentials: "include" for cookie-based device binding, which requires same-origin requests.');
229
+ }
230
+ return `<!DOCTYPE html>
231
+ <html lang="en">
232
+ <head>
233
+ <meta charset="utf-8">
234
+ <meta name="viewport" content="width=device-width, initial-scale=1">
235
+ <meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'unsafe-inline'; style-src 'unsafe-inline'; connect-src 'self'">
236
+ <title>Verifying</title>
237
+ <style>
238
+ * { box-sizing: border-box; margin: 0; padding: 0; }
239
+ body { font-family: -apple-system, BlinkMacSystemFont, 'SF Pro Text', system-ui, sans-serif; display: flex; justify-content: center; align-items: center; min-height: 100vh; min-height: 100dvh; background: #fff; color: #1a1a1a; -webkit-font-smoothing: antialiased; }
240
+ .spinner-wrap { text-align: center; }
241
+ .spinner { width: 44px; height: 44px; border: 3px solid transparent; border-top-color: #222; border-radius: 50%; animation: spin 0.7s linear infinite; margin: 0 auto; }
242
+ @keyframes spin { to { transform: rotate(360deg); } }
243
+ .hidden { display: none; }
244
+ .hint { margin-top: 1.5rem; font-size: 0.875rem; color: #999; }
245
+ .err { display: none; text-align: center; padding: 2rem 1.5rem; max-width: 340px; }
246
+ .err h2 { font-size: 1.25rem; font-weight: 600; color: #1a1a1a; margin-bottom: 1rem; letter-spacing: -0.02em; line-height: 1.3; }
247
+ .err-points { text-align: left; margin: 0 auto 1.5rem; max-width: 280px; }
248
+ .err-points li { font-size: 0.875rem; line-height: 1.6; color: #555; margin-bottom: 0.375rem; padding-left: 0.25rem; }
249
+ .illust { margin: 0 auto 1.5rem; }
250
+ .illust svg { color: #94a3b8; }
251
+ .illust-browser { width: 140px; height: 90px; position: relative; }
252
+ .illust-browser .b { position: absolute; width: 52px; height: 40px; border: 2px solid #cbd5e1; border-radius: 6px; background: #f8fafc; }
253
+ .illust-browser .b .bar { height: 10px; border-bottom: 1.5px solid #e2e8f0; display: flex; align-items: center; padding: 0 4px; gap: 2px; }
254
+ .illust-browser .b .bar i { width: 3px; height: 3px; border-radius: 50%; background: #cbd5e1; }
255
+ .illust-browser .b1 { left: 10px; top: 16px; } .illust-browser .b2 { right: 10px; top: 16px; }
256
+ .illust-browser .aw { position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%); }
257
+ .illust-browser .aw svg { width: 36px; height: 36px; animation: arrowSpin 2.5s ease-in-out infinite; }
258
+ @keyframes arrowSpin { 0%,100% { transform: rotate(0); } 50% { transform: rotate(180deg); } }
259
+ .illust-retry svg { width: 48px; height: 48px; animation: retryPulse 2s ease-in-out infinite; }
260
+ @keyframes retryPulse { 0%,100% { transform: scale(1); opacity: 0.7; } 50% { transform: scale(1.1); opacity: 1; } }
261
+ .illust-wifi svg { width: 48px; height: 48px; animation: wifiBlink 2s ease-in-out infinite; }
262
+ @keyframes wifiBlink { 0%,100% { opacity: 1; } 50% { opacity: 0.4; } }
263
+ .illust-link svg { width: 48px; height: 48px; animation: linkShake 2s ease-in-out infinite; }
264
+ @keyframes linkShake { 0%,100% { transform: rotate(0); } 25% { transform: rotate(-5deg); } 75% { transform: rotate(5deg); } }
265
+ .badge { display: inline-flex; align-items: center; background: #fff4e6; border: 1px solid #ffd8a8; border-radius: 100px; padding: 5px 14px; font-size: 0.6875rem; font-weight: 600; color: #e67700; text-transform: uppercase; letter-spacing: 0.06em; margin-top: 0.25rem; }
266
+ @keyframes fadeUp { from { opacity: 0; transform: translateY(12px); } to { opacity: 1; transform: translateY(0); } }
267
+ .err.show { display: block; animation: fadeUp 0.35s ease-out; }
268
+ </style>
269
+ </head>
270
+ <body>
271
+ <div class="spinner-wrap" id="loading" role="status" aria-live="polite">
272
+ <div class="spinner"></div>
273
+ <p class="hint hidden" id="hint"></p>
274
+ </div>
275
+ <div class="err" id="error" role="alert" aria-live="assertive">
276
+ <h2 id="err-title"></h2>
277
+ <ol class="err-points" id="err-points"></ol>
278
+ <div class="illust illust-browser hidden" id="ill-browser">
279
+ <div class="illust-browser">
280
+ <div class="b b1"><div class="bar"><i></i><i></i><i></i></div></div>
281
+ <div class="b b2"><div class="bar"><i></i><i></i><i></i></div></div>
282
+ <div class="aw"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="23 4 23 10 17 10"/><polyline points="1 20 1 14 7 14"/><path d="M3.51 9a9 9 0 0114.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0020.49 15"/></svg></div>
283
+ </div>
284
+ </div>
285
+ <div class="illust illust-retry hidden" id="ill-retry">
286
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="23 4 23 10 17 10"/><path d="M20.49 15a9 9 0 11-2.12-9.36L23 10"/></svg>
287
+ </div>
288
+ <div class="illust illust-wifi hidden" id="ill-wifi">
289
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M1 1l22 22M16.72 11.06A10.94 10.94 0 0119 12.55M5 12.55a10.94 10.94 0 015.17-2.39M10.71 5.05A16 16 0 0122.56 9M1.42 9a15.91 15.91 0 014.7-2.88M8.53 16.11a6 6 0 016.95 0M12 20h.01"/></svg>
290
+ </div>
291
+ <div class="illust illust-link hidden" id="ill-link">
292
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M15 7h3a5 5 0 010 10h-3m-6 0H6a5 5 0 010-10h3"/><line x1="8" y1="12" x2="16" y2="12"/></svg>
293
+ </div>
294
+ <div class="badge">Action required</div>
295
+ </div>
296
+ <script>
297
+ (async function() {
298
+ var fragment = new URLSearchParams(window.location.hash.substring(1));
299
+ var aggCode = fragment.get('agg_code');
300
+ var sessionKey = fragment.get('session_key');
301
+
302
+ window.history.replaceState(null, '', window.location.pathname + window.location.search);
303
+
304
+ if (!aggCode || !sessionKey) {
305
+ showError('Invalid verification link', ['This verification link is not valid.', 'Please go back and try again.'], 'link');
306
+ return;
307
+ }
308
+
309
+ try {
310
+ var res = await fetch(${JSON.stringify(completeEndpoint)}, {
311
+ method: 'POST',
312
+ credentials: 'include',
313
+ headers: { 'Content-Type': 'application/json' },
314
+ body: JSON.stringify({ agg_code: aggCode, session_key: sessionKey, user_agent: navigator.userAgent })
315
+ });
316
+
317
+ if (res.ok) {
318
+ var signalKey = 'glide_signal_' + sessionKey;
319
+ try {
320
+ localStorage.setItem(signalKey, sessionKey);
321
+ setTimeout(function() {
322
+ try { localStorage.removeItem(signalKey); } catch(e) {}
323
+ }, 5000);
324
+ } catch(e) {}
325
+ tryClose();
326
+ } else {
327
+ var code = '';
328
+ try { var data = await res.json(); code = (data && (data.code || data.error)) || ''; } catch (_) {}
329
+ var err = friendlyError(code);
330
+ showError(err.title, err.points, err.illust);
331
+ }
332
+ } catch (e) {
333
+ showError('Connection issue', ['We could not reach the server.', 'Check your internet connection and try again.'], 'wifi');
334
+ }
335
+
336
+ function friendlyError(code) {
337
+ switch (code) {
338
+ case 'MISSING_BINDING_COOKIE':
339
+ case 'BROWSER_MISMATCH':
340
+ return { title: 'Open in your default browser', points: ['Carrier verification must be started from your default browser.', 'The redirect will always open in your default browser to complete.'], illust: 'browser' };
341
+ default:
342
+ return { title: 'Verification could not be completed', points: ['Something went wrong during verification.', 'Please go back and try again.'], illust: 'retry' };
343
+ }
344
+ }
345
+
346
+ function tryClose() {
347
+ document.querySelector('.spinner').classList.add('hidden');
348
+ window.close();
349
+ try { window.open('', '_self').close(); } catch(e) {}
350
+ setTimeout(function() {
351
+ document.getElementById('hint').textContent = 'You may close this tab.';
352
+ document.getElementById('hint').classList.remove('hidden');
353
+ }, 300);
354
+ }
355
+
356
+ function showError(title, points, illust) {
357
+ document.getElementById('loading').classList.add('hidden');
358
+ document.getElementById('err-title').textContent = title;
359
+ var ol = document.getElementById('err-points');
360
+ ol.innerHTML = '';
361
+ (points || []).forEach(function(p) { var li = document.createElement('li'); li.textContent = p; ol.appendChild(li); });
362
+ var ids = ['ill-browser','ill-retry','ill-wifi','ill-link'];
363
+ ids.forEach(function(id) { document.getElementById(id).classList.add('hidden'); });
364
+ var el = document.getElementById('ill-' + (illust || 'retry'));
365
+ if (el) el.classList.remove('hidden');
366
+ document.getElementById('error').classList.add('show');
367
+ }
368
+ })();
369
+ </script>
370
+ </body>
371
+ </html>`;
372
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,uCAAuC,CAAC;AAClE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uCAAuC,CAAC;AAE3E,OAAO,EAAE,SAAS,EAAE,CAAC;AACrB,YAAY,EAAE,aAAa,EAAE,CAAC;AAE9B;;;;;;;;;;;GAWG;AACH,qBAAa,gBAAiB,SAAQ,KAAK;IACvC,SAAgB,IAAI,sBAAsB;IAC1C,+CAA+C;IAC/C,SAAgB,IAAI,EAAE,MAAM,CAAC;IAC7B,wBAAwB;IACxB,SAAgB,MAAM,EAAE,MAAM,CAAC;IAC/B,gCAAgC;IAChC,SAAgB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnC,SAAgB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnC,SAAgB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjC,SAAgB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChC,SAAgB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjC,SAAgB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;gBAEtC,aAAa,EAAE;QACvB,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;KACrC;IAsBD,kDAAkD;IAClD,EAAE,CAAC,IAAI,EAAE,aAAa,GAAG,OAAO;IAIhC,MAAM;;;;;;;;;;;CAqBT"}
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,uCAAuC,CAAC;AAClE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uCAAuC,CAAC;AAE3E,OAAO,EAAE,SAAS,EAAE,CAAC;AACrB,YAAY,EAAE,aAAa,EAAE,CAAC;AAE9B;;;;;;;;;;;GAWG;AACH,qBAAa,gBAAiB,SAAQ,KAAK;IACvC,SAAgB,IAAI,sBAAsB;IAC1C,+CAA+C;IAC/C,SAAgB,IAAI,EAAE,MAAM,CAAC;IAC7B,wBAAwB;IACxB,SAAgB,MAAM,EAAE,MAAM,CAAC;IAC/B,gCAAgC;IAChC,SAAgB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnC,SAAgB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnC,SAAgB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjC,SAAgB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChC,SAAgB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjC,SAAgB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;gBAEtC,aAAa,EAAE;QACvB,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;KACrC;IAsBD,kDAAkD;IAClD,EAAE,CAAC,IAAI,EAAE,aAAa,GAAG,OAAO;IAIhC,MAAM;;;;;;;;;;;CAaT"}
package/dist/errors.js CHANGED
@@ -52,14 +52,5 @@ class MagicalAuthError extends Error {
52
52
  details: this.details
53
53
  };
54
54
  }
55
- /** Custom inspect for cleaner console output (Node.js). */
56
- [Symbol.for('nodejs.util.inspect.custom')]() {
57
- const props = [`code: '${this.code}'`, `status: ${this.status}`];
58
- if (this.requestId)
59
- props.push(`requestId: '${this.requestId}'`);
60
- if (this.details)
61
- props.push(`details: ${JSON.stringify(this.details)}`);
62
- return `MagicalAuthError: ${this.message} { ${props.join(', ')} }`;
63
- }
64
55
  }
65
56
  exports.MagicalAuthError = MagicalAuthError;
package/dist/index.d.ts CHANGED
@@ -1,11 +1,13 @@
1
1
  export * from '@glideidentity/glide-be-sdk-node-core';
2
- export type { GlideSdkSettings, LogFormat, TokenResponse, CachedToken } from './types';
2
+ export type { GlideSdkSettings, LogFormat, TokenResponse, CachedToken, PrepareResult } from './types';
3
3
  export { GlideClient } from './glide';
4
4
  export type { Logger, LogField } from './logger';
5
5
  export { LogLevel, createLogger, generateRequestId } from './logger';
6
6
  export { MagicalAuthError } from './errors';
7
7
  export { validatePhoneNumber, validatePlmn, validateUseCaseRequirements } from './validation';
8
8
  export type { ValidationResult } from './validation';
9
+ export { BINDING_COOKIE_PREFIX, BINDING_COOKIE_MAX_AGE, getBindingCookieName, HEX_64, assertValidFeCode, generateFeCode, computeFeHash, buildSetBindingCookieHeader, buildClearBindingCookieHeader, parseBindingCookie, clearStaleBindingCookies, getCompletionPageHtml, } from './device-binding';
10
+ export type { BindingCookieOptions } from './device-binding';
9
11
  export { request, FetchError } from './internal/http';
10
12
  export type { RequestOptions, HttpResponse } from './internal/http';
11
13
  export { getStatusUrl } from './utils';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,cAAc,uCAAuC,CAAC;AAGtD,YAAY,EAAE,gBAAgB,EAAE,SAAS,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAGvF,OAAO,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAGtC,YAAY,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AACjD,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAC;AAGrE,OAAO,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAC;AAG5C,OAAO,EAAE,mBAAmB,EAAE,YAAY,EAAE,2BAA2B,EAAE,MAAM,cAAc,CAAC;AAC9F,YAAY,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAGrD,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AACtD,YAAY,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAGpE,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,cAAc,uCAAuC,CAAC;AAGtD,YAAY,EAAE,gBAAgB,EAAE,SAAS,EAAE,aAAa,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAGtG,OAAO,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAGtC,YAAY,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AACjD,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAC;AAGrE,OAAO,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAC;AAG5C,OAAO,EAAE,mBAAmB,EAAE,YAAY,EAAE,2BAA2B,EAAE,MAAM,cAAc,CAAC;AAC9F,YAAY,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAGrD,OAAO,EACH,qBAAqB,EACrB,sBAAsB,EACtB,oBAAoB,EACpB,MAAM,EACN,iBAAiB,EACjB,cAAc,EACd,aAAa,EACb,2BAA2B,EAC3B,6BAA6B,EAC7B,kBAAkB,EAClB,wBAAwB,EACxB,qBAAqB,GACxB,MAAM,kBAAkB,CAAC;AAC1B,YAAY,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAG7D,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AACtD,YAAY,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAGpE,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC"}
package/dist/index.js CHANGED
@@ -14,7 +14,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
14
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
- exports.getStatusUrl = exports.FetchError = exports.request = exports.validateUseCaseRequirements = exports.validatePlmn = exports.validatePhoneNumber = exports.MagicalAuthError = exports.generateRequestId = exports.createLogger = exports.LogLevel = exports.GlideClient = void 0;
17
+ exports.getStatusUrl = exports.FetchError = exports.request = exports.getCompletionPageHtml = exports.clearStaleBindingCookies = exports.parseBindingCookie = exports.buildClearBindingCookieHeader = exports.buildSetBindingCookieHeader = exports.computeFeHash = exports.generateFeCode = exports.assertValidFeCode = exports.HEX_64 = exports.getBindingCookieName = exports.BINDING_COOKIE_MAX_AGE = exports.BINDING_COOKIE_PREFIX = exports.validateUseCaseRequirements = exports.validatePlmn = exports.validatePhoneNumber = exports.MagicalAuthError = exports.generateRequestId = exports.createLogger = exports.LogLevel = exports.GlideClient = void 0;
18
18
  // Re-export all core types and constants (single source of truth)
19
19
  __exportStar(require("@glideidentity/glide-be-sdk-node-core"), exports);
20
20
  // Main client
@@ -32,6 +32,20 @@ var validation_1 = require("./validation");
32
32
  Object.defineProperty(exports, "validatePhoneNumber", { enumerable: true, get: function () { return validation_1.validatePhoneNumber; } });
33
33
  Object.defineProperty(exports, "validatePlmn", { enumerable: true, get: function () { return validation_1.validatePlmn; } });
34
34
  Object.defineProperty(exports, "validateUseCaseRequirements", { enumerable: true, get: function () { return validation_1.validateUseCaseRequirements; } });
35
+ // Device binding utilities
36
+ var device_binding_1 = require("./device-binding");
37
+ Object.defineProperty(exports, "BINDING_COOKIE_PREFIX", { enumerable: true, get: function () { return device_binding_1.BINDING_COOKIE_PREFIX; } });
38
+ Object.defineProperty(exports, "BINDING_COOKIE_MAX_AGE", { enumerable: true, get: function () { return device_binding_1.BINDING_COOKIE_MAX_AGE; } });
39
+ Object.defineProperty(exports, "getBindingCookieName", { enumerable: true, get: function () { return device_binding_1.getBindingCookieName; } });
40
+ Object.defineProperty(exports, "HEX_64", { enumerable: true, get: function () { return device_binding_1.HEX_64; } });
41
+ Object.defineProperty(exports, "assertValidFeCode", { enumerable: true, get: function () { return device_binding_1.assertValidFeCode; } });
42
+ Object.defineProperty(exports, "generateFeCode", { enumerable: true, get: function () { return device_binding_1.generateFeCode; } });
43
+ Object.defineProperty(exports, "computeFeHash", { enumerable: true, get: function () { return device_binding_1.computeFeHash; } });
44
+ Object.defineProperty(exports, "buildSetBindingCookieHeader", { enumerable: true, get: function () { return device_binding_1.buildSetBindingCookieHeader; } });
45
+ Object.defineProperty(exports, "buildClearBindingCookieHeader", { enumerable: true, get: function () { return device_binding_1.buildClearBindingCookieHeader; } });
46
+ Object.defineProperty(exports, "parseBindingCookie", { enumerable: true, get: function () { return device_binding_1.parseBindingCookie; } });
47
+ Object.defineProperty(exports, "clearStaleBindingCookies", { enumerable: true, get: function () { return device_binding_1.clearStaleBindingCookies; } });
48
+ Object.defineProperty(exports, "getCompletionPageHtml", { enumerable: true, get: function () { return device_binding_1.getCompletionPageHtml; } });
35
49
  // HTTP utilities - for advanced usage and dev-server
36
50
  var http_1 = require("./internal/http");
37
51
  Object.defineProperty(exports, "request", { enumerable: true, get: function () { return http_1.request; } });
@@ -1 +1 @@
1
- {"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../src/logger.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAEzC,2BAA2B;AAC3B,oBAAY,QAAQ;IAChB,KAAK,IAAI;IACT,IAAI,IAAI;IACR,IAAI,IAAI;IACR,KAAK,IAAI;CACZ;AAED,4BAA4B;AAC5B,MAAM,WAAW,QAAQ;IACrB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CAC1B;AAED,mDAAmD;AACnD,MAAM,WAAW,MAAM;IACnB,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,QAAQ,GAAG,IAAI,CAAC;IAChD,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,QAAQ,GAAG,IAAI,CAAC;IAC/C,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,QAAQ,GAAG,IAAI,CAAC;IAC/C,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,QAAQ,GAAG,IAAI,CAAC;CACnD;AAiHD;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,QAAQ,CAAC,EAAE,QAAQ,EAAE,SAAS,CAAC,EAAE,SAAS,GAAG,MAAM,CAG/E;AAED,qCAAqC;AACrC,wBAAgB,iBAAiB,IAAI,MAAM,CAE1C"}
1
+ {"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../src/logger.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAEzC,2BAA2B;AAC3B,oBAAY,QAAQ;IAChB,KAAK,IAAI;IACT,IAAI,IAAI;IACR,IAAI,IAAI;IACR,KAAK,IAAI;CACZ;AAED,4BAA4B;AAC5B,MAAM,WAAW,QAAQ;IACrB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CAC1B;AAED,mDAAmD;AACnD,MAAM,WAAW,MAAM;IACnB,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,QAAQ,GAAG,IAAI,CAAC;IAChD,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,QAAQ,GAAG,IAAI,CAAC;IAC/C,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,QAAQ,GAAG,IAAI,CAAC;IAC/C,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,QAAQ,GAAG,IAAI,CAAC;CACnD;AAmHD;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,QAAQ,CAAC,EAAE,QAAQ,EAAE,SAAS,CAAC,EAAE,SAAS,GAAG,MAAM,CAG/E;AAED,qCAAqC;AACrC,wBAAgB,iBAAiB,IAAI,MAAM,CAE1C"}
package/dist/logger.js CHANGED
@@ -13,7 +13,9 @@ var LogLevel;
13
13
  })(LogLevel || (exports.LogLevel = LogLevel = {}));
14
14
  const SENSITIVE_KEYS = [
15
15
  'apikey', 'api_key', 'api-key', 'token', 'password', 'secret',
16
- 'credential', 'authorization', 'bearer', 'key'
16
+ 'credential', 'authorization', 'bearer', 'key',
17
+ 'fe_code', 'fecode', 'fe_hash', 'fehash', 'agg_code', 'aggcode',
18
+ '_glide_bind', 'glide_bind'
17
19
  ];
18
20
  const SENSITIVE_PATTERN = new RegExp(`(${SENSITIVE_KEYS.join('|')})([=:\\s"']+)([^\\s"'&,}{\\]\\)]+)`, 'gi');
19
21
  function sanitizeMessage(message) {
@@ -1,15 +1,21 @@
1
- import { GlideSdkSettings } from '../types';
1
+ import { GlideSdkSettings, PrepareResult } from '../types';
2
2
  import { Logger } from '../logger';
3
3
  import { OAuthManager } from '../internal/oauth';
4
- import type { PrepareRequest, PrepareResponse, VerifyPhoneNumberRequest, VerifyPhoneNumberResponse, GetPhoneNumberRequest, GetPhoneNumberResponse, ReportInvocationRequest, ReportInvocationResponse } from '@glideidentity/glide-be-sdk-node-core';
5
- /** Handles SIM-based phone authentication via the Glide API. */
4
+ import type { PrepareRequest, CompleteRequest, VerifyPhoneNumberRequest, VerifyPhoneNumberResponse, GetPhoneNumberRequest, GetPhoneNumberResponse, ReportInvocationRequest, ReportInvocationResponse, StatusResponse } from '@glideidentity/glide-be-sdk-node-core';
6
5
  export declare class MagicalAuth {
7
6
  private settings;
8
7
  private oauthClient;
9
8
  private logger;
10
9
  constructor(settings: GlideSdkSettings, oauthClient: OAuthManager, logger: Logger);
11
- /** Initiates a phone authentication session. */
12
- prepare(req: PrepareRequest): Promise<PrepareResponse>;
10
+ /**
11
+ * Initiates a phone authentication session.
12
+ *
13
+ * For link protocol sessions, this method automatically generates a device
14
+ * binding code (`feCode`) and includes its hash in the aggregator request.
15
+ * When `feCode` is present in the result, the caller MUST set it as an
16
+ * HttpOnly cookie — use `buildSetBindingCookieHeader()` from `device-binding`.
17
+ */
18
+ prepare(req: PrepareRequest): Promise<PrepareResult>;
13
19
  /** Verifies a phone number matches the device. */
14
20
  verifyPhoneNumber(req: VerifyPhoneNumberRequest): Promise<VerifyPhoneNumberResponse>;
15
21
  /** Retrieves the phone number. */
@@ -27,6 +33,28 @@ export declare class MagicalAuth {
27
33
  * @returns Response with success status
28
34
  */
29
35
  reportInvocation(req: ReportInvocationRequest): Promise<ReportInvocationResponse>;
36
+ /**
37
+ * Completes a device-bound authentication session.
38
+ *
39
+ * Call this after the completion redirect page POSTs `agg_code` and `session_key`
40
+ * to the developer backend. The `fe_code` should be read from the `_glide_bind`
41
+ * HttpOnly cookie — use `parseBindingCookie()` from `device-binding`.
42
+ *
43
+ * The complete endpoint does not return verification results. The client-side
44
+ * poller detects the session status change to `completed` and calls `/process`.
45
+ *
46
+ * @param req - The complete request containing session_key, fe_code, and agg_code.
47
+ */
48
+ complete(req: CompleteRequest): Promise<void>;
49
+ /**
50
+ * Checks the status of an authentication session.
51
+ *
52
+ * Use this to poll for completion of async flows (link/desktop).
53
+ * This is the authenticated version — requires a Bearer token.
54
+ *
55
+ * @param sessionKey - The 16-character hex session key from the prepare response.
56
+ */
57
+ checkStatus(sessionKey: string): Promise<StatusResponse>;
30
58
  private handleError;
31
59
  }
32
60
  //# sourceMappingURL=magical-auth.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"magical-auth.d.ts","sourceRoot":"","sources":["../../src/services/magical-auth.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAC;AAC5C,OAAO,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AAEnC,OAAO,EAAE,YAAY,EAAc,MAAM,mBAAmB,CAAC;AAG7D,OAAO,KAAK,EACR,cAAc,EACd,eAAe,EACf,wBAAwB,EACxB,yBAAyB,EACzB,qBAAqB,EACrB,sBAAsB,EACtB,uBAAuB,EACvB,wBAAwB,EAC3B,MAAM,uCAAuC,CAAC;AAa/C,gEAAgE;AAChE,qBAAa,WAAW;IACpB,OAAO,CAAC,QAAQ,CAAmB;IACnC,OAAO,CAAC,WAAW,CAAe;IAClC,OAAO,CAAC,MAAM,CAAS;gBAEX,QAAQ,EAAE,gBAAgB,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,EAAE,MAAM;IAMjF,gDAAgD;IAC1C,OAAO,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,eAAe,CAAC;IAkF5D,kDAAkD;IAC5C,iBAAiB,CAAC,GAAG,EAAE,wBAAwB,GAAG,OAAO,CAAC,yBAAyB,CAAC;IA8C1F,kCAAkC;IAC5B,cAAc,CAAC,GAAG,EAAE,qBAAqB,GAAG,OAAO,CAAC,sBAAsB,CAAC;IA8CjF;;;;;;;;;;;OAWG;IACG,gBAAgB,CAAC,GAAG,EAAE,uBAAuB,GAAG,OAAO,CAAC,wBAAwB,CAAC;IAsCvF,OAAO,CAAC,WAAW;CAyCtB"}
1
+ {"version":3,"file":"magical-auth.d.ts","sourceRoot":"","sources":["../../src/services/magical-auth.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAC3D,OAAO,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AAEnC,OAAO,EAAE,YAAY,EAAc,MAAM,mBAAmB,CAAC;AAK7D,OAAO,KAAK,EACR,cAAc,EAEd,eAAe,EACf,wBAAwB,EACxB,yBAAyB,EACzB,qBAAqB,EACrB,sBAAsB,EACtB,uBAAuB,EACvB,wBAAwB,EACxB,cAAc,EACjB,MAAM,uCAAuC,CAAC;AA+B/C,qBAAa,WAAW;IACpB,OAAO,CAAC,QAAQ,CAAmB;IACnC,OAAO,CAAC,WAAW,CAAe;IAClC,OAAO,CAAC,MAAM,CAAS;gBAEX,QAAQ,EAAE,gBAAgB,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,EAAE,MAAM;IAMjF;;;;;;;OAOG;IACG,OAAO,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,aAAa,CAAC;IAkG1D,kDAAkD;IAC5C,iBAAiB,CAAC,GAAG,EAAE,wBAAwB,GAAG,OAAO,CAAC,yBAAyB,CAAC;IAkD1F,kCAAkC;IAC5B,cAAc,CAAC,GAAG,EAAE,qBAAqB,GAAG,OAAO,CAAC,sBAAsB,CAAC;IAgDjF;;;;;;;;;;;OAWG;IACG,gBAAgB,CAAC,GAAG,EAAE,uBAAuB,GAAG,OAAO,CAAC,wBAAwB,CAAC;IAsCvF;;;;;;;;;;;OAWG;IACG,QAAQ,CAAC,GAAG,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC;IAiEnD;;;;;;;OAOG;IACG,WAAW,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC;IAgC9D,OAAO,CAAC,WAAW;CAyCtB"}
@@ -5,6 +5,8 @@ const http_1 = require("../internal/http");
5
5
  const oauth_1 = require("../internal/oauth");
6
6
  const errors_1 = require("../errors");
7
7
  const validation_1 = require("../validation");
8
+ const device_binding_1 = require("../device-binding");
9
+ const glide_be_sdk_node_core_1 = require("@glideidentity/glide-be-sdk-node-core");
8
10
  function generateNonce(length = 32) {
9
11
  const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
10
12
  let result = '';
@@ -16,13 +18,36 @@ function generateNonce(length = 32) {
16
18
  return result;
17
19
  }
18
20
  /** Handles SIM-based phone authentication via the Glide API. */
21
+ /**
22
+ * Validates and appends fe_code to a request body if present.
23
+ * @throws {MagicalAuthError} if fe_code is not a valid 64-character hex string.
24
+ */
25
+ function applyFeCode(body, feCode) {
26
+ if (feCode !== undefined) {
27
+ if (!device_binding_1.HEX_64.test(feCode)) {
28
+ throw new errors_1.MagicalAuthError({
29
+ code: errors_1.ErrorCode.INVALID_CREDENTIAL,
30
+ message: 'fe_code must be a 64-character hex string',
31
+ status: 400,
32
+ });
33
+ }
34
+ body.fe_code = feCode;
35
+ }
36
+ }
19
37
  class MagicalAuth {
20
38
  constructor(settings, oauthClient, logger) {
21
39
  this.settings = settings;
22
40
  this.oauthClient = oauthClient;
23
41
  this.logger = logger;
24
42
  }
25
- /** Initiates a phone authentication session. */
43
+ /**
44
+ * Initiates a phone authentication session.
45
+ *
46
+ * For link protocol sessions, this method automatically generates a device
47
+ * binding code (`feCode`) and includes its hash in the aggregator request.
48
+ * When `feCode` is present in the result, the caller MUST set it as an
49
+ * HttpOnly cookie — use `buildSetBindingCookieHeader()` from `device-binding`.
50
+ */
26
51
  async prepare(req) {
27
52
  const hasParentSession = req.options?.parent_session_id;
28
53
  if (!hasParentSession) {
@@ -31,8 +56,7 @@ class MagicalAuth {
31
56
  throw new errors_1.MagicalAuthError({
32
57
  code: useCaseValidation.errorCode || errors_1.ErrorCode.INVALID_USE_CASE,
33
58
  message: useCaseValidation.error,
34
- status: 400,
35
- details: useCaseValidation.details
59
+ status: 400
36
60
  });
37
61
  }
38
62
  }
@@ -56,24 +80,29 @@ class MagicalAuth {
56
80
  });
57
81
  }
58
82
  }
83
+ // Generate device binding code and hash for potential link protocol sessions.
84
+ // Always sent — the aggregator ignores it for non-link strategies (TS43, desktop).
85
+ const feCode = (0, device_binding_1.generateFeCode)();
86
+ const feHash = (0, device_binding_1.computeFeHash)(feCode);
59
87
  const nonce = req.nonce || generateNonce();
60
- const body = { nonce };
61
- if (req.use_case)
62
- body.use_case = req.use_case;
63
- if (req.phone_number)
64
- body.phone_number = req.phone_number;
65
- if (req.plmn)
66
- body.plmn = req.plmn;
67
- if (req.consent_data)
68
- body.consent_data = req.consent_data;
69
- if (req.client_info) {
70
- body.client_info = {
71
- user_agent: req.client_info.user_agent || 'unknown',
72
- platform: req.client_info.platform || 'unknown'
73
- };
74
- }
75
- if (req.options)
76
- body.options = req.options;
88
+ // Only include optional fields when truthy to avoid sending null / ""
89
+ // to the aggregator — JSON.stringify serializes null and "" but the
90
+ // aggregator may reject or misinterpret them.
91
+ const body = {
92
+ nonce,
93
+ fe_hash: feHash,
94
+ ...(req.use_case ? { use_case: req.use_case } : undefined),
95
+ ...(req.phone_number ? { phone_number: req.phone_number } : undefined),
96
+ ...(req.plmn ? { plmn: req.plmn } : undefined),
97
+ ...(req.consent_data ? { consent_data: req.consent_data } : undefined),
98
+ ...(req.options ? { options: req.options } : undefined),
99
+ };
100
+ // Always include client_info so the aggregator can compare the
101
+ // browser user-agent between /prepare and /complete (browser mismatch detection).
102
+ body.client_info = {
103
+ user_agent: req.client_info?.user_agent || 'unknown',
104
+ platform: req.client_info?.platform || 'unknown'
105
+ };
77
106
  const url = `${this.settings.baseUrl}/magic-auth/v2/auth/prepare`;
78
107
  this.logger.debug('Prepare request', { url });
79
108
  try {
@@ -89,7 +118,12 @@ class MagicalAuth {
89
118
  });
90
119
  const result = response.json();
91
120
  this.logger.debug('Prepare completed', { strategy: result.authentication_strategy });
92
- return result;
121
+ // Only expose feCode when the session uses link protocol (device binding).
122
+ const prepareResult = { ...result };
123
+ if (result.authentication_strategy === glide_be_sdk_node_core_1.AuthenticationStrategy.LINK) {
124
+ prepareResult.feCode = feCode;
125
+ }
126
+ return prepareResult;
93
127
  }
94
128
  catch (error) {
95
129
  this.handleError(error);
@@ -115,6 +149,10 @@ class MagicalAuth {
115
149
  session: req.session,
116
150
  credential: req.credential,
117
151
  };
152
+ // Forward device binding code when provided. For device-bound sessions (link
153
+ // protocol), the server validates this against the hash committed during prepare(),
154
+ // extending device binding verification through the entire flow.
155
+ applyFeCode(body, req.fe_code);
118
156
  const url = `${this.settings.baseUrl}/magic-auth/v2/auth/verify-phone-number`;
119
157
  this.logger.debug('VerifyPhoneNumber request', { url });
120
158
  try {
@@ -156,6 +194,8 @@ class MagicalAuth {
156
194
  session: req.session,
157
195
  credential: req.credential,
158
196
  };
197
+ // Forward device binding code when provided (see verifyPhoneNumber for details).
198
+ applyFeCode(body, req.fe_code);
159
199
  const url = `${this.settings.baseUrl}/magic-auth/v2/auth/get-phone-number`;
160
200
  this.logger.debug('GetPhoneNumber request', { url });
161
201
  try {
@@ -214,7 +254,112 @@ class MagicalAuth {
214
254
  body: JSON.stringify(body),
215
255
  });
216
256
  const result = await response.json();
217
- this.logger.debug('ReportInvocation completed', { session_id: req.session_id, success: result.success });
257
+ this.logger.debug('ReportInvocation completed', { session_id: req.session_id, status: result.status });
258
+ return result;
259
+ }
260
+ catch (error) {
261
+ this.handleError(error);
262
+ }
263
+ }
264
+ /**
265
+ * Completes a device-bound authentication session.
266
+ *
267
+ * Call this after the completion redirect page POSTs `agg_code` and `session_key`
268
+ * to the developer backend. The `fe_code` should be read from the `_glide_bind`
269
+ * HttpOnly cookie — use `parseBindingCookie()` from `device-binding`.
270
+ *
271
+ * The complete endpoint does not return verification results. The client-side
272
+ * poller detects the session status change to `completed` and calls `/process`.
273
+ *
274
+ * @param req - The complete request containing session_key, fe_code, and agg_code.
275
+ */
276
+ async complete(req) {
277
+ if (!req.session_key) {
278
+ throw new errors_1.MagicalAuthError({
279
+ code: errors_1.ErrorCode.MISSING_REQUIRED_FIELD,
280
+ message: 'session_key is required',
281
+ status: 400
282
+ });
283
+ }
284
+ if (!req.fe_code) {
285
+ throw new errors_1.MagicalAuthError({
286
+ code: errors_1.ErrorCode.MISSING_BINDING_COOKIE,
287
+ message: 'fe_code is required (from _glide_bind cookie)',
288
+ status: 403
289
+ });
290
+ }
291
+ if (!req.agg_code) {
292
+ throw new errors_1.MagicalAuthError({
293
+ code: errors_1.ErrorCode.MISSING_REQUIRED_FIELD,
294
+ message: 'agg_code is required',
295
+ status: 400
296
+ });
297
+ }
298
+ // Validate format — both codes must be 64-char lowercase hex.
299
+ // Use a generic 403 to avoid leaking which value failed.
300
+ if (!device_binding_1.HEX_64.test(req.fe_code) || !device_binding_1.HEX_64.test(req.agg_code)) {
301
+ throw new errors_1.MagicalAuthError({
302
+ code: errors_1.ErrorCode.DEVICE_BINDING_FAILED,
303
+ message: 'Invalid device binding inputs',
304
+ status: 403
305
+ });
306
+ }
307
+ const body = {
308
+ session_key: req.session_key,
309
+ fe_code: req.fe_code,
310
+ agg_code: req.agg_code,
311
+ };
312
+ if (req.user_agent) {
313
+ body.user_agent = req.user_agent;
314
+ }
315
+ const url = `${this.settings.baseUrl}/magic-auth/v2/auth/complete`;
316
+ this.logger.debug('Complete request', { url });
317
+ try {
318
+ const accessToken = await this.oauthClient.getAccessToken();
319
+ await (0, http_1.request)(url, {
320
+ method: 'POST',
321
+ headers: {
322
+ 'Content-Type': 'application/json',
323
+ 'Accept': 'application/json',
324
+ 'Authorization': `Bearer ${accessToken}`,
325
+ },
326
+ body: JSON.stringify(body),
327
+ });
328
+ this.logger.debug('Complete succeeded');
329
+ }
330
+ catch (error) {
331
+ this.handleError(error);
332
+ }
333
+ }
334
+ /**
335
+ * Checks the status of an authentication session.
336
+ *
337
+ * Use this to poll for completion of async flows (link/desktop).
338
+ * This is the authenticated version — requires a Bearer token.
339
+ *
340
+ * @param sessionKey - The 16-character hex session key from the prepare response.
341
+ */
342
+ async checkStatus(sessionKey) {
343
+ if (!sessionKey) {
344
+ throw new errors_1.MagicalAuthError({
345
+ code: errors_1.ErrorCode.MISSING_REQUIRED_FIELD,
346
+ message: 'sessionKey is required',
347
+ status: 400
348
+ });
349
+ }
350
+ const url = `${this.settings.baseUrl}/magic-auth/v2/auth/status/${sessionKey}`;
351
+ this.logger.debug('CheckStatus request', { url, sessionKey });
352
+ try {
353
+ const accessToken = await this.oauthClient.getAccessToken();
354
+ const response = await (0, http_1.request)(url, {
355
+ method: 'GET',
356
+ headers: {
357
+ 'Accept': 'application/json',
358
+ 'Authorization': `Bearer ${accessToken}`,
359
+ },
360
+ });
361
+ const result = response.json();
362
+ this.logger.debug('CheckStatus completed', { sessionKey, status: result.status });
218
363
  return result;
219
364
  }
220
365
  catch (error) {
package/dist/types.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { Logger, LogLevel } from './logger';
2
+ import type { PrepareResponse } from '@glideidentity/glide-be-sdk-node-core';
2
3
  /** Log output format. */
3
4
  export type LogFormat = 'json' | 'text';
4
5
  /** OAuth2 token response from the token endpoint. */
@@ -19,6 +20,24 @@ export interface CachedToken {
19
20
  /** Timestamp (ms) when the token expires. */
20
21
  expiresAt: number;
21
22
  }
23
+ /**
24
+ * Extended prepare response that includes the device binding code for link protocol.
25
+ *
26
+ * Compatible with `PrepareResponse` — existing code that expects `PrepareResponse` will
27
+ * continue to work unchanged. The `feCode` field is only present when the session uses
28
+ * the link authentication strategy.
29
+ */
30
+ export interface PrepareResult extends PrepareResponse {
31
+ /**
32
+ * Device binding code for link protocol sessions.
33
+ *
34
+ * When present, the caller MUST set this as an HttpOnly cookie using
35
+ * `buildSetBindingCookieHeader(feCode)` from `device-binding.ts`.
36
+ *
37
+ * `undefined` for non-link strategies (TS43, desktop).
38
+ */
39
+ feCode?: string;
40
+ }
22
41
  /** Client configuration options. */
23
42
  export interface GlideSdkSettings {
24
43
  /** OAuth2 Client ID (required). */
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AAE5C,yBAAyB;AACzB,MAAM,MAAM,SAAS,GAAG,MAAM,GAAG,MAAM,CAAC;AAExC,qDAAqD;AACrD,MAAM,WAAW,aAAa;IAC1B,6CAA6C;IAC7C,YAAY,EAAE,MAAM,CAAC;IACrB,uCAAuC;IACvC,UAAU,EAAE,MAAM,CAAC;IACnB,uBAAuB;IACvB,UAAU,EAAE,MAAM,CAAC;IACnB,iCAAiC;IACjC,UAAU,EAAE,MAAM,CAAC;CACtB;AAED,6CAA6C;AAC7C,MAAM,WAAW,WAAW;IACxB,wBAAwB;IACxB,WAAW,EAAE,MAAM,CAAC;IACpB,6CAA6C;IAC7C,SAAS,EAAE,MAAM,CAAC;CACrB;AAED,oCAAoC;AACpC,MAAM,WAAW,gBAAgB;IAC7B,mCAAmC;IACnC,QAAQ,EAAE,MAAM,CAAC;IACjB,uCAAuC;IACvC,YAAY,EAAE,MAAM,CAAC;IACrB,4CAA4C;IAC5C,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,yBAAyB;IACzB,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB,iBAAiB;IACjB,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,oCAAoC;IACpC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,qFAAqF;IACrF,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC/B"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AAC5C,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uCAAuC,CAAC;AAE7E,yBAAyB;AACzB,MAAM,MAAM,SAAS,GAAG,MAAM,GAAG,MAAM,CAAC;AAExC,qDAAqD;AACrD,MAAM,WAAW,aAAa;IAC1B,6CAA6C;IAC7C,YAAY,EAAE,MAAM,CAAC;IACrB,uCAAuC;IACvC,UAAU,EAAE,MAAM,CAAC;IACnB,uBAAuB;IACvB,UAAU,EAAE,MAAM,CAAC;IACnB,iCAAiC;IACjC,UAAU,EAAE,MAAM,CAAC;CACtB;AAED,6CAA6C;AAC7C,MAAM,WAAW,WAAW;IACxB,wBAAwB;IACxB,WAAW,EAAE,MAAM,CAAC;IACpB,6CAA6C;IAC7C,SAAS,EAAE,MAAM,CAAC;CACrB;AAED;;;;;;GAMG;AACH,MAAM,WAAW,aAAc,SAAQ,eAAe;IAClD;;;;;;;OAOG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,oCAAoC;AACpC,MAAM,WAAW,gBAAgB;IAC7B,mCAAmC;IACnC,QAAQ,EAAE,MAAM,CAAC;IACjB,uCAAuC;IACvC,YAAY,EAAE,MAAM,CAAC;IACrB,4CAA4C;IAC5C,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,yBAAyB;IACzB,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB,iBAAiB;IACjB,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,oCAAoC;IACpC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,qFAAqF;IACrF,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC/B"}
@@ -4,7 +4,6 @@ export interface ValidationResult {
4
4
  valid: boolean;
5
5
  error?: string;
6
6
  errorCode?: string;
7
- details?: Record<string, unknown>;
8
7
  }
9
8
  /** Validates phone number format (E.164). */
10
9
  export declare function validatePhoneNumber(phoneNumber?: string): ValidationResult;
@@ -1 +1 @@
1
- {"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../src/validation.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,uCAAuC,CAAC;AAE/E,yBAAyB;AACzB,MAAM,WAAW,gBAAgB;IAC7B,KAAK,EAAE,OAAO,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACrC;AAED,6CAA6C;AAC7C,wBAAgB,mBAAmB,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,gBAAgB,CAyC1E;AAED,uCAAuC;AACvC,wBAAgB,YAAY,CAAC,IAAI,CAAC,EAAE,IAAI,GAAG,gBAAgB,CAoB1D;AAED,+EAA+E;AAC/E,wBAAgB,2BAA2B,CACvC,OAAO,CAAC,EAAE,WAAW,EACrB,WAAW,CAAC,EAAE,MAAM,EACpB,IAAI,CAAC,EAAE,IAAI,GACZ,gBAAgB,CA0ClB"}
1
+ {"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../src/validation.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,uCAAuC,CAAC;AAE/E,yBAAyB;AACzB,MAAM,WAAW,gBAAgB;IAC7B,KAAK,EAAE,OAAO,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,6CAA6C;AAC7C,wBAAgB,mBAAmB,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,gBAAgB,CAyC1E;AAED,uCAAuC;AACvC,wBAAgB,YAAY,CAAC,IAAI,CAAC,EAAE,IAAI,GAAG,gBAAgB,CAoB1D;AAED,+EAA+E;AAC/E,wBAAgB,2BAA2B,CACvC,OAAO,CAAC,EAAE,WAAW,EACrB,WAAW,CAAC,EAAE,MAAM,EACpB,IAAI,CAAC,EAAE,IAAI,GACZ,gBAAgB,CAuClB"}
@@ -75,16 +75,14 @@ function validateUseCaseRequirements(useCase, phoneNumber, plmn) {
75
75
  return {
76
76
  valid: false,
77
77
  error: 'Phone number should not be provided for GetPhoneNumber use case',
78
- errorCode: glide_be_sdk_node_core_1.ErrorCode.INVALID_USE_CASE,
79
- details: { invalid_field: 'phone_number', reason: 'not_allowed_for_use_case' }
78
+ errorCode: glide_be_sdk_node_core_1.ErrorCode.INVALID_USE_CASE
80
79
  };
81
80
  }
82
81
  if (!plmn) {
83
82
  return {
84
83
  valid: false,
85
84
  error: 'PLMN (MCC/MNC) is required for GetPhoneNumber use case',
86
- errorCode: glide_be_sdk_node_core_1.ErrorCode.MISSING_REQUIRED_FIELD,
87
- details: { missing_field: 'plmn' }
85
+ errorCode: glide_be_sdk_node_core_1.ErrorCode.MISSING_REQUIRED_FIELD
88
86
  };
89
87
  }
90
88
  break;
@@ -93,8 +91,7 @@ function validateUseCaseRequirements(useCase, phoneNumber, plmn) {
93
91
  return {
94
92
  valid: false,
95
93
  error: 'Phone number is required for VerifyPhoneNumber use case',
96
- errorCode: glide_be_sdk_node_core_1.ErrorCode.MISSING_REQUIRED_FIELD,
97
- details: { missing_field: 'phone_number' }
94
+ errorCode: glide_be_sdk_node_core_1.ErrorCode.MISSING_REQUIRED_FIELD
98
95
  };
99
96
  }
100
97
  break;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@glideidentity/glide-be-sdk-node",
3
- "version": "2.0.1",
3
+ "version": "2.1.1",
4
4
  "description": "Glide SDK for Node.js - carrier-based phone verification",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -21,8 +21,17 @@
21
21
  "clean": "rm -rf dist"
22
22
  },
23
23
  "dependencies": {
24
- "@glideidentity/glide-be-sdk-node-core": "2.0.1"
24
+ "@glideidentity/glide-be-sdk-node-core": "2.1.1"
25
25
  },
26
- "keywords": ["glide", "sdk", "phone", "verification", "magic-auth"],
26
+ "publishConfig": {
27
+ "registry": "https://us-central1-npm.pkg.dev/platform-devops-09ff133a/npm-packages/"
28
+ },
29
+ "keywords": [
30
+ "glide",
31
+ "sdk",
32
+ "phone",
33
+ "verification",
34
+ "magic-auth"
35
+ ],
27
36
  "license": "SEE LICENSE IN LICENSE"
28
37
  }