@oxy.so/core 7.12.0 → 7.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/dist/cjs/api/apps.js +1 -1
  2. package/dist/cjs/api/auth.js +1 -1
  3. package/dist/cjs/api/billing.js +4 -3
  4. package/dist/cjs/api/devices.js +5 -4
  5. package/dist/cjs/api/follows.js +11 -10
  6. package/dist/cjs/api/privacy.js +8 -7
  7. package/dist/cjs/api/session.js +10 -7
  8. package/dist/cjs/api/topics.js +4 -3
  9. package/dist/cjs/api/users.js +5 -4
  10. package/dist/cjs/i18n/locales/compact/es-ES.generated.js +1 -1
  11. package/dist/cjs/i18n/locales/en-US.js +40 -15
  12. package/dist/cjs/i18n/locales/es-ES.js +40 -15
  13. package/dist/esm/api/apps.js +1 -1
  14. package/dist/esm/api/auth.js +1 -1
  15. package/dist/esm/api/billing.js +4 -3
  16. package/dist/esm/api/devices.js +5 -4
  17. package/dist/esm/api/follows.js +11 -10
  18. package/dist/esm/api/privacy.js +8 -7
  19. package/dist/esm/api/session.js +10 -7
  20. package/dist/esm/api/topics.js +4 -3
  21. package/dist/esm/api/users.js +5 -4
  22. package/dist/esm/i18n/locales/compact/es-ES.generated.js +1 -1
  23. package/dist/esm/i18n/locales/en-US.js +40 -15
  24. package/dist/esm/i18n/locales/es-ES.js +40 -15
  25. package/dist/types/i18n/locales/en-US.d.ts +37 -12
  26. package/dist/types/i18n/locales/es-ES.d.ts +37 -12
  27. package/package.json +2 -2
  28. package/src/api/apps.ts +1 -1
  29. package/src/api/auth.ts +9 -4
  30. package/src/api/billing.ts +12 -5
  31. package/src/api/devices.ts +6 -4
  32. package/src/api/follows.ts +12 -10
  33. package/src/api/privacy.ts +9 -7
  34. package/src/api/session.ts +18 -9
  35. package/src/api/topics.ts +5 -3
  36. package/src/api/users.ts +6 -4
  37. package/src/i18n/locales/compact/es-ES.generated.ts +1 -1
  38. package/src/i18n/locales/en-US.ts +41 -15
  39. package/src/i18n/locales/es-ES.ts +42 -15
@@ -28,6 +28,8 @@ import {
28
28
  type FollowGraphParams,
29
29
  } from '../utils/apiUtils';
30
30
 
31
+ const enc = encodeURIComponent;
32
+
31
33
  const GRAPH_TTL = 2 * 60 * 1000;
32
34
  const STATUS_TTL = 60 * 1000;
33
35
 
@@ -130,7 +132,7 @@ export class FollowsApi {
130
132
  async follow(userId: string): Promise<FollowMutationResult> {
131
133
  const result = await this.ctx.request<FollowMutationResult>(
132
134
  'POST',
133
- `/users/${userId}/follow`,
135
+ `/users/${enc(userId)}/follow`,
134
136
  undefined,
135
137
  { cache: false },
136
138
  );
@@ -155,7 +157,7 @@ export class FollowsApi {
155
157
  async unfollow(userId: string): Promise<FollowMutationResult> {
156
158
  const result = await this.ctx.request<FollowMutationResult>(
157
159
  'DELETE',
158
- `/users/${userId}/follow`,
160
+ `/users/${enc(userId)}/follow`,
159
161
  undefined,
160
162
  { cache: false },
161
163
  );
@@ -178,7 +180,7 @@ export class FollowsApi {
178
180
 
179
181
  /** Whether the signed-in user follows `userId`. Cached 1 minute, busted by every write. */
180
182
  async status(userId: string): Promise<{ isFollowing: boolean }> {
181
- return this.ctx.request('GET', `/users/${userId}/follow-status`, undefined, {
183
+ return this.ctx.request('GET', `/users/${enc(userId)}/follow-status`, undefined, {
182
184
  cache: true,
183
185
  cacheTTL: STATUS_TTL,
184
186
  });
@@ -221,13 +223,13 @@ export class FollowsApi {
221
223
  * `limit`/`offset`/`sort` combination is its own cache entry (2 minutes).
222
224
  */
223
225
  async followers(userId: string, params?: FollowGraphParams): Promise<UserPage<'followers'>> {
224
- const { data, total, hasMore } = await this.userPage(`/users/${userId}/followers`, params);
226
+ const { data, total, hasMore } = await this.userPage(`/users/${enc(userId)}/followers`, params);
225
227
  return { followers: data, total, hasMore };
226
228
  }
227
229
 
228
230
  /** Who a user follows. `sort` as in `followers`. */
229
231
  async following(userId: string, params?: FollowGraphParams): Promise<UserPage<'following'>> {
230
- const { data, total, hasMore } = await this.userPage(`/users/${userId}/following`, params);
232
+ const { data, total, hasMore } = await this.userPage(`/users/${enc(userId)}/following`, params);
231
233
  return { following: data, total, hasMore };
232
234
  }
233
235
 
@@ -236,7 +238,7 @@ export class FollowsApi {
236
238
  * `userId`.
237
239
  */
238
240
  async mutuals(userId: string, params?: FollowGraphParams): Promise<UserPage<'mutuals'>> {
239
- const { data, total, hasMore } = await this.userPage(`/users/${userId}/mutuals`, params);
241
+ const { data, total, hasMore } = await this.userPage(`/users/${enc(userId)}/mutuals`, params);
240
242
  return { mutuals: data, total, hasMore };
241
243
  }
242
244
 
@@ -443,15 +445,15 @@ export class FollowsApi {
443
445
  const keys: string[] = ['GET:/users/me/graph'];
444
446
  const prefixes: string[] = ['GET:/profiles/username/', 'GET:/profiles/resolve'];
445
447
  for (const id of targetUserIds) {
446
- keys.push(`GET:/users/${id}/follow-status`);
448
+ keys.push(`GET:/users/${enc(id)}/follow-status`);
447
449
  // Profiles embed the viewer-relative `relationship`.
448
- keys.push(`GET:/users/${id}`);
450
+ keys.push(`GET:/users/${enc(id)}`);
449
451
  // The target gained or lost a follower, and "followers you know" with it.
450
- prefixes.push(`GET:/users/${id}/followers`, `GET:/users/${id}/mutuals`);
452
+ prefixes.push(`GET:/users/${enc(id)}/followers`, `GET:/users/${enc(id)}/mutuals`);
451
453
  }
452
454
  // The viewer's own following list changed.
453
455
  const viewerId = this.ctx.oxy.session.userId;
454
- if (viewerId) prefixes.push(`GET:/users/${viewerId}/following`);
456
+ if (viewerId) prefixes.push(`GET:/users/${enc(viewerId)}/following`);
455
457
  this.ctx.http.invalidateCache({ keys, prefixes });
456
458
  }
457
459
  }
@@ -5,6 +5,8 @@ import type { BlockedUser, PrivacySettings, RestrictedUser } from '../models/int
5
5
  import type { OxyContext } from '../client/context';
6
6
  import { evictOxyIdentityCache } from '../utils/identityCacheSweep';
7
7
 
8
+ const enc = encodeURIComponent;
9
+
8
10
  const LIST_TTL = 60 * 1000;
9
11
  const SETTINGS_TTL = 2 * 60 * 1000;
10
12
 
@@ -20,7 +22,7 @@ export class PrivacyApi {
20
22
  /** A user's privacy settings (default: the signed-in user's). */
21
23
  async settings(userId?: string): Promise<PrivacySettings> {
22
24
  const id = await this.resolveUserId(userId);
23
- return this.ctx.request<PrivacySettings>('GET', `/privacy/${id}/privacy`, undefined, {
25
+ return this.ctx.request<PrivacySettings>('GET', `/privacy/${enc(id)}/privacy`, undefined, {
24
26
  cache: true,
25
27
  cacheTTL: SETTINGS_TTL,
26
28
  });
@@ -34,13 +36,13 @@ export class PrivacyApi {
34
36
  const id = await this.resolveUserId(userId);
35
37
  const res = await this.ctx.request<PrivacySettings>(
36
38
  'PATCH',
37
- `/privacy/${id}/privacy`,
39
+ `/privacy/${enc(id)}/privacy`,
38
40
  settings,
39
41
  { cache: false },
40
42
  );
41
43
  // Privacy settings ride the user DTO, so every identity read goes stale too.
42
44
  evictOxyIdentityCache(this.ctx.http, id);
43
- this.ctx.oxy.cache.delete(`GET:/privacy/${id}/privacy`);
45
+ this.ctx.oxy.cache.delete(`GET:/privacy/${enc(id)}/privacy`);
44
46
  return res;
45
47
  }
46
48
 
@@ -59,7 +61,7 @@ export class PrivacyApi {
59
61
  requireId(userId);
60
62
  const res = await this.ctx.request<{ message: string }>(
61
63
  'POST',
62
- `/privacy/blocked/${userId}`,
64
+ `/privacy/blocked/${enc(userId)}`,
63
65
  undefined,
64
66
  { cache: false },
65
67
  );
@@ -72,7 +74,7 @@ export class PrivacyApi {
72
74
  requireId(userId);
73
75
  const res = await this.ctx.request<{ message: string }>(
74
76
  'DELETE',
75
- `/privacy/blocked/${userId}`,
77
+ `/privacy/blocked/${enc(userId)}`,
76
78
  undefined,
77
79
  { cache: false },
78
80
  );
@@ -105,7 +107,7 @@ export class PrivacyApi {
105
107
  requireId(userId);
106
108
  const res = await this.ctx.request<{ message: string }>(
107
109
  'POST',
108
- `/privacy/restricted/${userId}`,
110
+ `/privacy/restricted/${enc(userId)}`,
109
111
  undefined,
110
112
  { cache: false },
111
113
  );
@@ -118,7 +120,7 @@ export class PrivacyApi {
118
120
  requireId(userId);
119
121
  const res = await this.ctx.request<{ message: string }>(
120
122
  'DELETE',
121
- `/privacy/restricted/${userId}`,
123
+ `/privacy/restricted/${enc(userId)}`,
122
124
  undefined,
123
125
  { cache: false },
124
126
  );
@@ -20,6 +20,8 @@ import type {
20
20
  HolderEvidenceUnavailableReason,
21
21
  } from './holderEvidence';
22
22
 
23
+ const enc = encodeURIComponent;
24
+
23
25
  /** How long a refusal (disabled, rate limited, unreachable) is remembered per installation. */
24
26
  const EVIDENCE_REFUSAL_MS = 5_000;
25
27
  const unavailable = (reason: HolderEvidenceUnavailableReason): HolderEvidenceResult => ({
@@ -376,7 +378,7 @@ export class SessionApi {
376
378
  )
377
379
  : await this.ctx.request<SessionValidation>(
378
380
  'GET',
379
- `/session/validate/${sessionId}`,
381
+ `/session/validate/${enc(sessionId)}`,
380
382
  {
381
383
  ...(options.deviceFingerprint
382
384
  ? { deviceFingerprint: options.deviceFingerprint }
@@ -407,7 +409,7 @@ export class SessionApi {
407
409
  const invalid =
408
410
  candidate?.status === 401 && candidate.code === 'OXY_LIVE_SESSION_INVALID_V1';
409
411
  const denied = candidate?.status === 403 && candidate.code === 'OXY_LIVE_SESSION_POLICY_V1';
410
- if (invalid) this.ctx.oxy.cache.delete(`GET:/session/user/${sessionId}`);
412
+ if (invalid) this.ctx.oxy.cache.delete(`GET:/session/user/${enc(sessionId)}`);
411
413
  if (invalid || denied) throw error;
412
414
  throw new OxyApiError('Live session authority is unavailable', {
413
415
  status: 503,
@@ -416,29 +418,36 @@ export class SessionApi {
416
418
  });
417
419
  }
418
420
  // The session is gone: drop any user cached for it (#196).
419
- this.ctx.oxy.cache.delete(`GET:/session/user/${sessionId}`);
421
+ this.ctx.oxy.cache.delete(`GET:/session/user/${enc(sessionId)}`);
420
422
  throw error;
421
423
  }
422
424
  }
423
425
 
424
426
  /** The sessions that belong with `sessionId` (same account set). */
425
427
  async list(sessionId: string): Promise<ClientSession[]> {
426
- return this.ctx.request<ClientSession[]>('GET', `/session/sessions/${sessionId}`, undefined, {
427
- cache: false,
428
- });
428
+ return this.ctx.request<ClientSession[]>(
429
+ 'GET',
430
+ `/session/sessions/${enc(sessionId)}`,
431
+ undefined,
432
+ {
433
+ cache: false,
434
+ },
435
+ );
429
436
  }
430
437
 
431
438
  /** Sign out `sessionId`, or — with `targetSessionId` — one of the sessions beside it. */
432
439
  async logout(sessionId: string, targetSessionId?: string): Promise<void> {
433
440
  const url = targetSessionId
434
- ? `/session/logout/${sessionId}/${targetSessionId}`
435
- : `/session/logout/${sessionId}`;
441
+ ? `/session/logout/${enc(sessionId)}/${enc(targetSessionId)}`
442
+ : `/session/logout/${enc(sessionId)}`;
436
443
  await this.ctx.request('POST', url, undefined, { cache: false });
437
444
  }
438
445
 
439
446
  /** Sign out the other sessions for this account; preserve `sessionId`. */
440
447
  async logoutAll(sessionId: string): Promise<void> {
441
- await this.ctx.request('POST', `/session/logout-all/${sessionId}`, undefined, { cache: false });
448
+ await this.ctx.request('POST', `/session/logout-all/${enc(sessionId)}`, undefined, {
449
+ cache: false,
450
+ });
442
451
  }
443
452
 
444
453
  /** Decoded claims of the current token, memoised per token string. */
package/src/api/topics.ts CHANGED
@@ -4,6 +4,8 @@
4
4
  import type { OxyContext } from '../client/context';
5
5
  import type { TopicData, TopicListResult, TopicTranslation } from '../models/Topic';
6
6
 
7
+ const enc = encodeURIComponent;
8
+
7
9
  const SHORT = 60 * 1000;
8
10
  const LONG = 5 * 60 * 1000;
9
11
  const EXTRA_LONG = 30 * 60 * 1000;
@@ -51,7 +53,7 @@ export class TopicsApi {
51
53
 
52
54
  /** One topic by slug. */
53
55
  async get(slug: string): Promise<TopicData> {
54
- return this.ctx.request<TopicData>('GET', `/topics/${slug}`, undefined, {
56
+ return this.ctx.request<TopicData>('GET', `/topics/${enc(slug)}`, undefined, {
55
57
  cache: true,
56
58
  cacheTTL: LONG,
57
59
  });
@@ -86,10 +88,10 @@ export class TopicsApi {
86
88
  slug: string,
87
89
  data: { description?: string; translations?: Record<string, TopicTranslation> },
88
90
  ): Promise<TopicData> {
89
- const topic = await this.ctx.request<TopicData>('PATCH', `/topics/${slug}`, data, {
91
+ const topic = await this.ctx.request<TopicData>('PATCH', `/topics/${enc(slug)}`, data, {
90
92
  cache: false,
91
93
  });
92
- this.ctx.oxy.cache.delete(`GET:/topics/${slug}`);
94
+ this.ctx.oxy.cache.delete(`GET:/topics/${enc(slug)}`);
93
95
  return topic;
94
96
  }
95
97
  }
package/src/api/users.ts CHANGED
@@ -22,6 +22,8 @@ import { extractErrorStatus } from '../utils/errorUtils';
22
22
  import { type BatchLookup, runBatchLookup } from '../utils/batchLookup';
23
23
  import { logger } from '../logger';
24
24
 
25
+ const enc = encodeURIComponent;
26
+
25
27
  const PROFILE_TTL = 5 * 60 * 1000;
26
28
  const SESSION_USER_TTL = 2 * 60 * 1000;
27
29
  const ME_TTL = 60 * 1000;
@@ -65,7 +67,7 @@ export class UsersApi {
65
67
  * registry-fresh read that neither serves nor overwrites a cached entry.
66
68
  */
67
69
  async get(userId: string, options?: { cache?: boolean }): Promise<User> {
68
- const user = await this.ctx.request<User>('GET', `/users/${userId}`, undefined, {
70
+ const user = await this.ctx.request<User>('GET', `/users/${enc(userId)}`, undefined, {
69
71
  cache: options?.cache ?? true,
70
72
  cacheTTL: PROFILE_TTL,
71
73
  });
@@ -166,7 +168,7 @@ export class UsersApi {
166
168
 
167
169
  /** The user a session belongs to. */
168
170
  async bySession(sessionId: string): Promise<User> {
169
- const user = await this.ctx.request<User>('GET', `/session/user/${sessionId}`, undefined, {
171
+ const user = await this.ctx.request<User>('GET', `/session/user/${enc(sessionId)}`, undefined, {
170
172
  cache: true,
171
173
  cacheTTL: SESSION_USER_TTL,
172
174
  });
@@ -271,7 +273,7 @@ export class UsersApi {
271
273
  : (limitOrPagination ?? {});
272
274
  const users = await this.ctx.request<User[]>(
273
275
  'GET',
274
- `/profiles/${userId}/similar`,
276
+ `/profiles/${enc(userId)}/similar`,
275
277
  buildQueryParams(pagination),
276
278
  {
277
279
  cache: true,
@@ -289,7 +291,7 @@ export class UsersApi {
289
291
  async moderators(communityId: string): Promise<User[]> {
290
292
  const moderators = await this.ctx.request<User[]>(
291
293
  'GET',
292
- `/users/${communityId}/moderators`,
294
+ `/users/${enc(communityId)}/moderators`,
293
295
  undefined,
294
296
  { cache: true, cacheTTL: PROFILE_TTL },
295
297
  );