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