@oxyhq/core 3.4.8 → 3.4.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/.tsbuildinfo +1 -1
- package/dist/cjs/HttpService.js +6 -6
- package/dist/cjs/index.js +8 -3
- package/dist/cjs/mixins/OxyServices.auth.js +29 -7
- package/dist/cjs/mixins/OxyServices.fedcm.js +5 -1
- package/dist/cjs/mixins/OxyServices.user.js +12 -7
- package/dist/cjs/utils/userIdentity.js +41 -0
- package/dist/esm/.tsbuildinfo +1 -1
- package/dist/esm/HttpService.js +6 -6
- package/dist/esm/index.js +1 -0
- package/dist/esm/mixins/OxyServices.auth.js +29 -7
- package/dist/esm/mixins/OxyServices.fedcm.js +5 -1
- package/dist/esm/mixins/OxyServices.user.js +12 -7
- package/dist/esm/utils/userIdentity.js +36 -0
- package/dist/types/.tsbuildinfo +1 -1
- package/dist/types/index.d.ts +1 -0
- package/dist/types/mixins/OxyServices.user.d.ts +3 -1
- package/dist/types/utils/userIdentity.d.ts +12 -0
- package/package.json +1 -1
- package/src/HttpService.ts +7 -7
- package/src/__tests__/httpServiceCsrf.test.ts +92 -0
- package/src/__tests__/userIdentity.test.ts +75 -0
- package/src/index.ts +1 -0
- package/src/mixins/OxyServices.auth.ts +36 -7
- package/src/mixins/OxyServices.fedcm.ts +5 -1
- package/src/mixins/OxyServices.user.ts +14 -8
- package/src/utils/userIdentity.ts +51 -0
package/dist/cjs/HttpService.js
CHANGED
|
@@ -207,9 +207,11 @@ class HttpService {
|
|
|
207
207
|
const fullUrl = this.buildURL(url, params);
|
|
208
208
|
// Get auth token (with auto-refresh)
|
|
209
209
|
const authHeader = await this.getAuthHeader();
|
|
210
|
-
//
|
|
210
|
+
// CSRF protects cookie-authenticated browser writes. Bearer-authenticated
|
|
211
|
+
// SDK clients are not vulnerable to ambient-cookie CSRF, and linked app
|
|
212
|
+
// APIs should not need to implement a duplicate `/csrf-token` route.
|
|
211
213
|
const isStateChangingMethod = ['POST', 'PUT', 'PATCH', 'DELETE'].includes(method);
|
|
212
|
-
const csrfToken = isStateChangingMethod ? await this.fetchCsrfToken() : null;
|
|
214
|
+
const csrfToken = isStateChangingMethod && !authHeader ? await this.fetchCsrfToken() : null;
|
|
213
215
|
// Determine if data is FormData using robust detection
|
|
214
216
|
const isFormData = this.isFormData(data);
|
|
215
217
|
// Make fetch request
|
|
@@ -240,10 +242,8 @@ class HttpService {
|
|
|
240
242
|
if (isNativeApp && isStateChangingMethod) {
|
|
241
243
|
headers['X-Native-App'] = 'true';
|
|
242
244
|
}
|
|
243
|
-
// Debug logging for CSRF issues
|
|
244
|
-
//
|
|
245
|
-
// this was a bare console.log that leaked noise into every host app's
|
|
246
|
-
// stdout in development.
|
|
245
|
+
// Debug logging for CSRF issues, routed through SimpleLogger so it only
|
|
246
|
+
// fires when consumers opt in via `enableLogging`.
|
|
247
247
|
if (isStateChangingMethod) {
|
|
248
248
|
this.logger.debug('CSRF Debug:', {
|
|
249
249
|
url,
|
package/dist/cjs/index.js
CHANGED
|
@@ -18,9 +18,10 @@
|
|
|
18
18
|
* If a symbol does not appear here, it is NOT part of the public API.
|
|
19
19
|
*/
|
|
20
20
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
21
|
-
exports.
|
|
22
|
-
exports.
|
|
23
|
-
exports.
|
|
21
|
+
exports.isLightColor = exports.withOpacity = exports.rgbToHex = exports.hexToRgb = exports.lightenColor = exports.darkenColor = exports.isAndroid = exports.isIOS = exports.isNative = exports.isWeb = exports.setPlatformOS = exports.getPlatformOS = exports.isRTLLocale = exports.normalizeLanguageCode = exports.getNativeLanguageName = exports.getLanguageName = exports.getLanguageMetadata = exports.SUPPORTED_LANGUAGES = exports.TopicSource = exports.TopicType = exports.SECURITY_EVENT_SEVERITY_MAP = exports.DeviceManager = exports.RecoveryPhraseService = exports.SignatureService = exports.IdentityPersistError = exports.IdentityAlreadyExistsError = exports.KeyManager = exports.sessionsArraysEqual = exports.normalizeAndSortSessions = exports.mergeSessions = exports.authenticatedApiCall = exports.withAuthErrorHandling = exports.isAuthenticationError = exports.ensureValidToken = exports.AuthenticationFailedError = exports.SessionSyncRequiredError = exports.normalizeUserIdentityOrNull = exports.normalizeUserIdentity = exports.getNormalizedUserId = exports.OxyAppDataIdentifierError = exports.ServiceCredentialMismatchError = exports.createCrossDomainAuth = exports.CrossDomainAuth = exports.createAuthManager = exports.AuthManager = exports.oxyClient = exports.OXY_CLOUD_URL = exports.OxyAuthenticationTimeoutError = exports.OxyAuthenticationError = exports.OxyServices = void 0;
|
|
22
|
+
exports.isValidArray = exports.isRequiredBoolean = exports.isRequiredNumber = exports.isRequiredString = exports.isValidPassword = exports.isValidUsername = exports.isValidEmail = exports.PASSWORD_REGEX = exports.USERNAME_REGEX = exports.EMAIL_REGEX = exports.retryAsync = exports.validateRequiredFields = exports.handleHttpError = exports.createApiError = exports.ErrorCodes = exports.safeJsonParse = exports.buildPaginationParams = exports.buildUrl = exports.buildSearchParams = exports.translate = exports.createDebugLogger = exports.debugError = exports.debugWarn = exports.debugLog = exports.isDev = exports.withRetry = exports.delay = exports.shouldAllowRequest = exports.recordSuccess = exports.recordFailure = exports.calculateBackoffInterval = exports.createCircuitBreakerState = exports.DEFAULT_CIRCUIT_BREAKER_CONFIG = exports.isRetryableError = exports.isNetworkError = exports.isServerError = exports.isRateLimitError = exports.isNotFoundError = exports.isForbiddenError = exports.isUnauthorizedError = exports.isAlreadyRegisteredError = exports.getErrorMessage = exports.getErrorStatus = exports.HttpStatus = exports.getSystemColorScheme = exports.systemPrefersDarkMode = exports.getOppositeTheme = exports.normalizeColorScheme = exports.normalizeTheme = exports.getContrastTextColor = void 0;
|
|
23
|
+
exports.runColdBoot = exports.guardActive = exports.isCentralIdPOrigin = exports.buildSsoBounceUrl = exports.getSsoCallbackBootstrapScript = exports.ssoNavigate = exports.ssoCallbackBootstrapKey = exports.ssoAttemptedKey = exports.ssoNoSessionKey = exports.ssoDestKey = exports.ssoGuardKey = exports.ssoStateKey = exports.SSO_GUARD_TTL_MS = exports.SSO_CALLBACK_PATH = exports.generateSsoState = exports.consumeSsoReturn = exports.parseSsoReturnFragment = exports.resolveCentralAuthUrl = exports.CENTRAL_IDP_APEX = exports.CENTRAL_AUTH_URL = exports.MULTIPART_TLDS = exports.registrableApex = exports.autoDetectAuthWebUrl = exports.getAccountColor = exports.mergeAccountsFromRefreshAll = exports.formatPublicKeyHandle = exports.getAccountFallbackHandle = exports.getAccountDisplayName = exports.createQuickAccount = exports.buildAccountsArray = exports.updateAvatarVisibility = exports.logPerformance = exports.logPayment = exports.logDevice = exports.logUser = exports.logSession = exports.logApi = exports.logAuth = exports.LogLevel = exports.logger = exports.validateAndSanitizeUserInput = exports.isValidObjectId = exports.sanitizeHTML = exports.sanitizeString = exports.isValidFileType = exports.isValidFileSize = exports.isValidDate = exports.isValidURL = exports.isValidUUID = exports.isValidObject = void 0;
|
|
24
|
+
exports.packageInfo = void 0;
|
|
24
25
|
// Ensure crypto polyfills are loaded before anything else
|
|
25
26
|
require("./crypto/polyfill");
|
|
26
27
|
// ---------------------------------------------------------------------------
|
|
@@ -46,6 +47,10 @@ var OxyServices_auth_1 = require("./mixins/OxyServices.auth");
|
|
|
46
47
|
Object.defineProperty(exports, "ServiceCredentialMismatchError", { enumerable: true, get: function () { return OxyServices_auth_1.ServiceCredentialMismatchError; } });
|
|
47
48
|
var OxyServices_appData_1 = require("./mixins/OxyServices.appData");
|
|
48
49
|
Object.defineProperty(exports, "OxyAppDataIdentifierError", { enumerable: true, get: function () { return OxyServices_appData_1.OxyAppDataIdentifierError; } });
|
|
50
|
+
var userIdentity_1 = require("./utils/userIdentity");
|
|
51
|
+
Object.defineProperty(exports, "getNormalizedUserId", { enumerable: true, get: function () { return userIdentity_1.getNormalizedUserId; } });
|
|
52
|
+
Object.defineProperty(exports, "normalizeUserIdentity", { enumerable: true, get: function () { return userIdentity_1.normalizeUserIdentity; } });
|
|
53
|
+
Object.defineProperty(exports, "normalizeUserIdentityOrNull", { enumerable: true, get: function () { return userIdentity_1.normalizeUserIdentityOrNull; } });
|
|
49
54
|
// ---------------------------------------------------------------------------
|
|
50
55
|
// Auth helpers (token refresh, error normalisation, retry policies)
|
|
51
56
|
// ---------------------------------------------------------------------------
|
|
@@ -5,6 +5,7 @@ exports.OxyServicesAuthMixin = OxyServicesAuthMixin;
|
|
|
5
5
|
const OxyServices_errors_1 = require("../OxyServices.errors");
|
|
6
6
|
const platformCrypto_1 = require("../utils/platformCrypto");
|
|
7
7
|
const loggerUtils_1 = require("../utils/loggerUtils");
|
|
8
|
+
const userIdentity_1 = require("../utils/userIdentity");
|
|
8
9
|
/**
|
|
9
10
|
* Sentinel error raised when getServiceToken() is called with a known apiKey
|
|
10
11
|
* but a non-matching secret. Indicates either credential drift in the caller
|
|
@@ -313,7 +314,10 @@ function OxyServicesAuthMixin(Base) {
|
|
|
313
314
|
if (res?.accessToken) {
|
|
314
315
|
this.setTokens(res.accessToken);
|
|
315
316
|
}
|
|
316
|
-
return
|
|
317
|
+
return {
|
|
318
|
+
...res,
|
|
319
|
+
user: (0, userIdentity_1.normalizeUserIdentity)(res.user),
|
|
320
|
+
};
|
|
317
321
|
}
|
|
318
322
|
catch (error) {
|
|
319
323
|
throw this.handleError(error);
|
|
@@ -335,7 +339,8 @@ function OxyServicesAuthMixin(Base) {
|
|
|
335
339
|
*/
|
|
336
340
|
async getUserByPublicKey(publicKey) {
|
|
337
341
|
try {
|
|
338
|
-
|
|
342
|
+
const user = await this.makeRequest('GET', `/auth/user/${encodeURIComponent(publicKey)}`, undefined, { cache: true, cacheTTL: 2 * 60 * 1000 });
|
|
343
|
+
return (0, userIdentity_1.normalizeUserIdentity)(user);
|
|
339
344
|
}
|
|
340
345
|
catch (error) {
|
|
341
346
|
throw this.handleError(error);
|
|
@@ -346,10 +351,11 @@ function OxyServicesAuthMixin(Base) {
|
|
|
346
351
|
*/
|
|
347
352
|
async getUserBySession(sessionId) {
|
|
348
353
|
try {
|
|
349
|
-
|
|
354
|
+
const user = await this.makeRequest('GET', `/session/user/${sessionId}`, undefined, {
|
|
350
355
|
cache: true,
|
|
351
356
|
cacheTTL: 2 * 60 * 1000,
|
|
352
357
|
});
|
|
358
|
+
return (0, userIdentity_1.normalizeUserIdentity)(user);
|
|
353
359
|
}
|
|
354
360
|
catch (error) {
|
|
355
361
|
throw this.handleError(error);
|
|
@@ -364,11 +370,15 @@ function OxyServicesAuthMixin(Base) {
|
|
|
364
370
|
return [];
|
|
365
371
|
}
|
|
366
372
|
const uniqueSessionIds = Array.from(new Set(sessionIds)).sort();
|
|
367
|
-
|
|
373
|
+
const users = await this.makeRequest('POST', '/session/users/batch', { sessionIds: uniqueSessionIds }, {
|
|
368
374
|
cache: true,
|
|
369
375
|
cacheTTL: 2 * 60 * 1000,
|
|
370
376
|
deduplicate: true,
|
|
371
377
|
});
|
|
378
|
+
return users.map((entry) => ({
|
|
379
|
+
...entry,
|
|
380
|
+
user: (0, userIdentity_1.normalizeUserIdentityOrNull)(entry.user),
|
|
381
|
+
}));
|
|
372
382
|
}
|
|
373
383
|
catch (error) {
|
|
374
384
|
throw this.handleError(error);
|
|
@@ -693,7 +703,11 @@ function OxyServicesAuthMixin(Base) {
|
|
|
693
703
|
urlParams.deviceFingerprint = options.deviceFingerprint;
|
|
694
704
|
if (options.useHeaderValidation)
|
|
695
705
|
urlParams.useHeaderValidation = 'true';
|
|
696
|
-
|
|
706
|
+
const validation = await this.makeRequest('GET', `/session/validate/${sessionId}`, urlParams, { cache: false });
|
|
707
|
+
return {
|
|
708
|
+
...validation,
|
|
709
|
+
user: (0, userIdentity_1.normalizeUserIdentity)(validation.user),
|
|
710
|
+
};
|
|
697
711
|
}
|
|
698
712
|
catch (error) {
|
|
699
713
|
// Session is invalid — clear any cached user data for this session (#196)
|
|
@@ -728,13 +742,17 @@ function OxyServicesAuthMixin(Base) {
|
|
|
728
742
|
*/
|
|
729
743
|
async signUp(username, email, password, deviceName, deviceFingerprint) {
|
|
730
744
|
try {
|
|
731
|
-
|
|
745
|
+
const session = await this.makeRequest('POST', '/auth/signup', {
|
|
732
746
|
username,
|
|
733
747
|
email,
|
|
734
748
|
password,
|
|
735
749
|
deviceName,
|
|
736
750
|
deviceFingerprint,
|
|
737
751
|
}, { cache: false });
|
|
752
|
+
return {
|
|
753
|
+
...session,
|
|
754
|
+
user: (0, userIdentity_1.normalizeUserIdentity)(session.user),
|
|
755
|
+
};
|
|
738
756
|
}
|
|
739
757
|
catch (error) {
|
|
740
758
|
throw this.handleError(error);
|
|
@@ -745,12 +763,16 @@ function OxyServicesAuthMixin(Base) {
|
|
|
745
763
|
*/
|
|
746
764
|
async signIn(identifier, password, deviceName, deviceFingerprint) {
|
|
747
765
|
try {
|
|
748
|
-
|
|
766
|
+
const session = await this.makeRequest('POST', '/auth/login', {
|
|
749
767
|
identifier,
|
|
750
768
|
password,
|
|
751
769
|
deviceName,
|
|
752
770
|
deviceFingerprint,
|
|
753
771
|
}, { cache: false });
|
|
772
|
+
return {
|
|
773
|
+
...session,
|
|
774
|
+
user: (0, userIdentity_1.normalizeUserIdentity)(session.user),
|
|
775
|
+
};
|
|
754
776
|
}
|
|
755
777
|
catch (error) {
|
|
756
778
|
throw this.handleError(error);
|
|
@@ -4,6 +4,7 @@ exports.OxyServicesFedCMMixin = OxyServicesFedCMMixin;
|
|
|
4
4
|
exports.FedCMMixin = OxyServicesFedCMMixin;
|
|
5
5
|
const OxyServices_errors_1 = require("../OxyServices.errors");
|
|
6
6
|
const debugUtils_1 = require("../shared/utils/debugUtils");
|
|
7
|
+
const userIdentity_1 = require("../utils/userIdentity");
|
|
7
8
|
const debug = (0, debugUtils_1.createDebugLogger)('FedCM');
|
|
8
9
|
// Modern (W3C spec) → legacy (Chrome 125–131) mode value mapping. Used to
|
|
9
10
|
// retry a credential request when an older browser rejects the modern enum.
|
|
@@ -589,7 +590,10 @@ function OxyServicesFedCMMixin(Base) {
|
|
|
589
590
|
hasSession: !!response?.sessionId,
|
|
590
591
|
hasUser: !!response?.user,
|
|
591
592
|
});
|
|
592
|
-
return
|
|
593
|
+
return {
|
|
594
|
+
...response,
|
|
595
|
+
user: (0, userIdentity_1.normalizeUserIdentity)(response.user),
|
|
596
|
+
};
|
|
593
597
|
}
|
|
594
598
|
catch (error) {
|
|
595
599
|
debug.error('Token exchange failed:', error instanceof Error ? error.message : String(error));
|
|
@@ -4,6 +4,7 @@ exports.OxyServicesUserMixin = OxyServicesUserMixin;
|
|
|
4
4
|
const apiUtils_1 = require("../utils/apiUtils");
|
|
5
5
|
const keyManager_1 = require("../crypto/keyManager");
|
|
6
6
|
const signatureService_1 = require("../crypto/signatureService");
|
|
7
|
+
const userIdentity_1 = require("../utils/userIdentity");
|
|
7
8
|
function OxyServicesUserMixin(Base) {
|
|
8
9
|
return class extends Base {
|
|
9
10
|
constructor(...args) {
|
|
@@ -14,10 +15,11 @@ function OxyServicesUserMixin(Base) {
|
|
|
14
15
|
*/
|
|
15
16
|
async getProfileByUsername(username) {
|
|
16
17
|
try {
|
|
17
|
-
|
|
18
|
+
const user = await this.makeRequest('GET', `/profiles/username/${username}`, undefined, {
|
|
18
19
|
cache: true,
|
|
19
20
|
cacheTTL: 5 * 60 * 1000, // 5 minutes cache for profiles
|
|
20
21
|
});
|
|
22
|
+
return (0, userIdentity_1.normalizeUserIdentity)(user);
|
|
21
23
|
}
|
|
22
24
|
catch (error) {
|
|
23
25
|
throw this.handleError(error);
|
|
@@ -80,7 +82,7 @@ function OxyServicesUserMixin(Base) {
|
|
|
80
82
|
cache: true,
|
|
81
83
|
cacheTTL: 24 * 60 * 60 * 1000, // 24h cache — matches server-side staleness window
|
|
82
84
|
});
|
|
83
|
-
return result
|
|
85
|
+
return (0, userIdentity_1.normalizeUserIdentityOrNull)(result);
|
|
84
86
|
}
|
|
85
87
|
catch {
|
|
86
88
|
return null;
|
|
@@ -92,7 +94,7 @@ function OxyServicesUserMixin(Base) {
|
|
|
92
94
|
* method — calling services never write user data directly.
|
|
93
95
|
*/
|
|
94
96
|
async resolveExternalUser(data) {
|
|
95
|
-
return this.makeRequest('PUT', '/users/resolve', data);
|
|
97
|
+
return (0, userIdentity_1.normalizeUserIdentity)(await this.makeRequest('PUT', '/users/resolve', data));
|
|
96
98
|
}
|
|
97
99
|
/**
|
|
98
100
|
* Get profile recommendations, optionally filtering out specific user types.
|
|
@@ -122,20 +124,22 @@ function OxyServicesUserMixin(Base) {
|
|
|
122
124
|
const params = {};
|
|
123
125
|
if (limit)
|
|
124
126
|
params.limit = String(limit);
|
|
125
|
-
|
|
127
|
+
const users = await this.makeRequest('GET', `/profiles/${userId}/similar`, params, {
|
|
126
128
|
cache: true,
|
|
127
129
|
cacheTTL: 5 * 60 * 1000, // 5 min cache
|
|
128
130
|
});
|
|
131
|
+
return users.map((user) => (0, userIdentity_1.normalizeUserIdentity)(user));
|
|
129
132
|
}
|
|
130
133
|
/**
|
|
131
134
|
* Get user by ID
|
|
132
135
|
*/
|
|
133
136
|
async getUserById(userId) {
|
|
134
137
|
try {
|
|
135
|
-
|
|
138
|
+
const user = await this.makeRequest('GET', `/users/${userId}`, undefined, {
|
|
136
139
|
cache: true,
|
|
137
140
|
cacheTTL: 5 * 60 * 1000, // 5 minutes cache
|
|
138
141
|
});
|
|
142
|
+
return (0, userIdentity_1.normalizeUserIdentity)(user);
|
|
139
143
|
}
|
|
140
144
|
catch (error) {
|
|
141
145
|
throw this.handleError(error);
|
|
@@ -146,10 +150,11 @@ function OxyServicesUserMixin(Base) {
|
|
|
146
150
|
*/
|
|
147
151
|
async getCurrentUser() {
|
|
148
152
|
return this.withAuthRetry(async () => {
|
|
149
|
-
|
|
153
|
+
const user = await this.makeRequest('GET', '/users/me', undefined, {
|
|
150
154
|
cache: true,
|
|
151
155
|
cacheTTL: 1 * 60 * 1000, // 1 minute cache for current user
|
|
152
156
|
});
|
|
157
|
+
return (0, userIdentity_1.normalizeUserIdentity)(user);
|
|
153
158
|
}, 'getCurrentUser');
|
|
154
159
|
}
|
|
155
160
|
/**
|
|
@@ -167,7 +172,7 @@ function OxyServicesUserMixin(Base) {
|
|
|
167
172
|
*/
|
|
168
173
|
async updateProfile(updates) {
|
|
169
174
|
try {
|
|
170
|
-
const result = await this.makeRequest('PUT', '/users/me', updates, { cache: false });
|
|
175
|
+
const result = (0, userIdentity_1.normalizeUserIdentity)(await this.makeRequest('PUT', '/users/me', updates, { cache: false }));
|
|
171
176
|
// Bust every cached representation of the current user. We use a
|
|
172
177
|
// prefix sweep rather than an enumeration because the SDK never
|
|
173
178
|
// tracks the set of active session IDs centrally.
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getNormalizedUserId = getNormalizedUserId;
|
|
4
|
+
exports.normalizeUserIdentity = normalizeUserIdentity;
|
|
5
|
+
exports.normalizeUserIdentityOrNull = normalizeUserIdentityOrNull;
|
|
6
|
+
function stringifyIdentity(value) {
|
|
7
|
+
if (typeof value === 'string') {
|
|
8
|
+
const trimmed = value.trim();
|
|
9
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
10
|
+
}
|
|
11
|
+
if (typeof value === 'number' && Number.isFinite(value)) {
|
|
12
|
+
return String(value);
|
|
13
|
+
}
|
|
14
|
+
if (value && typeof value === 'object' && 'toString' in value) {
|
|
15
|
+
const toStringFn = value.toString;
|
|
16
|
+
if (typeof toStringFn === 'function' && toStringFn !== Object.prototype.toString) {
|
|
17
|
+
const rendered = toStringFn.call(value);
|
|
18
|
+
if (typeof rendered === 'string') {
|
|
19
|
+
const trimmed = rendered.trim();
|
|
20
|
+
return trimmed.length > 0 && trimmed !== '[object Object]' ? trimmed : null;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
function getNormalizedUserId(user) {
|
|
27
|
+
if (!user) {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
return stringifyIdentity(user.id) ?? stringifyIdentity(user._id);
|
|
31
|
+
}
|
|
32
|
+
function normalizeUserIdentity(user) {
|
|
33
|
+
const id = getNormalizedUserId(user);
|
|
34
|
+
if (!id) {
|
|
35
|
+
throw new Error('User response missing id');
|
|
36
|
+
}
|
|
37
|
+
return { ...user, id };
|
|
38
|
+
}
|
|
39
|
+
function normalizeUserIdentityOrNull(user) {
|
|
40
|
+
return user ? normalizeUserIdentity(user) : null;
|
|
41
|
+
}
|