@oxyhq/core 5.1.0 → 5.1.1

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.
@@ -230,16 +230,28 @@ export function OxyServicesUserMixin(Base) {
230
230
  * by `id`); each is run through `normalizeUserIdentity`, matching
231
231
  * `getUserById`.
232
232
  *
233
- * **Service-token auth (required).** `/users/by-ids` is a server-to-server
234
- * bulk fetch of PUBLIC user data and is called via `makeServiceRequest`,
235
- * which attaches `Authorization: Bearer <serviceToken>`. oxy-api's CSRF
236
- * middleware skips bearer-authenticated requests, so the calling client
237
- * MUST be service-configured (`configureServiceAuth(apiKey, apiSecret)`)
238
- * before invoking this method; otherwise `getServiceToken()` throws because
239
- * no credentials are available. (A plain user-session request fails here:
240
- * server-to-server there is no cookie jar, so the auto-attached
241
- * `X-CSRF-Token` has no matching cookie and oxy-api rejects the POST with
242
- * 403 "CSRF token missing".)
233
+ * **Dual-mode auth.** `/users/by-ids` is `optionalUserOrServiceAuth` on
234
+ * oxy-api: it accepts a service token, a user session, or an anonymous
235
+ * caller, and returns the SAME public `{ data: PublicUserProfile[] }`
236
+ * payload (canonical `name.displayName` + `_count`) in every case — no
237
+ * viewer-specific fields. This method picks the path automatically:
238
+ * - **Service-configured host (backend):** when `configureServiceAuth(apiKey,
239
+ * apiSecret)` has been called, the chunk is fetched via `makeServiceRequest`
240
+ * (attaches `Authorization: Bearer <serviceToken>`). This is the
241
+ * server-to-server feed/notification hydration path (e.g. Mention's
242
+ * `PostHydrationService`) and is unchanged.
243
+ * - **Plain client (browser / React Native with a user session):** when no
244
+ * service credentials are configured, the chunk is fetched via
245
+ * `makeRequest`, which attaches the configured user bearer. oxy-api's CSRF
246
+ * middleware skips bearer-authenticated writes, and `makeRequest` only
247
+ * fetches a CSRF token for cookie-only (no-bearer) state-changing requests,
248
+ * so the user-bearer POST is sent without CSRF and succeeds. Previously
249
+ * this method always used the service path, so every client-side caller
250
+ * silently received `[]` because `getServiceToken()` had no credentials.
251
+ *
252
+ * Both paths run results through `normalizeUserIdentity` and unwrap the
253
+ * API's `{ data }` envelope identically (`makeServiceRequest` is literally
254
+ * `makeRequest` plus a bearer service header).
243
255
  *
244
256
  * Resilience: chunks are independent. A failed chunk is logged and skipped
245
257
  * — the method returns every user that resolved successfully rather than
@@ -258,15 +270,22 @@ export function OxyServicesUserMixin(Base) {
258
270
  for (let i = 0; i < uniqueIds.length; i += USERS_BY_IDS_CHUNK_SIZE) {
259
271
  chunks.push(uniqueIds.slice(i, i + USERS_BY_IDS_CHUNK_SIZE));
260
272
  }
273
+ // A backend that called configureServiceAuth() uses the bearer-service
274
+ // path; any other caller (browser / RN with a user session) uses the
275
+ // user-bearer path. See the method doc for why the user path is CSRF-safe.
276
+ const useServiceAuth = Boolean(this._serviceApiKey && this._serviceApiSecret);
261
277
  // Run chunks concurrently; a single chunk failure must not sink the rest.
262
278
  const settled = await Promise.all(chunks.map(async (chunk) => {
263
279
  try {
264
- const users = await this.makeServiceRequest('POST', '/users/by-ids', { ids: chunk });
280
+ const users = useServiceAuth
281
+ ? await this.makeServiceRequest('POST', '/users/by-ids', { ids: chunk })
282
+ : await this.makeRequest('POST', '/users/by-ids', { ids: chunk }, { cache: false });
265
283
  return Array.isArray(users) ? users.map((user) => normalizeUserIdentity(user)) : [];
266
284
  }
267
285
  catch (error) {
268
286
  logger.warn('getUsersByIds: chunk failed, continuing with remaining chunks', {
269
287
  method: 'getUsersByIds',
288
+ mode: useServiceAuth ? 'service' : 'user',
270
289
  chunkSize: chunk.length,
271
290
  status: extractErrorStatus(error),
272
291
  error: error instanceof Error ? error.message : String(error),