@oxy.so/core 7.11.1 → 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 (56) hide show
  1. package/dist/cjs/HttpService.js +70 -1
  2. package/dist/cjs/OxyServices.js +1 -1
  3. package/dist/cjs/api/apps.js +1 -1
  4. package/dist/cjs/api/auth.js +1 -1
  5. package/dist/cjs/api/billing.js +4 -3
  6. package/dist/cjs/api/devices.js +5 -4
  7. package/dist/cjs/api/follows.js +11 -10
  8. package/dist/cjs/api/holderEvidence.js +12 -0
  9. package/dist/cjs/api/privacy.js +8 -7
  10. package/dist/cjs/api/session.js +128 -7
  11. package/dist/cjs/api/topics.js +4 -3
  12. package/dist/cjs/api/users.js +5 -4
  13. package/dist/cjs/i18n/locales/compact/es-ES.generated.js +1 -1
  14. package/dist/cjs/i18n/locales/en-US.js +40 -15
  15. package/dist/cjs/i18n/locales/es-ES.js +40 -15
  16. package/dist/cjs/index.js +6 -4
  17. package/dist/esm/HttpService.js +70 -1
  18. package/dist/esm/OxyServices.js +1 -1
  19. package/dist/esm/api/apps.js +1 -1
  20. package/dist/esm/api/auth.js +1 -1
  21. package/dist/esm/api/billing.js +4 -3
  22. package/dist/esm/api/devices.js +5 -4
  23. package/dist/esm/api/follows.js +11 -10
  24. package/dist/esm/api/holderEvidence.js +9 -0
  25. package/dist/esm/api/privacy.js +8 -7
  26. package/dist/esm/api/session.js +128 -7
  27. package/dist/esm/api/topics.js +4 -3
  28. package/dist/esm/api/users.js +5 -4
  29. package/dist/esm/i18n/locales/compact/es-ES.generated.js +1 -1
  30. package/dist/esm/i18n/locales/en-US.js +40 -15
  31. package/dist/esm/i18n/locales/es-ES.js +40 -15
  32. package/dist/esm/index.js +1 -0
  33. package/dist/types/HttpService.d.ts +25 -1
  34. package/dist/types/api/holderEvidence.d.ts +45 -0
  35. package/dist/types/api/session.d.ts +21 -0
  36. package/dist/types/i18n/locales/en-US.d.ts +37 -12
  37. package/dist/types/i18n/locales/es-ES.d.ts +37 -12
  38. package/dist/types/index.d.ts +3 -1
  39. package/dist/types/server/safety.d.ts +9 -9
  40. package/package.json +2 -2
  41. package/src/HttpService.ts +108 -1
  42. package/src/OxyServices.ts +4 -1
  43. package/src/api/apps.ts +1 -1
  44. package/src/api/auth.ts +9 -4
  45. package/src/api/billing.ts +12 -5
  46. package/src/api/devices.ts +6 -4
  47. package/src/api/follows.ts +12 -10
  48. package/src/api/holderEvidence.ts +66 -0
  49. package/src/api/privacy.ts +9 -7
  50. package/src/api/session.ts +163 -9
  51. package/src/api/topics.ts +5 -3
  52. package/src/api/users.ts +6 -4
  53. package/src/i18n/locales/compact/es-ES.generated.ts +1 -1
  54. package/src/i18n/locales/en-US.ts +41 -15
  55. package/src/i18n/locales/es-ES.ts +42 -15
  56. package/src/index.ts +8 -0
@@ -161,6 +161,9 @@ const dict = {
161
161
  requestExpired: 'This sign-in expired. Send a new email.',
162
162
  secondFactorInvalid: "That code isn't right. Try the current one.",
163
163
  originNotAllowed: "Sign-in isn't available here.",
164
+ network: "Couldn't reach Oxy. Check your connection and try again.",
165
+ invalidInput: "Something you entered isn't valid. Check it and try again.",
166
+ server: "Oxy couldn't finish this right now. Try again in a moment.",
164
167
  generic: 'Something went wrong. Please try again.',
165
168
  },
166
169
  subtitleToApp: 'to continue to {{app}}',
@@ -223,6 +226,31 @@ const dict = {
223
226
  invalidDescription: 'It expired or was already used. Ask for a new email in the app, or type the code from the latest one.',
224
227
  },
225
228
  },
229
+ actionErrors: {
230
+ updateProfile: "Couldn't update your profile. Please try again.",
231
+ uploadAvatar: "Couldn't upload the picture. Please try again.",
232
+ updateSettings: "Couldn't save your settings. Please try again.",
233
+ updatePrivacy: "Couldn't save your privacy settings. Please try again.",
234
+ updateNotifications: "Couldn't save your notification preferences. Please try again.",
235
+ updatePreferences: "Couldn't save your preferences. Please try again.",
236
+ revokeConnectedApp: "Couldn't disconnect the app. Please try again.",
237
+ switchSession: "Couldn't switch accounts. Please try again.",
238
+ logout: "Couldn't sign out. Please try again.",
239
+ logoutAll: "Couldn't sign out everywhere. Please try again.",
240
+ updateDeviceName: "Couldn't rename the device. Please try again.",
241
+ removeDevice: "Couldn't remove the device. Please try again.",
242
+ },
243
+ usernamePolicy: {
244
+ hint: 'Letters, numbers, - and _. No spaces.',
245
+ required: 'Choose a username.',
246
+ tooShort: 'Use at least {{min}} characters.',
247
+ tooLong: 'Use at most {{max}} characters.',
248
+ noSpaces: "Usernames can't contain spaces. Use - or _ instead.",
249
+ characters: 'Use only letters, numbers, - and _. Start and end with a letter or number, and never put two of - or _ together.',
250
+ onlyNumbers: "A username can't be only numbers. Add a letter.",
251
+ reserved: 'That username is reserved. Choose another one.',
252
+ mustEndInBot: 'A bot account’s username must end in “bot”.',
253
+ },
226
254
  signup: {
227
255
  title: 'Create your account',
228
256
  subtitle: 'Choose your username. Then we confirm your email.',
@@ -231,7 +259,6 @@ const dict = {
231
259
  username: {
232
260
  label: 'Username',
233
261
  placeholder: 'yourname',
234
- required: 'Choose a username.',
235
262
  taken: 'That username is taken.',
236
263
  },
237
264
  email: {
@@ -279,7 +306,6 @@ const dict = {
279
306
  firstPlaceholder: 'Your first name',
280
307
  lastLabel: 'Last name',
281
308
  lastPlaceholder: 'Your last name',
282
- saveFailed: 'Could not save your name',
283
309
  },
284
310
  avatar: {
285
311
  title: 'Make It Yours',
@@ -602,8 +628,6 @@ const dict = {
602
628
  label: 'Username',
603
629
  placeholder: 'Choose a username',
604
630
  choose: 'Choose a username',
605
- required: 'Username is required',
606
- tooShort: 'Username must be at least 3 characters',
607
631
  },
608
632
  email: {
609
633
  title: 'Email',
@@ -1258,7 +1282,7 @@ const dict = {
1258
1282
  },
1259
1283
  rules: {
1260
1284
  title: 'Username rules',
1261
- content: 'Usernames can only contain lowercase letters (a-z) and numbers (0-9). They must be at least 4 characters long. Special characters, spaces, and uppercase letters are not allowed to keep usernames simple and easy to remember.',
1285
+ content: 'Usernames are 3 to 30 characters long and use letters, numbers, hyphens (-) and underscores (_). They start and end with a letter or number, never put two of - or _ together, and are not only numbers. Spaces are not allowed. Capital letters are kept as you type them, but Alice and alice are the same username.',
1262
1286
  },
1263
1287
  unique: {
1264
1288
  title: 'Why must it be unique?',
@@ -1837,9 +1861,6 @@ const dict = {
1837
1861
  label: 'Username',
1838
1862
  available: 'Username is available',
1839
1863
  taken: 'Username is taken',
1840
- tooShort: 'Username must be at least 3 characters',
1841
- invalidChars: 'Only letters, numbers, hyphens, and underscores',
1842
- mustEndInBot: 'A bot account’s username must end in “bot”',
1843
1864
  checkFailed: 'Could not check availability',
1844
1865
  },
1845
1866
  accountCategory: {
@@ -1874,16 +1895,16 @@ const dict = {
1874
1895
  transfer: 'Transfer ownership',
1875
1896
  },
1876
1897
  errors: {
1877
- identifierRequired: 'Enter a username or email to invite',
1898
+ emailRequired: 'Enter an email to invite.',
1899
+ emailInvalid: 'Enter a valid email address, like name@example.com.',
1878
1900
  missingAccount: 'No account selected.',
1879
1901
  noPermission: 'You do not have permission to view members.',
1880
- userNotFound: 'User not found',
1881
1902
  },
1882
1903
  invite: {
1883
- title: 'Add a member',
1884
- identifierLabel: 'Username or email',
1904
+ title: 'Invite a member',
1905
+ emailLabel: 'Email',
1885
1906
  roleLabel: 'Role',
1886
- submit: 'Add member',
1907
+ submit: 'Send invitation',
1887
1908
  },
1888
1909
  removeConfirm: {
1889
1910
  title: 'Remove member',
@@ -1894,8 +1915,9 @@ const dict = {
1894
1915
  description: 'Transfer ownership of this account to this member? You will be demoted to admin. This cannot be undone.',
1895
1916
  },
1896
1917
  toasts: {
1897
- added: 'Member added',
1898
- addFailed: 'Failed to add member',
1918
+ invited: 'Invitation sent',
1919
+ inviteFailed: "Couldn't send the invitation. Please try again.",
1920
+ invitationUpdateFailed: "Couldn't update the invitation. Please try again.",
1899
1921
  removed: 'Member removed',
1900
1922
  removeFailed: 'Failed to remove member',
1901
1923
  roleUpdated: 'Role updated',
@@ -1957,6 +1979,7 @@ const dict = {
1957
1979
  title: 'Delete Account',
1958
1980
  warning: 'This action cannot be undone. Your account and all associated data will be permanently deleted.',
1959
1981
  confirmLabel: 'Type "{{username}}" to confirm',
1982
+ confirmMismatch: 'That isn\'t your username. Type "{{username}}" exactly.',
1960
1983
  confirm: 'Delete Forever',
1961
1984
  error: 'Failed to delete account',
1962
1985
  handoff: {
@@ -2309,6 +2332,7 @@ const dict = {
2309
2332
  newLabel: 'New password',
2310
2333
  repeatLabel: 'Repeat the password',
2311
2334
  tooShort: 'Use at least {{min}} characters.',
2335
+ tooLong: 'Use at most {{max}} characters.',
2312
2336
  mismatch: "The passwords don't match.",
2313
2337
  signOutOthers: 'Sign out everywhere else',
2314
2338
  save: 'Save password',
@@ -2371,6 +2395,7 @@ const dict = {
2371
2395
  request: "Couldn't start the verification. Check the domain and try again.",
2372
2396
  notYet: 'Not verified yet. Changes to DNS can take a while to show up.',
2373
2397
  remove: "Couldn't remove the domain",
2398
+ invalid: 'Enter just the domain, like example.com — no https://, slashes or spaces.',
2374
2399
  },
2375
2400
  remove: {
2376
2401
  title: 'Remove domain',
@@ -161,6 +161,9 @@ const dict = {
161
161
  requestExpired: 'Este inicio de sesión ha caducado. Envía un email nuevo.',
162
162
  secondFactorInvalid: 'Ese código no es correcto. Prueba con el actual.',
163
163
  originNotAllowed: 'Aquí no se puede iniciar sesión.',
164
+ network: 'No se ha podido conectar con Oxy. Comprueba tu conexión e inténtalo de nuevo.',
165
+ invalidInput: 'Algo de lo que has escrito no es válido. Revísalo e inténtalo de nuevo.',
166
+ server: 'Oxy no ha podido terminar esto ahora. Inténtalo de nuevo en un momento.',
164
167
  generic: 'Algo ha ido mal. Inténtalo de nuevo.',
165
168
  },
166
169
  subtitleToApp: 'para continuar en {{app}}',
@@ -223,6 +226,31 @@ const dict = {
223
226
  invalidDescription: 'Ha caducado o ya se ha usado. Pide un email nuevo en la app o escribe el código del último.',
224
227
  },
225
228
  },
229
+ actionErrors: {
230
+ updateProfile: 'No se ha podido actualizar tu perfil. Inténtalo de nuevo.',
231
+ uploadAvatar: 'No se ha podido subir la foto. Inténtalo de nuevo.',
232
+ updateSettings: 'No se han podido guardar tus ajustes. Inténtalo de nuevo.',
233
+ updatePrivacy: 'No se han podido guardar tus ajustes de privacidad. Inténtalo de nuevo.',
234
+ updateNotifications: 'No se han podido guardar tus preferencias de notificaciones. Inténtalo de nuevo.',
235
+ updatePreferences: 'No se han podido guardar tus preferencias. Inténtalo de nuevo.',
236
+ revokeConnectedApp: 'No se ha podido desconectar la app. Inténtalo de nuevo.',
237
+ switchSession: 'No se ha podido cambiar de cuenta. Inténtalo de nuevo.',
238
+ logout: 'No se ha podido cerrar la sesión. Inténtalo de nuevo.',
239
+ logoutAll: 'No se ha podido cerrar la sesión en todas partes. Inténtalo de nuevo.',
240
+ updateDeviceName: 'No se ha podido cambiar el nombre del dispositivo. Inténtalo de nuevo.',
241
+ removeDevice: 'No se ha podido quitar el dispositivo. Inténtalo de nuevo.',
242
+ },
243
+ usernamePolicy: {
244
+ hint: 'Letras, números, - y _. Sin espacios.',
245
+ required: 'Elige un nombre de usuario.',
246
+ tooShort: 'Usa al menos {{min}} caracteres.',
247
+ tooLong: 'Usa como máximo {{max}} caracteres.',
248
+ noSpaces: 'El nombre de usuario no puede tener espacios. Usa - o _ en su lugar.',
249
+ characters: 'Usa solo letras, números, - y _. Empieza y termina con una letra o un número, y no pongas dos - o _ seguidos.',
250
+ onlyNumbers: 'El nombre de usuario no puede ser solo números. Añade una letra.',
251
+ reserved: 'Ese nombre de usuario está reservado. Elige otro.',
252
+ mustEndInBot: 'El nombre de usuario de una cuenta bot debe terminar en «bot».',
253
+ },
226
254
  signup: {
227
255
  title: 'Crea tu cuenta',
228
256
  subtitle: 'Elige tu nombre de usuario. Después confirmamos tu email.',
@@ -231,7 +259,6 @@ const dict = {
231
259
  username: {
232
260
  label: 'Nombre de usuario',
233
261
  placeholder: 'tunombre',
234
- required: 'Elige un nombre de usuario.',
235
262
  taken: 'Ese nombre de usuario ya está en uso.',
236
263
  },
237
264
  email: {
@@ -303,8 +330,6 @@ const dict = {
303
330
  label: 'Usuario',
304
331
  placeholder: 'Elige un usuario',
305
332
  choose: 'Elige un usuario',
306
- required: 'El usuario es obligatorio',
307
- tooShort: 'El usuario debe tener al menos 3 caracteres',
308
333
  },
309
334
  email: {
310
335
  title: 'Correo electrónico',
@@ -902,7 +927,6 @@ const dict = {
902
927
  firstPlaceholder: 'Tu nombre',
903
928
  lastLabel: 'Apellidos',
904
929
  lastPlaceholder: 'Tus apellidos',
905
- saveFailed: 'No se pudo guardar tu nombre',
906
930
  },
907
931
  avatar: {
908
932
  title: 'Hazlo tuyo',
@@ -1258,7 +1282,7 @@ const dict = {
1258
1282
  },
1259
1283
  rules: {
1260
1284
  title: 'Reglas del nombre de usuario',
1261
- content: 'Los nombres de usuario solo pueden contener letras minúsculas (a-z) y números (0-9). Deben tener al menos 4 caracteres. No se permiten caracteres especiales, espacios ni mayúsculas para mantener los nombres de usuario simples y fáciles de recordar.',
1285
+ content: 'Los nombres de usuario tienen entre 3 y 30 caracteres y usan letras, números, guiones (-) y guiones bajos (_). Empiezan y terminan con una letra o un número, nunca llevan dos - o _ seguidos y no son solo números. No se permiten espacios. Las mayúsculas se guardan tal como las escribes, pero Alicia y alicia son el mismo nombre de usuario.',
1262
1286
  },
1263
1287
  unique: {
1264
1288
  title: '¿Por qué debe ser único?',
@@ -1837,9 +1861,6 @@ const dict = {
1837
1861
  label: 'Nombre de usuario',
1838
1862
  available: 'El nombre de usuario está disponible',
1839
1863
  taken: 'El nombre de usuario ya está en uso',
1840
- tooShort: 'El nombre de usuario debe tener al menos 3 caracteres',
1841
- invalidChars: 'Solo letras, números, guiones y guiones bajos',
1842
- mustEndInBot: 'El nombre de usuario de una cuenta de bot debe terminar en “bot”',
1843
1864
  checkFailed: 'No se pudo comprobar la disponibilidad',
1844
1865
  },
1845
1866
  accountCategory: {
@@ -1874,16 +1895,16 @@ const dict = {
1874
1895
  transfer: 'Transferir propiedad',
1875
1896
  },
1876
1897
  errors: {
1877
- identifierRequired: 'Introduce un nombre de usuario o correo para invitar',
1898
+ emailRequired: 'Introduce un email para invitar.',
1899
+ emailInvalid: 'Introduce un email válido, como nombre@ejemplo.com.',
1878
1900
  missingAccount: 'Ninguna cuenta seleccionada.',
1879
1901
  noPermission: 'No tienes permiso para ver los miembros.',
1880
- userNotFound: 'Usuario no encontrado',
1881
1902
  },
1882
1903
  invite: {
1883
- title: 'Añadir un miembro',
1884
- identifierLabel: 'Nombre de usuario o correo',
1904
+ title: 'Invitar a un miembro',
1905
+ emailLabel: 'Email',
1885
1906
  roleLabel: 'Rol',
1886
- submit: 'Añadir miembro',
1907
+ submit: 'Enviar invitación',
1887
1908
  },
1888
1909
  removeConfirm: {
1889
1910
  title: 'Eliminar miembro',
@@ -1894,8 +1915,9 @@ const dict = {
1894
1915
  description: '¿Transferir la propiedad de esta cuenta a este miembro? Pasarás a ser administrador. Esto no se puede deshacer.',
1895
1916
  },
1896
1917
  toasts: {
1897
- added: 'Miembro añadido',
1898
- addFailed: 'No se pudo añadir el miembro',
1918
+ invited: 'Invitación enviada',
1919
+ inviteFailed: 'No se ha podido enviar la invitación. Inténtalo de nuevo.',
1920
+ invitationUpdateFailed: 'No se ha podido actualizar la invitación. Inténtalo de nuevo.',
1899
1921
  removed: 'Miembro eliminado',
1900
1922
  removeFailed: 'No se pudo eliminar el miembro',
1901
1923
  roleUpdated: 'Rol actualizado',
@@ -1957,6 +1979,7 @@ const dict = {
1957
1979
  title: 'Eliminar cuenta',
1958
1980
  warning: 'Esta acción no se puede deshacer. Tu cuenta y todos los datos asociados se eliminarán de forma permanente.',
1959
1981
  confirmLabel: 'Escribe "{{username}}" para confirmar',
1982
+ confirmMismatch: 'Ese no es tu nombre de usuario. Escribe "{{username}}" exactamente.',
1960
1983
  confirm: 'Eliminar para siempre',
1961
1984
  error: 'No se pudo eliminar la cuenta',
1962
1985
  handoff: {
@@ -2309,6 +2332,7 @@ const dict = {
2309
2332
  newLabel: 'Contraseña nueva',
2310
2333
  repeatLabel: 'Repite la contraseña',
2311
2334
  tooShort: 'Usa al menos {{min}} caracteres.',
2335
+ tooLong: 'Usa como máximo {{max}} caracteres.',
2312
2336
  mismatch: 'Las contraseñas no coinciden.',
2313
2337
  signOutOthers: 'Cerrar sesión en todos los demás sitios',
2314
2338
  save: 'Guardar contraseña',
@@ -2371,6 +2395,7 @@ const dict = {
2371
2395
  request: 'No se pudo empezar la verificación. Revisa el dominio e inténtalo otra vez.',
2372
2396
  notYet: 'Aún no está verificado. Los cambios de DNS pueden tardar en aparecer.',
2373
2397
  remove: 'No se pudo quitar el dominio',
2398
+ invalid: 'Escribe solo el dominio, como ejemplo.com, sin https://, barras ni espacios.',
2374
2399
  },
2375
2400
  remove: {
2376
2401
  title: 'Quitar dominio',
package/dist/esm/index.js CHANGED
@@ -30,6 +30,7 @@ import './crypto/polyfill.js';
30
30
  // ---------------------------------------------------------------------------
31
31
  export { OxyServices, OXY_API_URL, OXY_CLOUD_URL } from './OxyServices.js';
32
32
  export { OxyApiError, OxyAuthenticationError, OxyAuthenticationTimeoutError, AssetUrlResolutionError, ServiceAssetMetadataError, } from './OxyServices.errors.js';
33
+ export { sameHolderIdentity } from './api/holderEvidence.js';
33
34
  export { ASSET_MULTIPART_UPLOAD_MAX_BYTES, assetUrlCacheTTL, MAX_UPLOAD_BYTES } from './api/assets.js';
34
35
  export { DirectUploadInterruptedError } from './api/directUpload.js';
35
36
  export { buildUserDid } from './api/identity.js';
@@ -22,6 +22,18 @@ export type AccessTokenProvider = () => string | null;
22
22
  * — `null` to send it anonymous. See {@link HttpService.setAnonymousAuthProvider}.
23
23
  */
24
24
  export type AnonymousAuthProvider = () => Promise<string | null>;
25
+ declare const credentialGenerationBrand: unique symbol;
26
+ /**
27
+ * An opaque, immutable token for ONE installation of a bearer in a client.
28
+ *
29
+ * `TokenStore` swaps it on every write path (setter, refresh, account or actor
30
+ * switch, cross-tab or restored token, clear), so two installations of the same
31
+ * bytes (A, B, A) are different generations. It names a local sequence only: it
32
+ * is not evidence of an account, a scope or authorization.
33
+ */
34
+ export type CredentialGeneration = {
35
+ readonly [credentialGenerationBrand]: true;
36
+ };
25
37
  /** A trusted platform transport, for example Expo's streaming fetch. */
26
38
  export type ResponseTransport = (input: string, init: RequestInit) => Promise<Response>;
27
39
  /**
@@ -44,6 +56,13 @@ export interface ResponseRequest {
44
56
  /** Same response contract, but rejects locally when the SDK has no bearer. */
45
57
  export type AuthenticatedResponseRequest = ResponseRequest;
46
58
  export interface RequestOptions {
59
+ /**
60
+ * Bind a write to ONE installation of the source's bearer. The transport
61
+ * rejects (`AUTH_SESSION_CHANGED`) when the generation was replaced before
62
+ * dispatch, after queueing, or before the result is returned. Implies no cache,
63
+ * deduplication or retry, and a 401 neither refreshes nor clears the session.
64
+ */
65
+ expectedCredentialGeneration?: CredentialGeneration;
47
66
  cache?: boolean;
48
67
  cacheTTL?: number;
49
68
  deduplicate?: boolean;
@@ -228,6 +247,7 @@ export declare class HttpService {
228
247
  * bearer — and through the token listeners, the account — back.
229
248
  */
230
249
  private sessionEpoch;
250
+ private readonly credentialSource;
231
251
  /** Preserve a known device across legacy rotations which omit the claim. */
232
252
  private sessionDeviceId;
233
253
  /** Preserve known actor and context hints across rotations without those claims. */
@@ -258,7 +278,7 @@ export declare class HttpService {
258
278
  */
259
279
  private _tokenChangeListeners;
260
280
  private requestMetrics;
261
- constructor(config: OxyConfig);
281
+ constructor(config: OxyConfig, credentialSource?: HttpService | null);
262
282
  private syncAccessTokenFromProvider;
263
283
  /**
264
284
  * Robust FormData detection that works in browser, React Native, and
@@ -521,6 +541,10 @@ export declare class HttpService {
521
541
  * plants tokens itself (the device-secret arm) compares it across its awaits.
522
542
  */
523
543
  getSessionEpoch(): number;
544
+ /** Capture only the local generation; the caller must separately bind authority. */
545
+ captureCredentialGeneration(): CredentialGeneration | null;
546
+ isCredentialGenerationCurrent(generation: CredentialGeneration): boolean;
547
+ private assertCredentialGeneration;
524
548
  /** Whether {@link endSession} ran and no token has been planted since. */
525
549
  hasSessionEnded(): boolean;
526
550
  setAuthRefreshHandler(handler: AuthRefreshHandler | null): void;
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Holder-context evidence: the server's point-in-time observation of which
3
+ * shared-device context a bearer is installed in, bound to ONE installation of
4
+ * that bearer in this client (ADR 0053).
5
+ *
6
+ * Evidence is never authorization. It lets a caller that already captured a
7
+ * viewer intent prove, before a write is dispatched, that the bearer the write
8
+ * will carry belongs to that viewer's account and actor, and then pin the write
9
+ * to the exact installation with `expectedCredentialGeneration`. Anything that
10
+ * cannot be proven is `unavailable`, and the caller keeps its existing checks.
11
+ */
12
+ import type { HolderContextEvidenceV1Response } from '@oxy.so/contracts';
13
+ import type { CredentialGeneration } from '../HttpService';
14
+ /** The identifiers the server observed; none of them is a credential. */
15
+ export type HolderEvidenceIdentity = Readonly<Pick<HolderContextEvidenceV1Response, 'deviceId' | 'deviceSessionId' | 'contextId' | 'actorId' | 'accountId' | 'sessionId'>>;
16
+ export interface HolderEvidence {
17
+ /** The installation of the bearer this evidence was observed for. */
18
+ readonly generation: CredentialGeneration;
19
+ readonly identity: HolderEvidenceIdentity;
20
+ /** The device-session revision the server observed. */
21
+ readonly deviceRevision: number;
22
+ /** Epoch milliseconds: the earlier of the bearer and session expiry. */
23
+ readonly expiresAtMs: number;
24
+ }
25
+ /**
26
+ * Why no evidence exists. Every reason means "keep the existing checks": none of
27
+ * them says the session is revoked, and none may clear or refresh credentials.
28
+ *
29
+ * - `no_credential`: no bearer is installed, or it comes from a pull-only provider.
30
+ * - `no_holder`: this client holds no device credential to prove the holder with.
31
+ * - `changed`: the bearer was replaced while the evidence was being acquired.
32
+ * - `unsupported`: a delegated, OAuth or legacy bearer, or the endpoint is not offered.
33
+ * - `binding_unavailable`: the server could not match holder, context and bearer.
34
+ * - `unavailable`: the endpoint is off, rate limited, unreachable or answered wrongly.
35
+ */
36
+ export type HolderEvidenceUnavailableReason = 'no_credential' | 'no_holder' | 'changed' | 'unsupported' | 'binding_unavailable' | 'unavailable';
37
+ export type HolderEvidenceResult = {
38
+ readonly status: 'ok';
39
+ readonly evidence: HolderEvidence;
40
+ } | {
41
+ readonly status: 'unavailable';
42
+ readonly reason: HolderEvidenceUnavailableReason;
43
+ };
44
+ /** The one comparison that decides whether two observations name the same viewer context. */
45
+ export declare function sameHolderIdentity(a: HolderEvidenceIdentity, b: HolderEvidenceIdentity): boolean;
@@ -2,6 +2,7 @@ import type { AccountActorChain, DeviceProof } from '@oxy.so/contracts';
2
2
  import type { OxyContext } from '../client/context';
3
3
  import type { ClientSession } from '../models/session';
4
4
  import type { User } from '../models/interfaces';
5
+ import type { HolderEvidence, HolderEvidenceResult } from './holderEvidence';
5
6
  /**
6
7
  * Reads the device credential this client holds, if any — `{ deviceId,
7
8
  * deviceSecret }` from the provider's auth store. See
@@ -29,6 +30,8 @@ export declare class SessionApi {
29
30
  private deviceCredentialProvider;
30
31
  private decodedFor;
31
32
  private decoded;
33
+ /** One server round trip per installed bearer; the key is the opaque generation. */
34
+ private readonly evidence;
32
35
  constructor(ctx: OxyContext);
33
36
  /** The current access token, or `null` when signed out. */
34
37
  get accessToken(): string | null;
@@ -75,6 +78,24 @@ export declare class SessionApi {
75
78
  * that cannot be read only means the sign-in gets its own device.
76
79
  */
77
80
  readDeviceProof(): Promise<DeviceProof | null>;
81
+ /**
82
+ * Ask the server which shared-device context the INSTALLED bearer belongs to,
83
+ * and bind the answer to that exact installation.
84
+ *
85
+ * The result is honored only if the credential generation captured before
86
+ * the request is still current when the answer is committed, so a refresh,
87
+ * setter, account or actor switch, cross-tab change, restore or A to B to A
88
+ * during the request yields `changed`. The answer must also agree with the
89
+ * claims of the bearer that was captured; a disagreement is
90
+ * `binding_unavailable`. Nothing here refreshes, clears or mints a credential,
91
+ * and no outcome is a revocation verdict: callers keep their existing checks.
92
+ * Concurrent callers share one request per installation.
93
+ */
94
+ observeHolderEvidence(): Promise<HolderEvidenceResult>;
95
+ /** Whether evidence still names the installed bearer and has not expired. */
96
+ isHolderEvidenceCurrent(evidence: HolderEvidence): boolean;
97
+ private refreshEvidence;
98
+ private acquireEvidence;
78
99
  /** Ask the server whether the current access token is valid. */
79
100
  validateToken(): Promise<boolean>;
80
101
  /**
@@ -161,6 +161,9 @@ declare const dict: {
161
161
  requestExpired: string;
162
162
  secondFactorInvalid: string;
163
163
  originNotAllowed: string;
164
+ network: string;
165
+ invalidInput: string;
166
+ server: string;
164
167
  generic: string;
165
168
  };
166
169
  subtitleToApp: string;
@@ -223,6 +226,31 @@ declare const dict: {
223
226
  invalidDescription: string;
224
227
  };
225
228
  };
229
+ actionErrors: {
230
+ updateProfile: string;
231
+ uploadAvatar: string;
232
+ updateSettings: string;
233
+ updatePrivacy: string;
234
+ updateNotifications: string;
235
+ updatePreferences: string;
236
+ revokeConnectedApp: string;
237
+ switchSession: string;
238
+ logout: string;
239
+ logoutAll: string;
240
+ updateDeviceName: string;
241
+ removeDevice: string;
242
+ };
243
+ usernamePolicy: {
244
+ hint: string;
245
+ required: string;
246
+ tooShort: string;
247
+ tooLong: string;
248
+ noSpaces: string;
249
+ characters: string;
250
+ onlyNumbers: string;
251
+ reserved: string;
252
+ mustEndInBot: string;
253
+ };
226
254
  signup: {
227
255
  title: string;
228
256
  subtitle: string;
@@ -231,7 +259,6 @@ declare const dict: {
231
259
  username: {
232
260
  label: string;
233
261
  placeholder: string;
234
- required: string;
235
262
  taken: string;
236
263
  };
237
264
  email: {
@@ -270,7 +297,6 @@ declare const dict: {
270
297
  firstPlaceholder: string;
271
298
  lastLabel: string;
272
299
  lastPlaceholder: string;
273
- saveFailed: string;
274
300
  };
275
301
  avatar: {
276
302
  title: string;
@@ -593,8 +619,6 @@ declare const dict: {
593
619
  label: string;
594
620
  placeholder: string;
595
621
  choose: string;
596
- required: string;
597
- tooShort: string;
598
622
  };
599
623
  email: {
600
624
  title: string;
@@ -1828,9 +1852,6 @@ declare const dict: {
1828
1852
  label: string;
1829
1853
  available: string;
1830
1854
  taken: string;
1831
- tooShort: string;
1832
- invalidChars: string;
1833
- mustEndInBot: string;
1834
1855
  checkFailed: string;
1835
1856
  };
1836
1857
  accountCategory: {
@@ -1865,14 +1886,14 @@ declare const dict: {
1865
1886
  transfer: string;
1866
1887
  };
1867
1888
  errors: {
1868
- identifierRequired: string;
1889
+ emailRequired: string;
1890
+ emailInvalid: string;
1869
1891
  missingAccount: string;
1870
1892
  noPermission: string;
1871
- userNotFound: string;
1872
1893
  };
1873
1894
  invite: {
1874
1895
  title: string;
1875
- identifierLabel: string;
1896
+ emailLabel: string;
1876
1897
  roleLabel: string;
1877
1898
  submit: string;
1878
1899
  };
@@ -1885,8 +1906,9 @@ declare const dict: {
1885
1906
  description: string;
1886
1907
  };
1887
1908
  toasts: {
1888
- added: string;
1889
- addFailed: string;
1909
+ invited: string;
1910
+ inviteFailed: string;
1911
+ invitationUpdateFailed: string;
1890
1912
  removed: string;
1891
1913
  removeFailed: string;
1892
1914
  roleUpdated: string;
@@ -1948,6 +1970,7 @@ declare const dict: {
1948
1970
  title: string;
1949
1971
  warning: string;
1950
1972
  confirmLabel: string;
1973
+ confirmMismatch: string;
1951
1974
  confirm: string;
1952
1975
  error: string;
1953
1976
  handoff: {
@@ -2300,6 +2323,7 @@ declare const dict: {
2300
2323
  newLabel: string;
2301
2324
  repeatLabel: string;
2302
2325
  tooShort: string;
2326
+ tooLong: string;
2303
2327
  mismatch: string;
2304
2328
  signOutOthers: string;
2305
2329
  save: string;
@@ -2362,6 +2386,7 @@ declare const dict: {
2362
2386
  request: string;
2363
2387
  notYet: string;
2364
2388
  remove: string;
2389
+ invalid: string;
2365
2390
  };
2366
2391
  remove: {
2367
2392
  title: string;