@oxyhq/core 3.4.14 → 3.4.16
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/README.md +30 -2
- package/dist/cjs/.tsbuildinfo +1 -1
- package/dist/cjs/index.js +10 -5
- package/dist/cjs/utils/accountUtils.js +5 -9
- package/dist/cjs/utils/userHandle.js +37 -0
- package/dist/cjs/utils/userIdentity.js +9 -18
- package/dist/esm/.tsbuildinfo +1 -1
- package/dist/esm/index.js +5 -1
- package/dist/esm/utils/accountUtils.js +5 -9
- package/dist/esm/utils/userHandle.js +33 -0
- package/dist/esm/utils/userIdentity.js +9 -17
- package/dist/types/.tsbuildinfo +1 -1
- package/dist/types/index.d.ts +3 -2
- package/dist/types/models/interfaces.d.ts +2 -0
- package/dist/types/utils/accountUtils.d.ts +4 -14
- package/dist/types/utils/userHandle.d.ts +26 -0
- package/dist/types/utils/userIdentity.d.ts +9 -11
- package/package.json +1 -1
- package/src/__tests__/userIdentity.test.ts +37 -12
- package/src/index.ts +9 -2
- package/src/models/interfaces.ts +2 -0
- package/src/utils/__tests__/accountUtils.test.ts +10 -22
- package/src/utils/accountUtils.ts +6 -16
- package/src/utils/asyncUtils.ts +1 -2
- package/src/utils/userHandle.ts +49 -0
- package/src/utils/userIdentity.ts +9 -31
package/dist/types/index.d.ts
CHANGED
|
@@ -34,8 +34,9 @@ export type { ServiceApp, ServiceActingAsVerification } from './mixins/OxyServic
|
|
|
34
34
|
export type { CreateManagedAccountInput, ManagedAccountManager, ManagedAccount, } from './mixins/OxyServices.managedAccounts';
|
|
35
35
|
export type { ContactDiscoveryMatch, ContactDiscoveryResponse, } from './mixins/OxyServices.contacts';
|
|
36
36
|
export { OxyAppDataIdentifierError } from './mixins/OxyServices.appData';
|
|
37
|
-
export {
|
|
38
|
-
export
|
|
37
|
+
export { getNormalizedUserId, normalizeUserIdentity, normalizeUserIdentityOrNull, } from './utils/userIdentity';
|
|
38
|
+
export { getCanonicalUserHandle, getNormalizedUserHandle, } from './utils/userHandle';
|
|
39
|
+
export type { CanonicalUserHandleInput, UserHandleInput } from './utils/userHandle';
|
|
39
40
|
export type { Application, PublicApplication, ApplicationMember, ApplicationCredential, ApplicationRole, ApplicationType, ApplicationStatus, ApplicationMemberStatus, ApplicationCredentialType, ApplicationCredentialStatus, ApplicationEnvironment, CreateApplicationInput, UpdateApplicationInput, InviteApplicationMemberInput, UpdateApplicationMemberInput, TransferApplicationOwnershipInput, CreateApplicationCredentialInput, ApplicationCredentialWithSecret, RotateApplicationCredentialResult, ApplicationUsagePeriod, ApplicationUsageSummary, ApplicationUsageByDay, ApplicationUsageByEndpoint, ApplicationUsageStats, ApplicationSuccessResult, } from './mixins/OxyServices.applications';
|
|
40
41
|
export type { Workspace, WorkspaceMember, WorkspaceRole, WorkspaceType, WorkspaceStatus, WorkspaceMemberStatus, CreateWorkspaceInput, UpdateWorkspaceInput, InviteWorkspaceMemberInput, UpdateWorkspaceMemberInput, TransferWorkspaceOwnershipInput, WorkspaceSuccessResult, } from './mixins/OxyServices.workspaces';
|
|
41
42
|
export type { ReputationCategory, TrustTier, ReputationTransactionStatus, ReputationTargetEntityType, ReputationDisputeStatus, ReputationInfluenceContext, ReputationTransaction, ReputationBalanceBreakdown, ReputationInfluence, ReputationReliability, ReputationBalance, ReputationDispute, ReputationRule, ReputationLeaderboardEntry, ReputationInfluenceResult, ReverseReputationTransactionResult, AwardReputationInput, CreateReputationDisputeInput, ResolveReputationDisputeInput, UpsertReputationRuleInput, ReverseReputationTransactionInput, } from './mixins/OxyServices.reputation';
|
|
@@ -32,13 +32,7 @@ export interface DisplayNameUserShape {
|
|
|
32
32
|
full?: string;
|
|
33
33
|
[key: string]: unknown;
|
|
34
34
|
};
|
|
35
|
-
/**
|
|
36
|
-
* Pre-resolved display name as emitted by the server's `displayName` virtual
|
|
37
|
-
* (raw `/users/me` responses). NOTE: the server virtual resolves to
|
|
38
|
-
* `username || truncatedPublicKey || 'Anonymous'` — it does NOT compose the
|
|
39
|
-
* structured `name`. It is therefore preferred only AFTER a real structured
|
|
40
|
-
* name, so a first-name-only account never collapses to its username/key.
|
|
41
|
-
*/
|
|
35
|
+
/** Canonical display name resolved by the API. */
|
|
42
36
|
displayName?: string;
|
|
43
37
|
username?: string;
|
|
44
38
|
publicKey?: string;
|
|
@@ -52,13 +46,9 @@ export declare const formatPublicKeyHandle: (publicKey: string) => string;
|
|
|
52
46
|
* Resolve a friendly display name for a user.
|
|
53
47
|
*
|
|
54
48
|
* Order of preference:
|
|
55
|
-
* 1. `
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
* 2. `name` (when stored as a plain string)
|
|
59
|
-
* 3. `displayName` (server `displayName` virtual — `username || truncatedKey`).
|
|
60
|
-
* Placed AFTER the structured name on purpose: the server virtual ignores
|
|
61
|
-
* `name`, so preferring it first would re-introduce the first-only bug.
|
|
49
|
+
* 1. `displayName` from the API contract.
|
|
50
|
+
* 2. `name.full`, or composed `name.first name.last` for local unsaved shapes.
|
|
51
|
+
* 3. `name` when stored as a plain string.
|
|
62
52
|
* 4. `username`
|
|
63
53
|
* 5. `Account 0x12345678…` (derived from publicKey, when present)
|
|
64
54
|
* 6. Translated fallback (e.g. "Unnamed")
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export interface UserHandleInput {
|
|
2
|
+
username?: string | null;
|
|
3
|
+
handle?: string | null;
|
|
4
|
+
instance?: string | null;
|
|
5
|
+
isFederated?: boolean | null;
|
|
6
|
+
type?: string | null;
|
|
7
|
+
federation?: {
|
|
8
|
+
domain?: string | null;
|
|
9
|
+
} | null;
|
|
10
|
+
}
|
|
11
|
+
export type CanonicalUserHandleInput = UserHandleInput;
|
|
12
|
+
/**
|
|
13
|
+
* Returns the normalized profile handle used by Oxy consumers for display and
|
|
14
|
+
* profile routing.
|
|
15
|
+
*
|
|
16
|
+
* Local users resolve to `username`. Federated users resolve to
|
|
17
|
+
* `username@instance` when the username does not already include an instance.
|
|
18
|
+
* Route-like values are rejected so callers do not accidentally turn paths or
|
|
19
|
+
* query strings into profile destinations.
|
|
20
|
+
*/
|
|
21
|
+
export declare function getNormalizedUserHandle(user: UserHandleInput | null | undefined): string | null;
|
|
22
|
+
/**
|
|
23
|
+
* Compatibility alias for the first public name shipped with this helper.
|
|
24
|
+
* Prefer {@link getNormalizedUserHandle} in new code.
|
|
25
|
+
*/
|
|
26
|
+
export declare function getCanonicalUserHandle(user: CanonicalUserHandleInput | null | undefined): string | null;
|
|
@@ -2,22 +2,20 @@ interface UserIdentityInput {
|
|
|
2
2
|
id?: unknown;
|
|
3
3
|
_id?: unknown;
|
|
4
4
|
}
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
isFederated?: boolean | null;
|
|
10
|
-
type?: string | null;
|
|
11
|
-
federation?: {
|
|
12
|
-
domain?: string | null;
|
|
13
|
-
} | null;
|
|
14
|
-
}
|
|
5
|
+
/**
|
|
6
|
+
* Returns the stable SDK user id from API payloads that may use either `id` or
|
|
7
|
+
* Mongo-style `_id`.
|
|
8
|
+
*/
|
|
15
9
|
export declare function getNormalizedUserId(user: UserIdentityInput | null | undefined): string | null;
|
|
10
|
+
/**
|
|
11
|
+
* Normalizes a user payload to always expose `id`. Throws when the payload does
|
|
12
|
+
* not contain a usable id, because SDK callers should never receive anonymous
|
|
13
|
+
* user objects from authenticated identity endpoints.
|
|
14
|
+
*/
|
|
16
15
|
export declare function normalizeUserIdentity<T extends UserIdentityInput>(user: T): T & {
|
|
17
16
|
id: string;
|
|
18
17
|
};
|
|
19
18
|
export declare function normalizeUserIdentityOrNull<T extends UserIdentityInput>(user: T | null | undefined): (T & {
|
|
20
19
|
id: string;
|
|
21
20
|
}) | null;
|
|
22
|
-
export declare function getCanonicalUserHandle(user: CanonicalUserHandleInput | null | undefined): string | null;
|
|
23
21
|
export {};
|
package/package.json
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { OxyServices } from '../OxyServices';
|
|
2
|
-
import {
|
|
2
|
+
import { getNormalizedUserId, normalizeUserIdentity } from '../utils/userIdentity';
|
|
3
|
+
import { getCanonicalUserHandle, getNormalizedUserHandle } from '../utils/userHandle';
|
|
3
4
|
|
|
4
5
|
interface FetchCall {
|
|
5
6
|
url: string;
|
|
@@ -72,14 +73,18 @@ describe('user identity normalization', () => {
|
|
|
72
73
|
});
|
|
73
74
|
});
|
|
74
75
|
|
|
75
|
-
describe('
|
|
76
|
+
describe('getNormalizedUserHandle', () => {
|
|
76
77
|
it('normalizes local usernames without route prefixes', () => {
|
|
77
|
-
expect(
|
|
78
|
-
expect(
|
|
78
|
+
expect(getNormalizedUserHandle({ username: ' nate ' })).toBe('nate');
|
|
79
|
+
expect(getNormalizedUserHandle({ username: '@nate' })).toBe('nate');
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('falls back to handle when username is missing', () => {
|
|
83
|
+
expect(getNormalizedUserHandle({ handle: '@nate' })).toBe('nate');
|
|
79
84
|
});
|
|
80
85
|
|
|
81
86
|
it('builds federated handles from username and instance', () => {
|
|
82
|
-
expect(
|
|
87
|
+
expect(getNormalizedUserHandle({
|
|
83
88
|
username: 'joannastern',
|
|
84
89
|
isFederated: true,
|
|
85
90
|
instance: 'threads.net',
|
|
@@ -87,7 +92,7 @@ describe('getCanonicalUserHandle', () => {
|
|
|
87
92
|
});
|
|
88
93
|
|
|
89
94
|
it('does not append an instance twice', () => {
|
|
90
|
-
expect(
|
|
95
|
+
expect(getNormalizedUserHandle({
|
|
91
96
|
username: '@joannastern@threads.net',
|
|
92
97
|
type: 'federated',
|
|
93
98
|
instance: 'threads.net',
|
|
@@ -95,7 +100,7 @@ describe('getCanonicalUserHandle', () => {
|
|
|
95
100
|
});
|
|
96
101
|
|
|
97
102
|
it('uses federation domain as the federated instance source', () => {
|
|
98
|
-
expect(
|
|
103
|
+
expect(getNormalizedUserHandle({
|
|
99
104
|
username: 'alice',
|
|
100
105
|
isFederated: true,
|
|
101
106
|
federation: { domain: '@example.social' },
|
|
@@ -103,16 +108,36 @@ describe('getCanonicalUserHandle', () => {
|
|
|
103
108
|
});
|
|
104
109
|
|
|
105
110
|
it('does not use instance for local users', () => {
|
|
106
|
-
expect(
|
|
111
|
+
expect(getNormalizedUserHandle({
|
|
107
112
|
username: 'alice',
|
|
108
113
|
instance: 'example.social',
|
|
109
114
|
})).toBe('alice');
|
|
110
115
|
});
|
|
111
116
|
|
|
117
|
+
it('keeps case while normalizing whitespace', () => {
|
|
118
|
+
expect(getNormalizedUserHandle({
|
|
119
|
+
username: ' Alice ',
|
|
120
|
+
isFederated: true,
|
|
121
|
+
instance: ' Example.Social ',
|
|
122
|
+
})).toBe('Alice@Example.Social');
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it('does not turn an invalid instance into a federated suffix', () => {
|
|
126
|
+
expect(getNormalizedUserHandle({
|
|
127
|
+
username: 'alice',
|
|
128
|
+
isFederated: true,
|
|
129
|
+
instance: 'example.social/users/alice',
|
|
130
|
+
})).toBe('alice');
|
|
131
|
+
});
|
|
132
|
+
|
|
112
133
|
it('rejects empty and route-like values', () => {
|
|
113
|
-
expect(
|
|
114
|
-
expect(
|
|
115
|
-
expect(
|
|
116
|
-
expect(
|
|
134
|
+
expect(getNormalizedUserHandle({ username: '' })).toBeNull();
|
|
135
|
+
expect(getNormalizedUserHandle({ username: 'alice/about' })).toBeNull();
|
|
136
|
+
expect(getNormalizedUserHandle({ username: 'alice?tab=posts' })).toBeNull();
|
|
137
|
+
expect(getNormalizedUserHandle({ username: 'alice#posts' })).toBeNull();
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it('keeps the compatibility alias wired to the normalized implementation', () => {
|
|
141
|
+
expect(getCanonicalUserHandle({ username: '@nate' })).toBe('nate');
|
|
117
142
|
});
|
|
118
143
|
});
|
package/src/index.ts
CHANGED
|
@@ -62,13 +62,20 @@ export type {
|
|
|
62
62
|
ContactDiscoveryResponse,
|
|
63
63
|
} from './mixins/OxyServices.contacts';
|
|
64
64
|
export { OxyAppDataIdentifierError } from './mixins/OxyServices.appData';
|
|
65
|
+
|
|
66
|
+
// ---------------------------------------------------------------------------
|
|
67
|
+
// User identity and handles
|
|
68
|
+
// ---------------------------------------------------------------------------
|
|
65
69
|
export {
|
|
66
|
-
getCanonicalUserHandle,
|
|
67
70
|
getNormalizedUserId,
|
|
68
71
|
normalizeUserIdentity,
|
|
69
72
|
normalizeUserIdentityOrNull,
|
|
70
73
|
} from './utils/userIdentity';
|
|
71
|
-
export
|
|
74
|
+
export {
|
|
75
|
+
getCanonicalUserHandle,
|
|
76
|
+
getNormalizedUserHandle,
|
|
77
|
+
} from './utils/userHandle';
|
|
78
|
+
export type { CanonicalUserHandleInput, UserHandleInput } from './utils/userHandle';
|
|
72
79
|
|
|
73
80
|
// ---------------------------------------------------------------------------
|
|
74
81
|
// Applications (multi-user apps: membership, roles, credentials)
|
package/src/models/interfaces.ts
CHANGED
|
@@ -4,16 +4,17 @@ import {
|
|
|
4
4
|
formatPublicKeyHandle,
|
|
5
5
|
} from '../accountUtils';
|
|
6
6
|
|
|
7
|
-
/**
|
|
8
|
-
* Regression coverage for the auth-app display-name drift (Phase 1 of the
|
|
9
|
-
* contract-centralisation refactor).
|
|
10
|
-
*
|
|
11
|
-
* The auth app previously required BOTH `name.first` AND `name.last` to compose
|
|
12
|
-
* a display name, falling back to the lowercase `username` for first-name-only
|
|
13
|
-
* accounts. The canonical resolver in core MUST be first-name-only safe: a user
|
|
14
|
-
* with only a first name resolves to that first name, never to the username.
|
|
15
|
-
*/
|
|
16
7
|
describe('getAccountDisplayName', () => {
|
|
8
|
+
it('prefers the API displayName when present', () => {
|
|
9
|
+
expect(
|
|
10
|
+
getAccountDisplayName({
|
|
11
|
+
name: { first: 'Nate' },
|
|
12
|
+
displayName: 'Nate Isern',
|
|
13
|
+
username: 'nateus',
|
|
14
|
+
}),
|
|
15
|
+
).toBe('Nate Isern');
|
|
16
|
+
});
|
|
17
|
+
|
|
17
18
|
it('returns first name for first-name-only accounts (NOT the username)', () => {
|
|
18
19
|
const result = getAccountDisplayName({
|
|
19
20
|
name: { first: 'Nate' },
|
|
@@ -50,19 +51,6 @@ describe('getAccountDisplayName', () => {
|
|
|
50
51
|
).toBe('Nathaniel Isern');
|
|
51
52
|
});
|
|
52
53
|
|
|
53
|
-
it('uses the structured name over a server displayName virtual', () => {
|
|
54
|
-
// Server `displayName` virtual = `username || truncatedKey`; it ignores
|
|
55
|
-
// the structured name. A real name must win so a first-only account is
|
|
56
|
-
// never collapsed to its username.
|
|
57
|
-
expect(
|
|
58
|
-
getAccountDisplayName({
|
|
59
|
-
name: { first: 'Nate' },
|
|
60
|
-
displayName: 'nateus',
|
|
61
|
-
username: 'nateus',
|
|
62
|
-
}),
|
|
63
|
-
).toBe('Nate');
|
|
64
|
-
});
|
|
65
|
-
|
|
66
54
|
it('uses displayName when there is no structured name', () => {
|
|
67
55
|
expect(
|
|
68
56
|
getAccountDisplayName({
|
|
@@ -31,13 +31,7 @@ export interface QuickAccount {
|
|
|
31
31
|
/** Minimal user shape accepted by display-name helpers. Avoids importing the full User type. */
|
|
32
32
|
export interface DisplayNameUserShape {
|
|
33
33
|
name?: string | { first?: string; last?: string; full?: string; [key: string]: unknown };
|
|
34
|
-
/**
|
|
35
|
-
* Pre-resolved display name as emitted by the server's `displayName` virtual
|
|
36
|
-
* (raw `/users/me` responses). NOTE: the server virtual resolves to
|
|
37
|
-
* `username || truncatedPublicKey || 'Anonymous'` — it does NOT compose the
|
|
38
|
-
* structured `name`. It is therefore preferred only AFTER a real structured
|
|
39
|
-
* name, so a first-name-only account never collapses to its username/key.
|
|
40
|
-
*/
|
|
34
|
+
/** Canonical display name resolved by the API. */
|
|
41
35
|
displayName?: string;
|
|
42
36
|
username?: string;
|
|
43
37
|
publicKey?: string;
|
|
@@ -57,13 +51,9 @@ export const formatPublicKeyHandle = (publicKey: string): string => {
|
|
|
57
51
|
* Resolve a friendly display name for a user.
|
|
58
52
|
*
|
|
59
53
|
* Order of preference:
|
|
60
|
-
* 1. `
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
* 2. `name` (when stored as a plain string)
|
|
64
|
-
* 3. `displayName` (server `displayName` virtual — `username || truncatedKey`).
|
|
65
|
-
* Placed AFTER the structured name on purpose: the server virtual ignores
|
|
66
|
-
* `name`, so preferring it first would re-introduce the first-only bug.
|
|
54
|
+
* 1. `displayName` from the API contract.
|
|
55
|
+
* 2. `name.full`, or composed `name.first name.last` for local unsaved shapes.
|
|
56
|
+
* 3. `name` when stored as a plain string.
|
|
67
57
|
* 4. `username`
|
|
68
58
|
* 5. `Account 0x12345678…` (derived from publicKey, when present)
|
|
69
59
|
* 6. Translated fallback (e.g. "Unnamed")
|
|
@@ -79,6 +69,8 @@ export const getAccountDisplayName = (
|
|
|
79
69
|
|
|
80
70
|
const { name, displayName, username, publicKey } = user;
|
|
81
71
|
|
|
72
|
+
if (typeof displayName === 'string' && displayName.trim()) return displayName.trim();
|
|
73
|
+
|
|
82
74
|
if (name && typeof name === 'object') {
|
|
83
75
|
if (typeof name.full === 'string' && name.full.trim()) return name.full.trim();
|
|
84
76
|
const first = typeof name.first === 'string' ? name.first.trim() : '';
|
|
@@ -89,8 +81,6 @@ export const getAccountDisplayName = (
|
|
|
89
81
|
return name.trim();
|
|
90
82
|
}
|
|
91
83
|
|
|
92
|
-
if (typeof displayName === 'string' && displayName.trim()) return displayName.trim();
|
|
93
|
-
|
|
94
84
|
if (typeof username === 'string' && username.trim()) return username.trim();
|
|
95
85
|
|
|
96
86
|
if (typeof publicKey === 'string' && publicKey.length > 0) {
|
package/src/utils/asyncUtils.ts
CHANGED
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
* Async utilities for common asynchronous patterns and error handling
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import { TTLCache, registerCacheForCleanup } from './cache';
|
|
6
5
|
import { logger } from './loggerUtils';
|
|
7
6
|
|
|
8
7
|
/**
|
|
@@ -278,4 +277,4 @@ export async function retryOnError<T>(
|
|
|
278
277
|
const errorCode = error?.code || error?.status || error?.message;
|
|
279
278
|
return retryableErrors.includes(errorCode);
|
|
280
279
|
});
|
|
281
|
-
}
|
|
280
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
export interface UserHandleInput {
|
|
2
|
+
username?: string | null;
|
|
3
|
+
handle?: string | null;
|
|
4
|
+
instance?: string | null;
|
|
5
|
+
isFederated?: boolean | null;
|
|
6
|
+
type?: string | null;
|
|
7
|
+
federation?: {
|
|
8
|
+
domain?: string | null;
|
|
9
|
+
} | null;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export type CanonicalUserHandleInput = UserHandleInput;
|
|
13
|
+
|
|
14
|
+
function normalizeHandlePart(value?: string | null): string | null {
|
|
15
|
+
const trimmed = value?.trim().replace(/^@+/, '');
|
|
16
|
+
if (!trimmed || /[/?#]/.test(trimmed)) return null;
|
|
17
|
+
return trimmed;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Returns the normalized profile handle used by Oxy consumers for display and
|
|
22
|
+
* profile routing.
|
|
23
|
+
*
|
|
24
|
+
* Local users resolve to `username`. Federated users resolve to
|
|
25
|
+
* `username@instance` when the username does not already include an instance.
|
|
26
|
+
* Route-like values are rejected so callers do not accidentally turn paths or
|
|
27
|
+
* query strings into profile destinations.
|
|
28
|
+
*/
|
|
29
|
+
export function getNormalizedUserHandle(user: UserHandleInput | null | undefined): string | null {
|
|
30
|
+
const username = normalizeHandlePart(user?.username ?? user?.handle);
|
|
31
|
+
if (!username) return null;
|
|
32
|
+
|
|
33
|
+
const isFederated = user?.isFederated === true || user?.type === 'federated';
|
|
34
|
+
const instance = normalizeHandlePart(user?.instance ?? user?.federation?.domain);
|
|
35
|
+
|
|
36
|
+
if (isFederated && instance && !username.includes('@')) {
|
|
37
|
+
return `${username}@${instance}`;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return username;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Compatibility alias for the first public name shipped with this helper.
|
|
45
|
+
* Prefer {@link getNormalizedUserHandle} in new code.
|
|
46
|
+
*/
|
|
47
|
+
export function getCanonicalUserHandle(user: CanonicalUserHandleInput | null | undefined): string | null {
|
|
48
|
+
return getNormalizedUserHandle(user);
|
|
49
|
+
}
|
|
@@ -3,17 +3,6 @@ interface UserIdentityInput {
|
|
|
3
3
|
_id?: unknown;
|
|
4
4
|
}
|
|
5
5
|
|
|
6
|
-
export interface CanonicalUserHandleInput {
|
|
7
|
-
username?: string | null;
|
|
8
|
-
handle?: string | null;
|
|
9
|
-
instance?: string | null;
|
|
10
|
-
isFederated?: boolean | null;
|
|
11
|
-
type?: string | null;
|
|
12
|
-
federation?: {
|
|
13
|
-
domain?: string | null;
|
|
14
|
-
} | null;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
6
|
function stringifyIdentity(value: unknown): string | null {
|
|
18
7
|
if (typeof value === 'string') {
|
|
19
8
|
const trimmed = value.trim();
|
|
@@ -38,12 +27,10 @@ function stringifyIdentity(value: unknown): string | null {
|
|
|
38
27
|
return null;
|
|
39
28
|
}
|
|
40
29
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
}
|
|
46
|
-
|
|
30
|
+
/**
|
|
31
|
+
* Returns the stable SDK user id from API payloads that may use either `id` or
|
|
32
|
+
* Mongo-style `_id`.
|
|
33
|
+
*/
|
|
47
34
|
export function getNormalizedUserId(user: UserIdentityInput | null | undefined): string | null {
|
|
48
35
|
if (!user) {
|
|
49
36
|
return null;
|
|
@@ -52,6 +39,11 @@ export function getNormalizedUserId(user: UserIdentityInput | null | undefined):
|
|
|
52
39
|
return stringifyIdentity(user.id) ?? stringifyIdentity(user._id);
|
|
53
40
|
}
|
|
54
41
|
|
|
42
|
+
/**
|
|
43
|
+
* Normalizes a user payload to always expose `id`. Throws when the payload does
|
|
44
|
+
* not contain a usable id, because SDK callers should never receive anonymous
|
|
45
|
+
* user objects from authenticated identity endpoints.
|
|
46
|
+
*/
|
|
55
47
|
export function normalizeUserIdentity<T extends UserIdentityInput>(user: T): T & { id: string } {
|
|
56
48
|
const id = getNormalizedUserId(user);
|
|
57
49
|
if (!id) {
|
|
@@ -66,17 +58,3 @@ export function normalizeUserIdentityOrNull<T extends UserIdentityInput>(
|
|
|
66
58
|
): (T & { id: string }) | null {
|
|
67
59
|
return user ? normalizeUserIdentity(user) : null;
|
|
68
60
|
}
|
|
69
|
-
|
|
70
|
-
export function getCanonicalUserHandle(user: CanonicalUserHandleInput | null | undefined): string | null {
|
|
71
|
-
const username = normalizeHandle(user?.username ?? user?.handle);
|
|
72
|
-
if (!username) return null;
|
|
73
|
-
|
|
74
|
-
const isFederated = user?.isFederated === true || user?.type === 'federated';
|
|
75
|
-
const instance = normalizeHandle(user?.instance ?? user?.federation?.domain);
|
|
76
|
-
|
|
77
|
-
if (isFederated && instance && !username.includes('@')) {
|
|
78
|
-
return `${username}@${instance}`;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
return username;
|
|
82
|
-
}
|