@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.
- package/dist/cjs/api/apps.js +1 -1
- package/dist/cjs/api/auth.js +1 -1
- package/dist/cjs/api/billing.js +4 -3
- package/dist/cjs/api/devices.js +5 -4
- package/dist/cjs/api/follows.js +11 -10
- package/dist/cjs/api/privacy.js +8 -7
- package/dist/cjs/api/session.js +10 -7
- package/dist/cjs/api/topics.js +4 -3
- package/dist/cjs/api/users.js +5 -4
- package/dist/cjs/i18n/locales/compact/es-ES.generated.js +1 -1
- package/dist/cjs/i18n/locales/en-US.js +40 -15
- package/dist/cjs/i18n/locales/es-ES.js +40 -15
- package/dist/esm/api/apps.js +1 -1
- package/dist/esm/api/auth.js +1 -1
- package/dist/esm/api/billing.js +4 -3
- package/dist/esm/api/devices.js +5 -4
- package/dist/esm/api/follows.js +11 -10
- package/dist/esm/api/privacy.js +8 -7
- package/dist/esm/api/session.js +10 -7
- package/dist/esm/api/topics.js +4 -3
- package/dist/esm/api/users.js +5 -4
- package/dist/esm/i18n/locales/compact/es-ES.generated.js +1 -1
- package/dist/esm/i18n/locales/en-US.js +40 -15
- package/dist/esm/i18n/locales/es-ES.js +40 -15
- package/dist/types/i18n/locales/en-US.d.ts +37 -12
- package/dist/types/i18n/locales/es-ES.d.ts +37 -12
- package/package.json +2 -2
- package/src/api/apps.ts +1 -1
- package/src/api/auth.ts +9 -4
- package/src/api/billing.ts +12 -5
- package/src/api/devices.ts +6 -4
- package/src/api/follows.ts +12 -10
- package/src/api/privacy.ts +9 -7
- package/src/api/session.ts +18 -9
- package/src/api/topics.ts +5 -3
- package/src/api/users.ts +6 -4
- package/src/i18n/locales/compact/es-ES.generated.ts +1 -1
- package/src/i18n/locales/en-US.ts +41 -15
- package/src/i18n/locales/es-ES.ts +42 -15
package/src/api/follows.ts
CHANGED
|
@@ -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
|
}
|
package/src/api/privacy.ts
CHANGED
|
@@ -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
|
);
|
package/src/api/session.ts
CHANGED
|
@@ -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[]>(
|
|
427
|
-
|
|
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, {
|
|
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
|
);
|