@oxyhq/core 3.4.16 → 3.4.17

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.
@@ -2,6 +2,7 @@
2
2
  * User Management Methods Mixin
3
3
  */
4
4
  import type { User, Notification, NotificationPreferences, UserPreferences, SearchProfilesResponse, PrivacySettings } from '../models/interfaces';
5
+ import type { UserNameResponse, UserProfileUpdate } from '@oxyhq/contracts';
5
6
  import type { OxyServicesBase } from '../OxyServices.base';
6
7
  import { type PaginationParams } from '../utils/apiUtils';
7
8
  export declare function OxyServicesUserMixin<T extends typeof OxyServicesBase>(Base: T): {
@@ -12,7 +13,7 @@ export declare function OxyServicesUserMixin<T extends typeof OxyServicesBase>(B
12
13
  getProfileByUsername(username: string): Promise<User>;
13
14
  /**
14
15
  * Lightweight username lookup for login flows.
15
- * Returns minimal public info: exists, color, avatar, displayName.
16
+ * Returns minimal public info: exists, color, avatar, name.displayName.
16
17
  * Faster than getProfileByUsername — no stats, no formatting.
17
18
  */
18
19
  lookupUsername(username: string): Promise<{
@@ -20,7 +21,7 @@ export declare function OxyServicesUserMixin<T extends typeof OxyServicesBase>(B
20
21
  username: string;
21
22
  color: string | null;
22
23
  avatar: string | null;
23
- displayName: string;
24
+ name: UserNameResponse;
24
25
  }>;
25
26
  /**
26
27
  * Search user profiles
@@ -62,11 +63,7 @@ export declare function OxyServicesUserMixin<T extends typeof OxyServicesBase>(B
62
63
  }): Promise<Array<{
63
64
  id: string;
64
65
  username: string;
65
- name?: {
66
- first?: string;
67
- last?: string;
68
- full?: string;
69
- };
66
+ name: UserNameResponse;
70
67
  description?: string;
71
68
  isFederated?: boolean;
72
69
  isAgent?: boolean;
@@ -111,7 +108,7 @@ export declare function OxyServicesUserMixin<T extends typeof OxyServicesBase>(B
111
108
  *
112
109
  * TanStack Query handles offline queuing automatically.
113
110
  */
114
- updateProfile(updates: Partial<User>): Promise<User>;
111
+ updateProfile(updates: UserProfileUpdate): Promise<User>;
115
112
  /**
116
113
  * Get privacy settings for a user
117
114
  * @param userId - The user ID (defaults to current user)
@@ -80,29 +80,28 @@ export interface User {
80
80
  id: string;
81
81
  publicKey: string;
82
82
  username: string;
83
- /** Canonical display name resolved by the API. */
84
- displayName: string;
85
83
  email?: string;
86
- avatar?: string;
87
- color?: string;
84
+ avatar?: string | null;
85
+ color?: string | null;
88
86
  privacySettings?: PrivacySettings;
89
87
  /**
90
- * Structured human name. The canonical wire shape ({@link UserNameResponse}):
91
- * `{ first?, last?, full? }` where `full` is a Mongoose virtual (present only
92
- * when the query materialised virtuals). The single source of truth lives in
93
- * `@oxyhq/contracts` — do NOT re-declare a bare `string` here.
88
+ * Structured human name. `name.displayName` is the canonical display string
89
+ * resolved by the API; consumers render it directly instead of recomposing
90
+ * names from `first` / `last` / `full` / `username`.
94
91
  */
95
- name?: UserNameResponse;
92
+ name: UserNameResponse;
96
93
  bio?: string;
97
94
  location?: string;
98
95
  website?: string;
99
96
  createdAt?: string;
100
97
  updatedAt?: string;
101
- links?: Array<{
98
+ links?: string[];
99
+ linksMetadata?: Array<{
100
+ url: string;
102
101
  title?: string;
103
102
  description?: string;
104
103
  image?: string;
105
- link: string;
104
+ id?: string;
106
105
  }>;
107
106
  _count?: {
108
107
  followers?: number;
@@ -541,7 +540,7 @@ export interface RefreshAllAccountUser {
541
540
  * string. The server projects `name` verbatim from the user document. The
542
541
  * single source of truth is `@oxyhq/contracts`.
543
542
  */
544
- name?: UserNameResponse;
543
+ name: UserNameResponse;
545
544
  avatar?: string | null;
546
545
  email?: string;
547
546
  color?: string | null;
@@ -1,3 +1,4 @@
1
+ import type { UserNameResponse } from '@oxyhq/contracts';
1
2
  export interface ClientSession {
2
3
  sessionId: string;
3
4
  deviceId: string;
@@ -21,6 +22,7 @@ export interface StorageKeys {
21
22
  export interface MinimalUserData {
22
23
  id: string;
23
24
  username: string;
25
+ name: UserNameResponse;
24
26
  avatar?: string;
25
27
  }
26
28
  export interface SessionLoginResponse {
@@ -27,12 +27,13 @@ export interface QuickAccount {
27
27
  /** Minimal user shape accepted by display-name helpers. Avoids importing the full User type. */
28
28
  export interface DisplayNameUserShape {
29
29
  name?: string | {
30
+ displayName?: string;
30
31
  first?: string;
31
32
  last?: string;
32
33
  full?: string;
33
34
  [key: string]: unknown;
34
35
  };
35
- /** Canonical display name resolved by the API. */
36
+ /** Pre-normalized account-row display name, not the API User DTO field. */
36
37
  displayName?: string;
37
38
  username?: string;
38
39
  publicKey?: string;
@@ -46,12 +47,13 @@ export declare const formatPublicKeyHandle: (publicKey: string) => string;
46
47
  * Resolve a friendly display name for a user.
47
48
  *
48
49
  * Order of preference:
49
- * 1. `displayName` from the API contract.
50
+ * 1. `name.displayName` from the API user contract.
50
51
  * 2. `name.full`, or composed `name.first name.last` for local unsaved shapes.
51
- * 3. `name` when stored as a plain string.
52
- * 4. `username`
53
- * 5. `Account 0x12345678…` (derived from publicKey, when present)
54
- * 6. Translated fallback (e.g. "Unnamed")
52
+ * 3. `name` when passed as a plain string by local non-DTO call sites.
53
+ * 4. pre-normalized account-row `displayName`.
54
+ * 5. `username`
55
+ * 6. `Account 0x12345678…` (derived from publicKey, when present)
56
+ * 7. Translated fallback (e.g. "Unnamed")
55
57
  *
56
58
  * The translation key `common.unnamed` is used for the final fallback. If the
57
59
  * caller does not pass a locale, the default English translation is used.
@@ -79,6 +81,7 @@ export declare const buildAccountsArray: (accounts: Record<string, QuickAccount>
79
81
  */
80
82
  export declare const createQuickAccount: (sessionId: string, userData: {
81
83
  name?: string | {
84
+ displayName?: string;
82
85
  full?: string;
83
86
  first?: string;
84
87
  last?: string;
@@ -89,7 +92,7 @@ export declare const createQuickAccount: (sessionId: string, userData: {
89
92
  _id?: {
90
93
  toString(): string;
91
94
  } | string;
92
- avatar?: string;
95
+ avatar?: string | null;
93
96
  }, existingAccount?: QuickAccount, getFileDownloadUrl?: (fileId: string, variant: string) => string) => QuickAccount;
94
97
  /**
95
98
  * Merge a fresh `/auth/refresh-all` snapshot into an existing QuickAccount
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oxyhq/core",
3
- "version": "3.4.16",
3
+ "version": "3.4.17",
4
4
  "description": "OxyHQ SDK Foundation — API client, authentication, cryptographic identity, and shared utilities",
5
5
  "main": "dist/cjs/index.js",
6
6
  "module": "dist/esm/index.js",
@@ -97,7 +97,7 @@
97
97
  }
98
98
  },
99
99
  "dependencies": {
100
- "@oxyhq/contracts": "^0.1.0",
100
+ "@oxyhq/contracts": "^0.1.1",
101
101
  "bip39": "^3.1.0",
102
102
  "buffer": "^6.0.3",
103
103
  "elliptic": "^6.6.1",
@@ -497,7 +497,7 @@ export class AuthManager {
497
497
  this.oxyServices.httpService.setTokens(session.accessToken);
498
498
  }
499
499
 
500
- if (session.user && typeof (session.user as any).id === 'string' && (session.user as any).id.length > 0) {
500
+ if (session.user && typeof session.user.id === 'string' && session.user.id.length > 0) {
501
501
  this.currentUser = session.user;
502
502
  }
503
503
 
@@ -514,6 +514,7 @@ export class AuthManager {
514
514
  user: {
515
515
  id: session.user.id,
516
516
  username: session.user.username,
517
+ name: session.user.name,
517
518
  avatar: session.user.avatar ?? null,
518
519
  },
519
520
  accessToken: session.accessToken,
@@ -758,6 +759,7 @@ export class AuthManager {
758
759
  return {
759
760
  id: account.user.id,
760
761
  username: account.user.username,
762
+ name: account.user.name,
761
763
  avatar: account.user.avatar ?? undefined,
762
764
  };
763
765
  }
@@ -806,6 +808,7 @@ export class AuthManager {
806
808
  this.currentUser = {
807
809
  id: hydrated.id,
808
810
  username: hydrated.username,
811
+ name: hydrated.name,
809
812
  avatar: hydrated.avatar ?? undefined,
810
813
  };
811
814
  this.notifyListeners();
@@ -1022,6 +1025,7 @@ export class AuthManager {
1022
1025
  ? {
1023
1026
  id: updated.user.id,
1024
1027
  username: updated.user.username,
1028
+ name: updated.user.name,
1025
1029
  avatar: updated.user.avatar ?? undefined,
1026
1030
  }
1027
1031
  : null;
@@ -1083,6 +1087,7 @@ export class AuthManager {
1083
1087
  ? {
1084
1088
  id: next.user.id,
1085
1089
  username: next.user.username,
1090
+ name: next.user.name,
1086
1091
  avatar: next.user.avatar ?? undefined,
1087
1092
  }
1088
1093
  : null;
@@ -684,7 +684,7 @@ export function OxyServicesAuthMixin<T extends typeof OxyServicesBase>(Base: T)
684
684
  continue;
685
685
  }
686
686
  const userId = e.user.id ?? e.user._id;
687
- if (!userId || !e.user.username) {
687
+ if (!userId || !e.user.username || !e.user.name?.displayName) {
688
688
  continue;
689
689
  }
690
690
  if (typeof e.authuser !== 'number') {
@@ -25,6 +25,7 @@
25
25
 
26
26
  import type { OxyServicesBase } from '../OxyServices.base';
27
27
  import type { SessionLoginResponse, MinimalUserData } from '../models/session';
28
+ import type { UserNameResponse } from '@oxyhq/contracts';
28
29
  import { createDebugLogger } from '../shared/utils/debugUtils';
29
30
 
30
31
  const debug = createDebugLogger('SSO');
@@ -40,6 +41,7 @@ interface SsoExchangeWireResponse {
40
41
  id?: string;
41
42
  _id?: string;
42
43
  username?: string;
44
+ name?: UserNameResponse;
43
45
  avatar?: string;
44
46
  };
45
47
  expiresAt?: string;
@@ -140,13 +142,14 @@ export function OxyServicesSsoMixin<T extends typeof OxyServicesBase>(Base: T) {
140
142
  }
141
143
 
142
144
  const userId = payload.user?.id ?? payload.user?._id;
143
- if (!userId || typeof payload.user?.username !== 'string') {
145
+ if (!userId || typeof payload.user?.username !== 'string' || typeof payload.user.name?.displayName !== 'string') {
144
146
  throw this.handleError(new Error('SSO exchange returned an invalid user'));
145
147
  }
146
148
 
147
149
  const user: MinimalUserData = {
148
150
  id: userId,
149
151
  username: payload.user.username,
152
+ name: payload.user.name,
150
153
  avatar: payload.user.avatar,
151
154
  };
152
155
 
@@ -10,6 +10,7 @@ import type {
10
10
  PaginationInfo,
11
11
  PrivacySettings,
12
12
  } from '../models/interfaces';
13
+ import type { UserNameResponse, UserProfileUpdate } from '@oxyhq/contracts';
13
14
  import type { OxyServicesBase } from '../OxyServices.base';
14
15
  import { buildSearchParams, buildPaginationParams, type PaginationParams } from '../utils/apiUtils';
15
16
  import { KeyManager } from '../crypto/keyManager';
@@ -38,7 +39,7 @@ export function OxyServicesUserMixin<T extends typeof OxyServicesBase>(Base: T)
38
39
 
39
40
  /**
40
41
  * Lightweight username lookup for login flows.
41
- * Returns minimal public info: exists, color, avatar, displayName.
42
+ * Returns minimal public info: exists, color, avatar, name.displayName.
42
43
  * Faster than getProfileByUsername — no stats, no formatting.
43
44
  */
44
45
  async lookupUsername(username: string): Promise<{
@@ -46,7 +47,7 @@ export function OxyServicesUserMixin<T extends typeof OxyServicesBase>(Base: T)
46
47
  username: string;
47
48
  color: string | null;
48
49
  avatar: string | null;
49
- displayName: string;
50
+ name: UserNameResponse;
50
51
  }> {
51
52
  return await this.makeRequest('GET', `/auth/lookup/${encodeURIComponent(username)}`, undefined, {
52
53
  cache: true,
@@ -150,7 +151,7 @@ export function OxyServicesUserMixin<T extends typeof OxyServicesBase>(Base: T)
150
151
  }): Promise<Array<{
151
152
  id: string;
152
153
  username: string;
153
- name?: { first?: string; last?: string; full?: string };
154
+ name: UserNameResponse;
154
155
  description?: string;
155
156
  isFederated?: boolean;
156
157
  isAgent?: boolean;
@@ -225,7 +226,7 @@ export function OxyServicesUserMixin<T extends typeof OxyServicesBase>(Base: T)
225
226
  *
226
227
  * TanStack Query handles offline queuing automatically.
227
228
  */
228
- async updateProfile(updates: Partial<User>): Promise<User> {
229
+ async updateProfile(updates: UserProfileUpdate): Promise<User> {
229
230
  try {
230
231
  const result = normalizeUserIdentity(
231
232
  await this.makeRequest<User>('PUT', '/users/me', updates, { cache: false }),
@@ -87,32 +87,31 @@ export interface User {
87
87
  id: string;
88
88
  publicKey: string;
89
89
  username: string;
90
- /** Canonical display name resolved by the API. */
91
- displayName: string;
92
90
  email?: string;
93
91
  // Avatar file id (asset id)
94
- avatar?: string;
92
+ avatar?: string | null;
95
93
  // Named color preset (e.g. 'teal', 'blue', 'purple')
96
- color?: string;
94
+ color?: string | null;
97
95
  // Privacy and security settings
98
96
  privacySettings?: PrivacySettings;
99
97
  /**
100
- * Structured human name. The canonical wire shape ({@link UserNameResponse}):
101
- * `{ first?, last?, full? }` where `full` is a Mongoose virtual (present only
102
- * when the query materialised virtuals). The single source of truth lives in
103
- * `@oxyhq/contracts` — do NOT re-declare a bare `string` here.
98
+ * Structured human name. `name.displayName` is the canonical display string
99
+ * resolved by the API; consumers render it directly instead of recomposing
100
+ * names from `first` / `last` / `full` / `username`.
104
101
  */
105
- name?: UserNameResponse;
102
+ name: UserNameResponse;
106
103
  bio?: string;
107
104
  location?: string;
108
105
  website?: string;
109
106
  createdAt?: string;
110
107
  updatedAt?: string;
111
- links?: Array<{
108
+ links?: string[];
109
+ linksMetadata?: Array<{
110
+ url: string;
112
111
  title?: string;
113
112
  description?: string;
114
113
  image?: string;
115
- link: string;
114
+ id?: string;
116
115
  }>;
117
116
  // Social counts
118
117
  _count?: {
@@ -653,7 +652,7 @@ export interface RefreshAllAccountUser {
653
652
  * string. The server projects `name` verbatim from the user document. The
654
653
  * single source of truth is `@oxyhq/contracts`.
655
654
  */
656
- name?: UserNameResponse;
655
+ name: UserNameResponse;
657
656
  avatar?: string | null;
658
657
  email?: string;
659
658
  color?: string | null;
@@ -1,3 +1,5 @@
1
+ import type { UserNameResponse } from '@oxyhq/contracts';
2
+
1
3
  export interface ClientSession {
2
4
  sessionId: string;
3
5
  deviceId: string;
@@ -23,6 +25,7 @@ export interface StorageKeys {
23
25
  export interface MinimalUserData {
24
26
  id: string;
25
27
  username: string;
28
+ name: UserNameResponse;
26
29
  avatar?: string; // file id
27
30
  }
28
31
 
@@ -5,11 +5,10 @@ import {
5
5
  } from '../accountUtils';
6
6
 
7
7
  describe('getAccountDisplayName', () => {
8
- it('prefers the API displayName when present', () => {
8
+ it('prefers the API name.displayName when present', () => {
9
9
  expect(
10
10
  getAccountDisplayName({
11
- name: { first: 'Nate' },
12
- displayName: 'Nate Isern',
11
+ name: { first: 'Nate', displayName: 'Nate Isern' },
13
12
  username: 'nateus',
14
13
  }),
15
14
  ).toBe('Nate Isern');
@@ -51,7 +50,7 @@ describe('getAccountDisplayName', () => {
51
50
  ).toBe('Nathaniel Isern');
52
51
  });
53
52
 
54
- it('uses displayName when there is no structured name', () => {
53
+ it('uses pre-normalized account-row displayName when there is no structured name', () => {
55
54
  expect(
56
55
  getAccountDisplayName({
57
56
  displayName: 'Cool Display',
@@ -30,8 +30,14 @@ export interface QuickAccount {
30
30
 
31
31
  /** Minimal user shape accepted by display-name helpers. Avoids importing the full User type. */
32
32
  export interface DisplayNameUserShape {
33
- name?: string | { first?: string; last?: string; full?: string; [key: string]: unknown };
34
- /** Canonical display name resolved by the API. */
33
+ name?: string | {
34
+ displayName?: string;
35
+ first?: string;
36
+ last?: string;
37
+ full?: string;
38
+ [key: string]: unknown;
39
+ };
40
+ /** Pre-normalized account-row display name, not the API User DTO field. */
35
41
  displayName?: string;
36
42
  username?: string;
37
43
  publicKey?: string;
@@ -51,12 +57,13 @@ export const formatPublicKeyHandle = (publicKey: string): string => {
51
57
  * Resolve a friendly display name for a user.
52
58
  *
53
59
  * Order of preference:
54
- * 1. `displayName` from the API contract.
60
+ * 1. `name.displayName` from the API user contract.
55
61
  * 2. `name.full`, or composed `name.first name.last` for local unsaved shapes.
56
- * 3. `name` when stored as a plain string.
57
- * 4. `username`
58
- * 5. `Account 0x12345678…` (derived from publicKey, when present)
59
- * 6. Translated fallback (e.g. "Unnamed")
62
+ * 3. `name` when passed as a plain string by local non-DTO call sites.
63
+ * 4. pre-normalized account-row `displayName`.
64
+ * 5. `username`
65
+ * 6. `Account 0x12345678…` (derived from publicKey, when present)
66
+ * 7. Translated fallback (e.g. "Unnamed")
60
67
  *
61
68
  * The translation key `common.unnamed` is used for the final fallback. If the
62
69
  * caller does not pass a locale, the default English translation is used.
@@ -69,9 +76,10 @@ export const getAccountDisplayName = (
69
76
 
70
77
  const { name, displayName, username, publicKey } = user;
71
78
 
72
- if (typeof displayName === 'string' && displayName.trim()) return displayName.trim();
73
-
74
79
  if (name && typeof name === 'object') {
80
+ if (typeof name.displayName === 'string' && name.displayName.trim()) {
81
+ return name.displayName.trim();
82
+ }
75
83
  if (typeof name.full === 'string' && name.full.trim()) return name.full.trim();
76
84
  const first = typeof name.first === 'string' ? name.first.trim() : '';
77
85
  const last = typeof name.last === 'string' ? name.last.trim() : '';
@@ -81,6 +89,8 @@ export const getAccountDisplayName = (
81
89
  return name.trim();
82
90
  }
83
91
 
92
+ if (typeof displayName === 'string' && displayName.trim()) return displayName.trim();
93
+
84
94
  if (typeof username === 'string' && username.trim()) return username.trim();
85
95
 
86
96
  if (typeof publicKey === 'string' && publicKey.length > 0) {
@@ -136,12 +146,12 @@ export const buildAccountsArray = (
136
146
  export const createQuickAccount = (
137
147
  sessionId: string,
138
148
  userData: {
139
- name?: string | { full?: string; first?: string; last?: string };
149
+ name?: string | { displayName?: string; full?: string; first?: string; last?: string };
140
150
  username?: string;
141
151
  publicKey?: string;
142
152
  id?: string;
143
153
  _id?: { toString(): string } | string;
144
- avatar?: string;
154
+ avatar?: string | null;
145
155
  },
146
156
  existingAccount?: QuickAccount,
147
157
  getFileDownloadUrl?: (fileId: string, variant: string) => string
@@ -151,10 +161,11 @@ export const createQuickAccount = (
151
161
 
152
162
  // Preserve existing avatarUrl if avatar hasn't changed (prevents image reload)
153
163
  let avatarUrl: string | undefined;
154
- if (existingAccount && existingAccount.avatar === userData.avatar && existingAccount.avatarUrl) {
164
+ const avatar = userData.avatar ?? undefined;
165
+ if (existingAccount && existingAccount.avatar === avatar && existingAccount.avatarUrl) {
155
166
  avatarUrl = existingAccount.avatarUrl;
156
- } else if (userData.avatar && getFileDownloadUrl) {
157
- avatarUrl = getFileDownloadUrl(userData.avatar, 'thumb');
167
+ } else if (avatar && getFileDownloadUrl) {
168
+ avatarUrl = getFileDownloadUrl(avatar, 'thumb');
158
169
  }
159
170
 
160
171
  return {
@@ -162,7 +173,7 @@ export const createQuickAccount = (
162
173
  userId,
163
174
  username: userData.username || '',
164
175
  displayName,
165
- avatar: userData.avatar,
176
+ avatar,
166
177
  avatarUrl,
167
178
  };
168
179
  };