@oxy.so/core 1.0.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 (673) hide show
  1. package/LICENSE +202 -0
  2. package/NOTICE +16 -0
  3. package/README.md +221 -0
  4. package/dist/cjs/.tsbuildinfo +1 -0
  5. package/dist/cjs/HttpService.js +1270 -0
  6. package/dist/cjs/OxyServices.base.js +390 -0
  7. package/dist/cjs/OxyServices.errors.js +95 -0
  8. package/dist/cjs/OxyServices.js +72 -0
  9. package/dist/cjs/boot/sessionColdBoot.js +395 -0
  10. package/dist/cjs/constants/version.js +16 -0
  11. package/dist/cjs/crypto/aead.js +79 -0
  12. package/dist/cjs/crypto/ecdh.js +35 -0
  13. package/dist/cjs/crypto/identityMarker.js +255 -0
  14. package/dist/cjs/crypto/kdf.js +39 -0
  15. package/dist/cjs/crypto/keyManager.js +2234 -0
  16. package/dist/cjs/crypto/polyfill.js +131 -0
  17. package/dist/cjs/crypto/recoveryPhrase.js +242 -0
  18. package/dist/cjs/crypto/signatureService.js +273 -0
  19. package/dist/cjs/i18n/accountCategoryLabels.js +44 -0
  20. package/dist/cjs/i18n/accountRoleLabels.js +27 -0
  21. package/dist/cjs/i18n/index.js +90 -0
  22. package/dist/cjs/i18n/locales/ar-SA.json +282 -0
  23. package/dist/cjs/i18n/locales/ca-ES.json +282 -0
  24. package/dist/cjs/i18n/locales/de-DE.json +282 -0
  25. package/dist/cjs/i18n/locales/en-US.json +2161 -0
  26. package/dist/cjs/i18n/locales/es-ES.json +2161 -0
  27. package/dist/cjs/i18n/locales/fr-FR.json +282 -0
  28. package/dist/cjs/i18n/locales/it-IT.json +282 -0
  29. package/dist/cjs/i18n/locales/ja-JP.json +282 -0
  30. package/dist/cjs/i18n/locales/ko-KR.json +282 -0
  31. package/dist/cjs/i18n/locales/locales/ar-SA.json +282 -0
  32. package/dist/cjs/i18n/locales/locales/ca-ES.json +282 -0
  33. package/dist/cjs/i18n/locales/locales/de-DE.json +282 -0
  34. package/dist/cjs/i18n/locales/locales/en-US.json +2161 -0
  35. package/dist/cjs/i18n/locales/locales/es-ES.json +2161 -0
  36. package/dist/cjs/i18n/locales/locales/fr-FR.json +282 -0
  37. package/dist/cjs/i18n/locales/locales/it-IT.json +282 -0
  38. package/dist/cjs/i18n/locales/locales/ja-JP.json +282 -0
  39. package/dist/cjs/i18n/locales/locales/ko-KR.json +282 -0
  40. package/dist/cjs/i18n/locales/locales/pt-PT.json +282 -0
  41. package/dist/cjs/i18n/locales/locales/zh-CN.json +282 -0
  42. package/dist/cjs/i18n/locales/pt-PT.json +282 -0
  43. package/dist/cjs/i18n/locales/zh-CN.json +282 -0
  44. package/dist/cjs/i18n/reputationCategoryLabels.js +20 -0
  45. package/dist/cjs/i18n/trustTierLabels.js +19 -0
  46. package/dist/cjs/index.js +498 -0
  47. package/dist/cjs/inference/OxyInferenceClient.js +544 -0
  48. package/dist/cjs/logger/index.js +149 -0
  49. package/dist/cjs/mixins/OxyServices.accounts.js +555 -0
  50. package/dist/cjs/mixins/OxyServices.analytics.js +49 -0
  51. package/dist/cjs/mixins/OxyServices.appData.js +108 -0
  52. package/dist/cjs/mixins/OxyServices.assets.js +764 -0
  53. package/dist/cjs/mixins/OxyServices.auth.js +1254 -0
  54. package/dist/cjs/mixins/OxyServices.chains.js +73 -0
  55. package/dist/cjs/mixins/OxyServices.civic.js +611 -0
  56. package/dist/cjs/mixins/OxyServices.connectedApps.js +78 -0
  57. package/dist/cjs/mixins/OxyServices.contacts.js +50 -0
  58. package/dist/cjs/mixins/OxyServices.deviceBoot.js +191 -0
  59. package/dist/cjs/mixins/OxyServices.deviceTransfer.js +314 -0
  60. package/dist/cjs/mixins/OxyServices.devices.js +116 -0
  61. package/dist/cjs/mixins/OxyServices.features.js +335 -0
  62. package/dist/cjs/mixins/OxyServices.followGraph.js +221 -0
  63. package/dist/cjs/mixins/OxyServices.identity.js +461 -0
  64. package/dist/cjs/mixins/OxyServices.identityBackup.js +172 -0
  65. package/dist/cjs/mixins/OxyServices.inference.js +59 -0
  66. package/dist/cjs/mixins/OxyServices.language.js +125 -0
  67. package/dist/cjs/mixins/OxyServices.links.js +72 -0
  68. package/dist/cjs/mixins/OxyServices.location.js +43 -0
  69. package/dist/cjs/mixins/OxyServices.nodes.js +175 -0
  70. package/dist/cjs/mixins/OxyServices.notifications.js +63 -0
  71. package/dist/cjs/mixins/OxyServices.payment.js +158 -0
  72. package/dist/cjs/mixins/OxyServices.privacy.js +199 -0
  73. package/dist/cjs/mixins/OxyServices.reputation.js +289 -0
  74. package/dist/cjs/mixins/OxyServices.security.js +89 -0
  75. package/dist/cjs/mixins/OxyServices.store.js +266 -0
  76. package/dist/cjs/mixins/OxyServices.topics.js +137 -0
  77. package/dist/cjs/mixins/OxyServices.user.js +902 -0
  78. package/dist/cjs/mixins/OxyServices.utility.js +1098 -0
  79. package/dist/cjs/mixins/index.js +135 -0
  80. package/dist/cjs/mixins/mixinHelpers.js +53 -0
  81. package/dist/cjs/models/Topic.js +16 -0
  82. package/dist/cjs/models/interfaces.js +20 -0
  83. package/dist/cjs/models/session.js +2 -0
  84. package/dist/cjs/server/auth.js +167 -0
  85. package/dist/cjs/server/capabilityTicket.js +192 -0
  86. package/dist/cjs/server/cors.js +225 -0
  87. package/dist/cjs/server/index.js +97 -0
  88. package/dist/cjs/server/rateLimit.js +207 -0
  89. package/dist/cjs/server/safeFetch.js +458 -0
  90. package/dist/cjs/server/securityHeaders.js +377 -0
  91. package/dist/cjs/server/userInvalidation.js +156 -0
  92. package/dist/cjs/server/verifySecret.js +50 -0
  93. package/dist/cjs/session/SessionClient.js +865 -0
  94. package/dist/cjs/session/accountDelegationAccess.js +48 -0
  95. package/dist/cjs/session/accountDialogController.js +1131 -0
  96. package/dist/cjs/session/accountSwitchTargets.js +81 -0
  97. package/dist/cjs/session/authStateStore.js +404 -0
  98. package/dist/cjs/session/createSessionClient.js +32 -0
  99. package/dist/cjs/session/deviceDirectory.js +143 -0
  100. package/dist/cjs/session/deviceSwitcherRows.js +76 -0
  101. package/dist/cjs/session/identityPin.js +256 -0
  102. package/dist/cjs/session/identitySession.js +135 -0
  103. package/dist/cjs/session/projectSessionState.js +121 -0
  104. package/dist/cjs/session/refresh.js +398 -0
  105. package/dist/cjs/session/sessionClientHost.js +34 -0
  106. package/dist/cjs/session/sharedDeviceCredential.js +247 -0
  107. package/dist/cjs/session/socketLoader.js +55 -0
  108. package/dist/cjs/shared/utils/colorUtils.js +153 -0
  109. package/dist/cjs/shared/utils/errorUtils.js +183 -0
  110. package/dist/cjs/shared/utils/index.js +49 -0
  111. package/dist/cjs/shared/utils/networkUtils.js +183 -0
  112. package/dist/cjs/shared/utils/themeUtils.js +106 -0
  113. package/dist/cjs/utils/accountCacheSweep.js +80 -0
  114. package/dist/cjs/utils/accountUtils.js +140 -0
  115. package/dist/cjs/utils/activityId.js +6 -0
  116. package/dist/cjs/utils/apiUtils.js +91 -0
  117. package/dist/cjs/utils/asyncUtils.js +222 -0
  118. package/dist/cjs/utils/authHelpers.js +120 -0
  119. package/dist/cjs/utils/authWebUrl.js +19 -0
  120. package/dist/cjs/utils/avatarUtils.js +29 -0
  121. package/dist/cjs/utils/cache.js +235 -0
  122. package/dist/cjs/utils/cacheKey.js +85 -0
  123. package/dist/cjs/utils/coldBoot.js +122 -0
  124. package/dist/cjs/utils/commonsApproval.js +31 -0
  125. package/dist/cjs/utils/commonsDelivery.js +59 -0
  126. package/dist/cjs/utils/deviceManager.js +174 -0
  127. package/dist/cjs/utils/displayNamePolicyRanges.generated.js +60 -0
  128. package/dist/cjs/utils/edgeRegion.js +6 -0
  129. package/dist/cjs/utils/errorUtils.js +243 -0
  130. package/dist/cjs/utils/identityCacheSweep.js +97 -0
  131. package/dist/cjs/utils/languageUtils.js +224 -0
  132. package/dist/cjs/utils/oauthPkce.js +311 -0
  133. package/dist/cjs/utils/officialOrigins.js +64 -0
  134. package/dist/cjs/utils/oxyServiceEnvironment.js +19 -0
  135. package/dist/cjs/utils/platform.js +119 -0
  136. package/dist/cjs/utils/profileLinks.js +63 -0
  137. package/dist/cjs/utils/redactUrl.js +29 -0
  138. package/dist/cjs/utils/registrableApex.js +45 -0
  139. package/dist/cjs/utils/requestUtils.js +209 -0
  140. package/dist/cjs/utils/sessionUtils.js +181 -0
  141. package/dist/cjs/utils/textNormalization.js +158 -0
  142. package/dist/cjs/utils/userHandle.js +37 -0
  143. package/dist/cjs/utils/userIdentity.js +50 -0
  144. package/dist/cjs/utils/validationUtils.js +370 -0
  145. package/dist/cjs/utils/webauthnOrigin.js +51 -0
  146. package/dist/esm/.tsbuildinfo +1 -0
  147. package/dist/esm/HttpService.js +1266 -0
  148. package/dist/esm/OxyServices.base.js +386 -0
  149. package/dist/esm/OxyServices.errors.js +88 -0
  150. package/dist/esm/OxyServices.js +66 -0
  151. package/dist/esm/boot/sessionColdBoot.js +392 -0
  152. package/dist/esm/constants/version.js +13 -0
  153. package/dist/esm/crypto/aead.js +74 -0
  154. package/dist/esm/crypto/ecdh.js +32 -0
  155. package/dist/esm/crypto/identityMarker.js +248 -0
  156. package/dist/esm/crypto/kdf.js +36 -0
  157. package/dist/esm/crypto/keyManager.js +2227 -0
  158. package/dist/esm/crypto/polyfill.js +129 -0
  159. package/dist/esm/crypto/recoveryPhrase.js +238 -0
  160. package/dist/esm/crypto/signatureService.js +269 -0
  161. package/dist/esm/i18n/accountCategoryLabels.js +37 -0
  162. package/dist/esm/i18n/accountRoleLabels.js +20 -0
  163. package/dist/esm/i18n/index.js +83 -0
  164. package/dist/esm/i18n/locales/ar-SA.json +282 -0
  165. package/dist/esm/i18n/locales/ca-ES.json +282 -0
  166. package/dist/esm/i18n/locales/de-DE.json +282 -0
  167. package/dist/esm/i18n/locales/en-US.json +2161 -0
  168. package/dist/esm/i18n/locales/es-ES.json +2161 -0
  169. package/dist/esm/i18n/locales/fr-FR.json +282 -0
  170. package/dist/esm/i18n/locales/it-IT.json +282 -0
  171. package/dist/esm/i18n/locales/ja-JP.json +282 -0
  172. package/dist/esm/i18n/locales/ko-KR.json +282 -0
  173. package/dist/esm/i18n/locales/locales/ar-SA.json +282 -0
  174. package/dist/esm/i18n/locales/locales/ca-ES.json +282 -0
  175. package/dist/esm/i18n/locales/locales/de-DE.json +282 -0
  176. package/dist/esm/i18n/locales/locales/en-US.json +2161 -0
  177. package/dist/esm/i18n/locales/locales/es-ES.json +2161 -0
  178. package/dist/esm/i18n/locales/locales/fr-FR.json +282 -0
  179. package/dist/esm/i18n/locales/locales/it-IT.json +282 -0
  180. package/dist/esm/i18n/locales/locales/ja-JP.json +282 -0
  181. package/dist/esm/i18n/locales/locales/ko-KR.json +282 -0
  182. package/dist/esm/i18n/locales/locales/pt-PT.json +282 -0
  183. package/dist/esm/i18n/locales/locales/zh-CN.json +282 -0
  184. package/dist/esm/i18n/locales/pt-PT.json +282 -0
  185. package/dist/esm/i18n/locales/zh-CN.json +282 -0
  186. package/dist/esm/i18n/reputationCategoryLabels.js +13 -0
  187. package/dist/esm/i18n/trustTierLabels.js +12 -0
  188. package/dist/esm/index.js +254 -0
  189. package/dist/esm/inference/OxyInferenceClient.js +538 -0
  190. package/dist/esm/logger/index.js +140 -0
  191. package/dist/esm/mixins/OxyServices.accounts.js +543 -0
  192. package/dist/esm/mixins/OxyServices.analytics.js +46 -0
  193. package/dist/esm/mixins/OxyServices.appData.js +103 -0
  194. package/dist/esm/mixins/OxyServices.assets.js +761 -0
  195. package/dist/esm/mixins/OxyServices.auth.js +1247 -0
  196. package/dist/esm/mixins/OxyServices.chains.js +70 -0
  197. package/dist/esm/mixins/OxyServices.civic.js +605 -0
  198. package/dist/esm/mixins/OxyServices.connectedApps.js +75 -0
  199. package/dist/esm/mixins/OxyServices.contacts.js +47 -0
  200. package/dist/esm/mixins/OxyServices.deviceBoot.js +186 -0
  201. package/dist/esm/mixins/OxyServices.deviceTransfer.js +311 -0
  202. package/dist/esm/mixins/OxyServices.devices.js +113 -0
  203. package/dist/esm/mixins/OxyServices.features.js +332 -0
  204. package/dist/esm/mixins/OxyServices.followGraph.js +218 -0
  205. package/dist/esm/mixins/OxyServices.identity.js +457 -0
  206. package/dist/esm/mixins/OxyServices.identityBackup.js +169 -0
  207. package/dist/esm/mixins/OxyServices.inference.js +56 -0
  208. package/dist/esm/mixins/OxyServices.language.js +122 -0
  209. package/dist/esm/mixins/OxyServices.links.js +69 -0
  210. package/dist/esm/mixins/OxyServices.location.js +40 -0
  211. package/dist/esm/mixins/OxyServices.nodes.js +172 -0
  212. package/dist/esm/mixins/OxyServices.notifications.js +60 -0
  213. package/dist/esm/mixins/OxyServices.payment.js +155 -0
  214. package/dist/esm/mixins/OxyServices.privacy.js +196 -0
  215. package/dist/esm/mixins/OxyServices.reputation.js +286 -0
  216. package/dist/esm/mixins/OxyServices.security.js +86 -0
  217. package/dist/esm/mixins/OxyServices.store.js +263 -0
  218. package/dist/esm/mixins/OxyServices.topics.js +134 -0
  219. package/dist/esm/mixins/OxyServices.user.js +899 -0
  220. package/dist/esm/mixins/OxyServices.utility.js +1095 -0
  221. package/dist/esm/mixins/index.js +132 -0
  222. package/dist/esm/mixins/mixinHelpers.js +48 -0
  223. package/dist/esm/models/Topic.js +13 -0
  224. package/dist/esm/models/interfaces.js +17 -0
  225. package/dist/esm/models/session.js +1 -0
  226. package/dist/esm/server/auth.js +154 -0
  227. package/dist/esm/server/capabilityTicket.js +183 -0
  228. package/dist/esm/server/cors.js +220 -0
  229. package/dist/esm/server/index.js +47 -0
  230. package/dist/esm/server/rateLimit.js +201 -0
  231. package/dist/esm/server/safeFetch.js +447 -0
  232. package/dist/esm/server/securityHeaders.js +364 -0
  233. package/dist/esm/server/userInvalidation.js +152 -0
  234. package/dist/esm/server/verifySecret.js +47 -0
  235. package/dist/esm/session/SessionClient.js +861 -0
  236. package/dist/esm/session/accountDelegationAccess.js +45 -0
  237. package/dist/esm/session/accountDialogController.js +1126 -0
  238. package/dist/esm/session/accountSwitchTargets.js +77 -0
  239. package/dist/esm/session/authStateStore.js +398 -0
  240. package/dist/esm/session/createSessionClient.js +29 -0
  241. package/dist/esm/session/deviceDirectory.js +135 -0
  242. package/dist/esm/session/deviceSwitcherRows.js +72 -0
  243. package/dist/esm/session/identityPin.js +249 -0
  244. package/dist/esm/session/identitySession.js +131 -0
  245. package/dist/esm/session/projectSessionState.js +114 -0
  246. package/dist/esm/session/refresh.js +390 -0
  247. package/dist/esm/session/sessionClientHost.js +31 -0
  248. package/dist/esm/session/sharedDeviceCredential.js +239 -0
  249. package/dist/esm/session/socketLoader.js +19 -0
  250. package/dist/esm/shared/utils/colorUtils.js +143 -0
  251. package/dist/esm/shared/utils/errorUtils.js +170 -0
  252. package/dist/esm/shared/utils/index.js +15 -0
  253. package/dist/esm/shared/utils/networkUtils.js +173 -0
  254. package/dist/esm/shared/utils/themeUtils.js +98 -0
  255. package/dist/esm/utils/accountCacheSweep.js +75 -0
  256. package/dist/esm/utils/accountUtils.js +131 -0
  257. package/dist/esm/utils/activityId.js +2 -0
  258. package/dist/esm/utils/apiUtils.js +84 -0
  259. package/dist/esm/utils/asyncUtils.js +207 -0
  260. package/dist/esm/utils/authHelpers.js +111 -0
  261. package/dist/esm/utils/authWebUrl.js +16 -0
  262. package/dist/esm/utils/avatarUtils.js +26 -0
  263. package/dist/esm/utils/cache.js +227 -0
  264. package/dist/esm/utils/cacheKey.js +80 -0
  265. package/dist/esm/utils/coldBoot.js +119 -0
  266. package/dist/esm/utils/commonsApproval.js +27 -0
  267. package/dist/esm/utils/commonsDelivery.js +54 -0
  268. package/dist/esm/utils/deviceManager.js +170 -0
  269. package/dist/esm/utils/displayNamePolicyRanges.generated.js +57 -0
  270. package/dist/esm/utils/edgeRegion.js +2 -0
  271. package/dist/esm/utils/errorUtils.js +232 -0
  272. package/dist/esm/utils/identityCacheSweep.js +92 -0
  273. package/dist/esm/utils/languageUtils.js +212 -0
  274. package/dist/esm/utils/oauthPkce.js +297 -0
  275. package/dist/esm/utils/officialOrigins.js +59 -0
  276. package/dist/esm/utils/oxyServiceEnvironment.js +16 -0
  277. package/dist/esm/utils/platform.js +110 -0
  278. package/dist/esm/utils/profileLinks.js +60 -0
  279. package/dist/esm/utils/redactUrl.js +26 -0
  280. package/dist/esm/utils/registrableApex.js +42 -0
  281. package/dist/esm/utils/requestUtils.js +203 -0
  282. package/dist/esm/utils/sessionUtils.js +171 -0
  283. package/dist/esm/utils/textNormalization.js +154 -0
  284. package/dist/esm/utils/userHandle.js +33 -0
  285. package/dist/esm/utils/userIdentity.js +45 -0
  286. package/dist/esm/utils/validationUtils.js +350 -0
  287. package/dist/esm/utils/webauthnOrigin.js +48 -0
  288. package/dist/types/.tsbuildinfo +1 -0
  289. package/dist/types/HttpService.d.ts +413 -0
  290. package/dist/types/OxyServices.base.d.ts +187 -0
  291. package/dist/types/OxyServices.d.ts +139 -0
  292. package/dist/types/OxyServices.errors.d.ts +84 -0
  293. package/dist/types/boot/sessionColdBoot.d.ts +140 -0
  294. package/dist/types/constants/version.d.ts +13 -0
  295. package/dist/types/crypto/aead.d.ts +56 -0
  296. package/dist/types/crypto/ecdh.d.ts +29 -0
  297. package/dist/types/crypto/identityMarker.d.ts +94 -0
  298. package/dist/types/crypto/kdf.d.ts +25 -0
  299. package/dist/types/crypto/keyManager.d.ts +582 -0
  300. package/dist/types/crypto/polyfill.d.ts +25 -0
  301. package/dist/types/crypto/recoveryPhrase.d.ts +173 -0
  302. package/dist/types/crypto/signatureService.d.ts +136 -0
  303. package/dist/types/i18n/accountCategoryLabels.d.ts +34 -0
  304. package/dist/types/i18n/accountRoleLabels.d.ts +10 -0
  305. package/dist/types/i18n/index.d.ts +3 -0
  306. package/dist/types/i18n/reputationCategoryLabels.d.ts +10 -0
  307. package/dist/types/i18n/trustTierLabels.d.ts +9 -0
  308. package/dist/types/index.d.ts +145 -0
  309. package/dist/types/inference/OxyInferenceClient.d.ts +342 -0
  310. package/dist/types/logger/index.d.ts +104 -0
  311. package/dist/types/mixins/OxyServices.accounts.d.ts +868 -0
  312. package/dist/types/mixins/OxyServices.analytics.d.ts +72 -0
  313. package/dist/types/mixins/OxyServices.appData.d.ts +108 -0
  314. package/dist/types/mixins/OxyServices.assets.d.ts +329 -0
  315. package/dist/types/mixins/OxyServices.auth.d.ts +908 -0
  316. package/dist/types/mixins/OxyServices.chains.d.ts +156 -0
  317. package/dist/types/mixins/OxyServices.civic.d.ts +510 -0
  318. package/dist/types/mixins/OxyServices.connectedApps.d.ts +169 -0
  319. package/dist/types/mixins/OxyServices.contacts.d.ts +100 -0
  320. package/dist/types/mixins/OxyServices.deviceBoot.d.ts +185 -0
  321. package/dist/types/mixins/OxyServices.deviceTransfer.d.ts +149 -0
  322. package/dist/types/mixins/OxyServices.devices.d.ts +103 -0
  323. package/dist/types/mixins/OxyServices.features.d.ts +250 -0
  324. package/dist/types/mixins/OxyServices.followGraph.d.ts +223 -0
  325. package/dist/types/mixins/OxyServices.identity.d.ts +351 -0
  326. package/dist/types/mixins/OxyServices.identityBackup.d.ts +129 -0
  327. package/dist/types/mixins/OxyServices.inference.d.ts +95 -0
  328. package/dist/types/mixins/OxyServices.language.d.ts +87 -0
  329. package/dist/types/mixins/OxyServices.links.d.ts +104 -0
  330. package/dist/types/mixins/OxyServices.location.d.ts +70 -0
  331. package/dist/types/mixins/OxyServices.nodes.d.ts +198 -0
  332. package/dist/types/mixins/OxyServices.notifications.d.ts +143 -0
  333. package/dist/types/mixins/OxyServices.payment.d.ts +117 -0
  334. package/dist/types/mixins/OxyServices.privacy.d.ts +146 -0
  335. package/dist/types/mixins/OxyServices.reputation.d.ts +202 -0
  336. package/dist/types/mixins/OxyServices.security.d.ts +84 -0
  337. package/dist/types/mixins/OxyServices.store.d.ts +334 -0
  338. package/dist/types/mixins/OxyServices.topics.d.ts +110 -0
  339. package/dist/types/mixins/OxyServices.user.d.ts +558 -0
  340. package/dist/types/mixins/OxyServices.utility.d.ts +393 -0
  341. package/dist/types/mixins/index.d.ts +87 -0
  342. package/dist/types/mixins/mixinHelpers.d.ts +31 -0
  343. package/dist/types/models/Topic.d.ts +42 -0
  344. package/dist/types/models/interfaces.d.ts +675 -0
  345. package/dist/types/models/session.d.ts +51 -0
  346. package/dist/types/server/auth.d.ts +130 -0
  347. package/dist/types/server/capabilityTicket.d.ts +41 -0
  348. package/dist/types/server/cors.d.ts +98 -0
  349. package/dist/types/server/index.d.ts +35 -0
  350. package/dist/types/server/rateLimit.d.ts +40 -0
  351. package/dist/types/server/safeFetch.d.ts +137 -0
  352. package/dist/types/server/securityHeaders.d.ts +221 -0
  353. package/dist/types/server/userInvalidation.d.ts +114 -0
  354. package/dist/types/server/verifySecret.d.ts +29 -0
  355. package/dist/types/session/SessionClient.d.ts +418 -0
  356. package/dist/types/session/accountDelegationAccess.d.ts +40 -0
  357. package/dist/types/session/accountDialogController.d.ts +648 -0
  358. package/dist/types/session/accountSwitchTargets.d.ts +70 -0
  359. package/dist/types/session/authStateStore.d.ts +153 -0
  360. package/dist/types/session/createSessionClient.d.ts +30 -0
  361. package/dist/types/session/deviceDirectory.d.ts +182 -0
  362. package/dist/types/session/deviceSwitcherRows.d.ts +92 -0
  363. package/dist/types/session/identityPin.d.ts +87 -0
  364. package/dist/types/session/identitySession.d.ts +105 -0
  365. package/dist/types/session/projectSessionState.d.ts +74 -0
  366. package/dist/types/session/refresh.d.ts +179 -0
  367. package/dist/types/session/sessionClientHost.d.ts +18 -0
  368. package/dist/types/session/sharedDeviceCredential.d.ts +202 -0
  369. package/dist/types/session/socketLoader.d.ts +11 -0
  370. package/dist/types/shared/utils/colorUtils.d.ts +104 -0
  371. package/dist/types/shared/utils/errorUtils.d.ts +97 -0
  372. package/dist/types/shared/utils/index.d.ts +13 -0
  373. package/dist/types/shared/utils/networkUtils.d.ts +139 -0
  374. package/dist/types/shared/utils/themeUtils.d.ts +90 -0
  375. package/dist/types/utils/accountCacheSweep.d.ts +75 -0
  376. package/dist/types/utils/accountUtils.d.ts +100 -0
  377. package/dist/types/utils/activityId.d.ts +2 -0
  378. package/dist/types/utils/apiUtils.d.ts +95 -0
  379. package/dist/types/utils/asyncUtils.d.ts +62 -0
  380. package/dist/types/utils/authHelpers.d.ts +58 -0
  381. package/dist/types/utils/authWebUrl.d.ts +16 -0
  382. package/dist/types/utils/avatarUtils.d.ts +16 -0
  383. package/dist/types/utils/cache.d.ts +130 -0
  384. package/dist/types/utils/cacheKey.d.ts +66 -0
  385. package/dist/types/utils/coldBoot.d.ts +131 -0
  386. package/dist/types/utils/commonsApproval.d.ts +13 -0
  387. package/dist/types/utils/commonsDelivery.d.ts +105 -0
  388. package/dist/types/utils/deviceManager.d.ts +65 -0
  389. package/dist/types/utils/displayNamePolicyRanges.generated.d.ts +57 -0
  390. package/dist/types/utils/edgeRegion.d.ts +2 -0
  391. package/dist/types/utils/errorUtils.d.ts +125 -0
  392. package/dist/types/utils/identityCacheSweep.d.ts +80 -0
  393. package/dist/types/utils/languageUtils.d.ts +100 -0
  394. package/dist/types/utils/oauthPkce.d.ts +157 -0
  395. package/dist/types/utils/officialOrigins.d.ts +13 -0
  396. package/dist/types/utils/oxyServiceEnvironment.d.ts +17 -0
  397. package/dist/types/utils/platform.d.ts +49 -0
  398. package/dist/types/utils/profileLinks.d.ts +44 -0
  399. package/dist/types/utils/redactUrl.d.ts +17 -0
  400. package/dist/types/utils/registrableApex.d.ts +27 -0
  401. package/dist/types/utils/requestUtils.d.ts +123 -0
  402. package/dist/types/utils/sessionUtils.d.ts +54 -0
  403. package/dist/types/utils/textNormalization.d.ts +103 -0
  404. package/dist/types/utils/userHandle.d.ts +26 -0
  405. package/dist/types/utils/userIdentity.d.ts +21 -0
  406. package/dist/types/utils/validationUtils.d.ts +268 -0
  407. package/dist/types/utils/webauthnOrigin.d.ts +32 -0
  408. package/package.json +168 -0
  409. package/src/HttpService.ts +1553 -0
  410. package/src/OxyServices.base.ts +478 -0
  411. package/src/OxyServices.errors.ts +122 -0
  412. package/src/OxyServices.ts +172 -0
  413. package/src/__tests__/authSocket.test.ts +96 -0
  414. package/src/__tests__/httpServiceActivityId.test.ts +49 -0
  415. package/src/__tests__/httpServiceAuthSelfAwait.test.ts +108 -0
  416. package/src/__tests__/httpServiceCache.test.ts +318 -0
  417. package/src/__tests__/httpServiceCsrf.test.ts +235 -0
  418. package/src/__tests__/httpServiceFormEncoded.test.ts +142 -0
  419. package/src/__tests__/httpServiceQueueDeadlock.test.ts +128 -0
  420. package/src/__tests__/httpServiceUnwrapEnvelope.test.ts +115 -0
  421. package/src/__tests__/inSessionRefresh.test.ts +260 -0
  422. package/src/__tests__/linkedClient.test.ts +370 -0
  423. package/src/__tests__/parseHttpErrorBody.test.ts +116 -0
  424. package/src/__tests__/serverValueImportsDeclared.test.ts +120 -0
  425. package/src/__tests__/userIdentity.test.ts +143 -0
  426. package/src/boot/__tests__/sessionColdBoot.identity.test.ts +393 -0
  427. package/src/boot/__tests__/sessionColdBoot.sharedDevice.test.ts +325 -0
  428. package/src/boot/__tests__/sessionColdBoot.test.ts +570 -0
  429. package/src/boot/sessionColdBoot.ts +529 -0
  430. package/src/constants/version.ts +15 -0
  431. package/src/crypto/__tests__/backupMaterial.test.ts +86 -0
  432. package/src/crypto/__tests__/cryptoPrimitives.test.ts +226 -0
  433. package/src/crypto/__tests__/identityMocks.ts +125 -0
  434. package/src/crypto/__tests__/keyManager.atomicity.test.ts +272 -0
  435. package/src/crypto/__tests__/keyManager.cacheSafety.test.ts +175 -0
  436. package/src/crypto/__tests__/keyManager.identityStatus.test.ts +217 -0
  437. package/src/crypto/__tests__/keyManager.recoveryLadder.test.ts +179 -0
  438. package/src/crypto/__tests__/keyManager.recoveryMnemonic.test.ts +138 -0
  439. package/src/crypto/__tests__/keyManager.storageMigration.test.ts +227 -0
  440. package/src/crypto/__tests__/keyManager.test.ts +329 -0
  441. package/src/crypto/__tests__/polyfill.test.ts +109 -0
  442. package/src/crypto/__tests__/recoveryPhrase.test.ts +61 -0
  443. package/src/crypto/__tests__/scopedSeed.test.ts +126 -0
  444. package/src/crypto/__tests__/signChallengeShared.test.ts +63 -0
  445. package/src/crypto/__tests__/signedRecord.test.ts +230 -0
  446. package/src/crypto/aead.ts +97 -0
  447. package/src/crypto/ecdh.ts +37 -0
  448. package/src/crypto/identityMarker.ts +291 -0
  449. package/src/crypto/kdf.ts +43 -0
  450. package/src/crypto/keyManager.ts +2607 -0
  451. package/src/crypto/polyfill.ts +143 -0
  452. package/src/crypto/recoveryPhrase.ts +337 -0
  453. package/src/crypto/signatureService.ts +350 -0
  454. package/src/i18n/__tests__/accountCategoryLabels.test.ts +62 -0
  455. package/src/i18n/__tests__/accountRoleLabels.test.ts +54 -0
  456. package/src/i18n/__tests__/reputationCategoryLabels.test.ts +56 -0
  457. package/src/i18n/__tests__/trustTierLabels.test.ts +47 -0
  458. package/src/i18n/accountCategoryLabels.ts +44 -0
  459. package/src/i18n/accountRoleLabels.ts +26 -0
  460. package/src/i18n/index.ts +88 -0
  461. package/src/i18n/locales/ar-SA.json +282 -0
  462. package/src/i18n/locales/ca-ES.json +282 -0
  463. package/src/i18n/locales/de-DE.json +282 -0
  464. package/src/i18n/locales/en-US.json +2161 -0
  465. package/src/i18n/locales/es-ES.json +2161 -0
  466. package/src/i18n/locales/fr-FR.json +282 -0
  467. package/src/i18n/locales/it-IT.json +282 -0
  468. package/src/i18n/locales/ja-JP.json +282 -0
  469. package/src/i18n/locales/ko-KR.json +282 -0
  470. package/src/i18n/locales/pt-PT.json +282 -0
  471. package/src/i18n/locales/zh-CN.json +282 -0
  472. package/src/i18n/reputationCategoryLabels.ts +20 -0
  473. package/src/i18n/trustTierLabels.ts +18 -0
  474. package/src/index.ts +881 -0
  475. package/src/inference/OxyInferenceClient.ts +888 -0
  476. package/src/inference/__tests__/OxyInferenceClient.test.ts +834 -0
  477. package/src/logger/__tests__/logger.test.ts +207 -0
  478. package/src/logger/index.ts +217 -0
  479. package/src/mixins/OxyServices.accounts.ts +1330 -0
  480. package/src/mixins/OxyServices.analytics.ts +53 -0
  481. package/src/mixins/OxyServices.appData.ts +158 -0
  482. package/src/mixins/OxyServices.assets.ts +861 -0
  483. package/src/mixins/OxyServices.auth.ts +1858 -0
  484. package/src/mixins/OxyServices.chains.ts +134 -0
  485. package/src/mixins/OxyServices.civic.ts +945 -0
  486. package/src/mixins/OxyServices.connectedApps.ts +174 -0
  487. package/src/mixins/OxyServices.contacts.ts +73 -0
  488. package/src/mixins/OxyServices.deviceBoot.ts +210 -0
  489. package/src/mixins/OxyServices.deviceTransfer.ts +393 -0
  490. package/src/mixins/OxyServices.devices.ts +120 -0
  491. package/src/mixins/OxyServices.features.ts +454 -0
  492. package/src/mixins/OxyServices.followGraph.ts +293 -0
  493. package/src/mixins/OxyServices.identity.ts +705 -0
  494. package/src/mixins/OxyServices.identityBackup.ts +250 -0
  495. package/src/mixins/OxyServices.inference.ts +57 -0
  496. package/src/mixins/OxyServices.language.ts +144 -0
  497. package/src/mixins/OxyServices.links.ts +107 -0
  498. package/src/mixins/OxyServices.location.ts +46 -0
  499. package/src/mixins/OxyServices.nodes.ts +337 -0
  500. package/src/mixins/OxyServices.notifications.ts +142 -0
  501. package/src/mixins/OxyServices.payment.ts +163 -0
  502. package/src/mixins/OxyServices.privacy.ts +219 -0
  503. package/src/mixins/OxyServices.reputation.ts +436 -0
  504. package/src/mixins/OxyServices.security.ts +110 -0
  505. package/src/mixins/OxyServices.store.ts +585 -0
  506. package/src/mixins/OxyServices.topics.ts +169 -0
  507. package/src/mixins/OxyServices.user.ts +1137 -0
  508. package/src/mixins/OxyServices.utility.ts +1370 -0
  509. package/src/mixins/__tests__/OxyServices.civic.test.ts +1095 -0
  510. package/src/mixins/__tests__/OxyServices.deviceBoot.test.ts +124 -0
  511. package/src/mixins/__tests__/OxyServices.deviceTransfer.test.ts +271 -0
  512. package/src/mixins/__tests__/OxyServices.identity.test.ts +376 -0
  513. package/src/mixins/__tests__/OxyServices.links.test.ts +154 -0
  514. package/src/mixins/__tests__/OxyServices.nodes.test.ts +341 -0
  515. package/src/mixins/__tests__/OxyServices.rotateKey.test.ts +276 -0
  516. package/src/mixins/__tests__/OxyServices.serviceAssetMetadata.test.ts +192 -0
  517. package/src/mixins/__tests__/OxyServices.serviceAssetMetadataBySha.test.ts +135 -0
  518. package/src/mixins/__tests__/accounts.test.ts +727 -0
  519. package/src/mixins/__tests__/appData.test.ts +203 -0
  520. package/src/mixins/__tests__/assetCredentials.test.ts +47 -0
  521. package/src/mixins/__tests__/assetUpload.test.ts +191 -0
  522. package/src/mixins/__tests__/backgroundCredential.httpIntegration.test.ts +135 -0
  523. package/src/mixins/__tests__/chains.test.ts +113 -0
  524. package/src/mixins/__tests__/commonsSignIn.test.ts +1144 -0
  525. package/src/mixins/__tests__/connectedApps.test.ts +123 -0
  526. package/src/mixins/__tests__/deviceTokenMintPinned.test.ts +113 -0
  527. package/src/mixins/__tests__/discoveryErrorHandling.test.ts +266 -0
  528. package/src/mixins/__tests__/followCacheInvalidation.test.ts +173 -0
  529. package/src/mixins/__tests__/followGraph.test.ts +147 -0
  530. package/src/mixins/__tests__/followGraphPagination.test.ts +271 -0
  531. package/src/mixins/__tests__/getFileDownloadUrl.test.ts +346 -0
  532. package/src/mixins/__tests__/getFollowStatuses.test.ts +95 -0
  533. package/src/mixins/__tests__/getUsersByIds.test.ts +149 -0
  534. package/src/mixins/__tests__/identityBackup.test.ts +315 -0
  535. package/src/mixins/__tests__/identityWriteCacheInvalidation.test.ts +407 -0
  536. package/src/mixins/__tests__/inferenceFactory.test.ts +58 -0
  537. package/src/mixins/__tests__/onTokensChanged.test.ts +129 -0
  538. package/src/mixins/__tests__/preSessionSkipAuth.test.ts +284 -0
  539. package/src/mixins/__tests__/privacyCacheInvalidation.test.ts +155 -0
  540. package/src/mixins/__tests__/reputation.test.ts +522 -0
  541. package/src/mixins/__tests__/serviceAuth.test.ts +832 -0
  542. package/src/mixins/__tests__/serviceTokenJwks.test.ts +208 -0
  543. package/src/mixins/__tests__/store.test.ts +304 -0
  544. package/src/mixins/__tests__/topics.test.ts +156 -0
  545. package/src/mixins/__tests__/userReadCacheBypass.test.ts +129 -0
  546. package/src/mixins/__tests__/userTokenAuth.test.ts +746 -0
  547. package/src/mixins/__tests__/verifyChallenge.test.ts +130 -0
  548. package/src/mixins/__tests__/verifyServiceActingAs.test.ts +167 -0
  549. package/src/mixins/__tests__/webauthnAuth.test.ts +205 -0
  550. package/src/mixins/index.ts +201 -0
  551. package/src/mixins/mixinHelpers.ts +69 -0
  552. package/src/models/Topic.ts +46 -0
  553. package/src/models/interfaces.ts +786 -0
  554. package/src/models/session.ts +55 -0
  555. package/src/server/__tests__/auth.test.ts +78 -0
  556. package/src/server/__tests__/capabilityTicket.test.ts +111 -0
  557. package/src/server/__tests__/cors.socket.test.ts +225 -0
  558. package/src/server/__tests__/cors.test.ts +158 -0
  559. package/src/server/__tests__/rateLimit.test.ts +257 -0
  560. package/src/server/__tests__/safeFetch.test.ts +232 -0
  561. package/src/server/__tests__/securityHeaders.test.ts +348 -0
  562. package/src/server/__tests__/serviceTokenAttribution.test.ts +396 -0
  563. package/src/server/__tests__/userInvalidation.test.ts +203 -0
  564. package/src/server/__tests__/verifySecret.test.ts +40 -0
  565. package/src/server/auth.ts +271 -0
  566. package/src/server/capabilityTicket.ts +258 -0
  567. package/src/server/cors.ts +277 -0
  568. package/src/server/index.ts +136 -0
  569. package/src/server/rateLimit.ts +313 -0
  570. package/src/server/safeFetch.ts +586 -0
  571. package/src/server/securityHeaders.ts +467 -0
  572. package/src/server/userInvalidation.ts +199 -0
  573. package/src/server/verifySecret.ts +52 -0
  574. package/src/session/SessionClient.ts +1001 -0
  575. package/src/session/__tests__/SessionClient.additive.test.ts +153 -0
  576. package/src/session/__tests__/SessionClient.broadcastChannel.test.ts +114 -0
  577. package/src/session/__tests__/SessionClient.diagnostics.test.ts +91 -0
  578. package/src/session/__tests__/SessionClient.directory.test.ts +688 -0
  579. package/src/session/__tests__/SessionClient.httpIntegration.test.ts +65 -0
  580. package/src/session/__tests__/SessionClient.identityPin.test.ts +175 -0
  581. package/src/session/__tests__/SessionClient.rest.test.ts +119 -0
  582. package/src/session/__tests__/SessionClient.serverEvents.test.ts +72 -0
  583. package/src/session/__tests__/SessionClient.socket.test.ts +202 -0
  584. package/src/session/__tests__/SessionClient.socketFactory.test.ts +81 -0
  585. package/src/session/__tests__/SessionClient.state.test.ts +78 -0
  586. package/src/session/__tests__/SessionClient.switchTokenOrder.test.ts +170 -0
  587. package/src/session/__tests__/accountDelegationAccess.test.ts +104 -0
  588. package/src/session/__tests__/accountDialogController.test.ts +1969 -0
  589. package/src/session/__tests__/accountDialogShape.test.ts +118 -0
  590. package/src/session/__tests__/accountSwitchTargets.test.ts +137 -0
  591. package/src/session/__tests__/authStateStore.test.ts +451 -0
  592. package/src/session/__tests__/deviceDirectory.test.ts +422 -0
  593. package/src/session/__tests__/deviceSwitcherRows.test.ts +223 -0
  594. package/src/session/__tests__/identityPin.test.ts +131 -0
  595. package/src/session/__tests__/identitySession.test.ts +194 -0
  596. package/src/session/__tests__/projectSessionState.test.ts +105 -0
  597. package/src/session/__tests__/refresh.identityPin.test.ts +307 -0
  598. package/src/session/__tests__/refresh.test.ts +443 -0
  599. package/src/session/__tests__/sessionIntegration.test.ts +209 -0
  600. package/src/session/__tests__/sharedDeviceCredential.test.ts +300 -0
  601. package/src/session/accountDelegationAccess.ts +72 -0
  602. package/src/session/accountDialogController.ts +1494 -0
  603. package/src/session/accountSwitchTargets.ts +93 -0
  604. package/src/session/authStateStore.ts +502 -0
  605. package/src/session/createSessionClient.ts +39 -0
  606. package/src/session/deviceDirectory.ts +269 -0
  607. package/src/session/deviceSwitcherRows.ts +145 -0
  608. package/src/session/identityPin.ts +310 -0
  609. package/src/session/identitySession.ts +206 -0
  610. package/src/session/projectSessionState.ts +137 -0
  611. package/src/session/refresh.ts +517 -0
  612. package/src/session/sessionClientHost.ts +39 -0
  613. package/src/session/sharedDeviceCredential.ts +349 -0
  614. package/src/session/socketLoader.ts +30 -0
  615. package/src/shared/utils/colorUtils.ts +155 -0
  616. package/src/shared/utils/errorUtils.ts +181 -0
  617. package/src/shared/utils/index.ts +59 -0
  618. package/src/shared/utils/networkUtils.ts +248 -0
  619. package/src/shared/utils/themeUtils.ts +115 -0
  620. package/src/types/buffer.d.ts +97 -0
  621. package/src/types/expo-crypto.d.ts +16 -0
  622. package/src/types/expo-secure-store.d.ts +17 -0
  623. package/src/utils/__tests__/accountUtils.test.ts +151 -0
  624. package/src/utils/__tests__/activityId.test.ts +45 -0
  625. package/src/utils/__tests__/asyncUtils.test.ts +187 -0
  626. package/src/utils/__tests__/authWebUrl.test.ts +15 -0
  627. package/src/utils/__tests__/cacheKey.test.ts +0 -0
  628. package/src/utils/__tests__/coldBoot.test.ts +496 -0
  629. package/src/utils/__tests__/commonsApproval.test.ts +60 -0
  630. package/src/utils/__tests__/identityCacheSweep.test.ts +151 -0
  631. package/src/utils/__tests__/languageUtils.test.ts +219 -0
  632. package/src/utils/__tests__/oauthPkce.test.ts +272 -0
  633. package/src/utils/__tests__/officialOrigins.test.ts +31 -0
  634. package/src/utils/__tests__/oxyServiceEnvironment.test.ts +7 -0
  635. package/src/utils/__tests__/profileLinks.test.ts +180 -0
  636. package/src/utils/__tests__/redactUrl.test.ts +33 -0
  637. package/src/utils/__tests__/registrableApex.test.ts +62 -0
  638. package/src/utils/__tests__/textNormalization.test.ts +205 -0
  639. package/src/utils/__tests__/validationUtils.test.ts +703 -0
  640. package/src/utils/__tests__/webauthnOrigin.test.ts +83 -0
  641. package/src/utils/accountCacheSweep.ts +93 -0
  642. package/src/utils/accountUtils.ts +194 -0
  643. package/src/utils/activityId.ts +2 -0
  644. package/src/utils/apiUtils.ts +137 -0
  645. package/src/utils/asyncUtils.ts +280 -0
  646. package/src/utils/authHelpers.ts +148 -0
  647. package/src/utils/authWebUrl.ts +17 -0
  648. package/src/utils/avatarUtils.ts +39 -0
  649. package/src/utils/cache.ts +273 -0
  650. package/src/utils/cacheKey.ts +93 -0
  651. package/src/utils/coldBoot.ts +218 -0
  652. package/src/utils/commonsApproval.ts +39 -0
  653. package/src/utils/commonsDelivery.ts +132 -0
  654. package/src/utils/deviceManager.ts +203 -0
  655. package/src/utils/displayNamePolicyRanges.generated.ts +69 -0
  656. package/src/utils/edgeRegion.ts +2 -0
  657. package/src/utils/errorUtils.ts +352 -0
  658. package/src/utils/identityCacheSweep.ts +104 -0
  659. package/src/utils/languageUtils.ts +256 -0
  660. package/src/utils/oauthPkce.ts +392 -0
  661. package/src/utils/officialOrigins.ts +62 -0
  662. package/src/utils/oxyServiceEnvironment.ts +17 -0
  663. package/src/utils/platform.ts +139 -0
  664. package/src/utils/profileLinks.ts +91 -0
  665. package/src/utils/redactUrl.ts +28 -0
  666. package/src/utils/registrableApex.ts +41 -0
  667. package/src/utils/requestUtils.ts +237 -0
  668. package/src/utils/sessionUtils.ts +206 -0
  669. package/src/utils/textNormalization.ts +162 -0
  670. package/src/utils/userHandle.ts +49 -0
  671. package/src/utils/userIdentity.ts +60 -0
  672. package/src/utils/validationUtils.ts +390 -0
  673. package/src/utils/webauthnOrigin.ts +52 -0
@@ -0,0 +1,861 @@
1
+ import type { AccountStorageUsageResponse, AssetUploadInput, AssetUrlResponse, AssetVariant, BatchFileAccessResponse, RNFileDescriptor, ServiceAssetMetadata, ServiceAssetMetadataBySha } from '../models/interfaces';
2
+ import type { OxyServicesBase } from '../OxyServices.base';
3
+ import { isReactNative } from '@oxy.so/protocol';
4
+ import { logger } from '../logger';
5
+ import { AssetUrlResolutionError, ServiceAssetMetadataError } from '../OxyServices.errors';
6
+ import { extractErrorStatus } from '../utils/errorUtils';
7
+ import { redactUrlQuery } from '../utils/redactUrl';
8
+
9
+ /**
10
+ * Maximum number of ids sent per `POST /assets/service/by-ids` request. Matches
11
+ * the server-side batch cap (the route rejects empty or > 100 id arrays with a
12
+ * 400); larger inputs are split into multiple chunked calls and merged. Mirrors
13
+ * `getUsersByIds`'s `USERS_BY_IDS_CHUNK_SIZE`.
14
+ */
15
+ const SERVICE_ASSET_METADATA_CHUNK_SIZE = 100;
16
+
17
+ /**
18
+ * Maximum number of content hashes sent per `POST /assets/service/by-sha256`
19
+ * request. Matches the server-side cap (the route rejects empty or > 100 hash
20
+ * arrays with a 400); larger inputs are chunked and merged. Same ceiling as
21
+ * {@link SERVICE_ASSET_METADATA_CHUNK_SIZE} for the forward id lookup.
22
+ */
23
+ const SERVICE_ASSET_METADATA_BY_SHA_CHUNK_SIZE = 100;
24
+
25
+ /**
26
+ * Lowercase hex SHA-256 digest matcher (exactly 64 hex chars). The reverse
27
+ * lookup drops non-conforming hashes client-side so a single malformed value
28
+ * never 400s an otherwise-valid chunk on the server.
29
+ */
30
+ const SHA256_HEX_PATTERN = /^[a-f0-9]{64}$/;
31
+
32
+ /**
33
+ * Conservative lower bound (10 min) the SDK assumes for the lifetime of the
34
+ * scoped media token (`mt`) the API embeds in the stream URL it returns for a
35
+ * private asset. The SDK never mints or inspects that token; this constant
36
+ * exists only so the SDK's own URL cache can be sized safely BELOW the token's
37
+ * real lifetime.
38
+ *
39
+ * The API currently mints tokens for 900s (`MEDIA_TOKEN_TTL_SECONDS` in
40
+ * `packages/api/src/utils/mediaToken.ts`). Core deliberately assumes a shorter
41
+ * 10-min floor rather than copying 15: core and the API are separate packages,
42
+ * so core cannot observe a server-side TTL change at runtime. Under-assuming the
43
+ * lifetime only ever shortens the cache (more refetches, never a dead URL), so
44
+ * it stays correct even if the server lowers its TTL toward this floor. The
45
+ * {@link ASSET_URL_CACHE_LIFETIME_FRACTION} discount is applied on top.
46
+ */
47
+ const ASSET_MEDIA_TOKEN_TTL_MS = 10 * 60 * 1000;
48
+
49
+ /**
50
+ * Fraction of a resolved URL's remaining lifetime the SDK is willing to keep it
51
+ * cached for. A resolved URL stops working the moment its media token expires,
52
+ * so caching it for its full nominal lifetime guarantees a window in which the
53
+ * cache hands out an already-dead URL (clock skew, time spent in the render
54
+ * pipeline, an image request queued behind others). Half the lifetime leaves a
55
+ * margin at least as large as the entry's own age.
56
+ */
57
+ const ASSET_URL_CACHE_LIFETIME_FRACTION = 0.5;
58
+
59
+ /**
60
+ * Fallback URL lifetime, in seconds, assumed when the caller does not request
61
+ * an explicit `expiresIn`. Mirrors the API's default signed-URL expiry; the
62
+ * effective value is still clamped by {@link ASSET_MEDIA_TOKEN_TTL_MS}.
63
+ */
64
+ const DEFAULT_ASSET_URL_EXPIRES_IN_SECONDS = 3600;
65
+
66
+ export function OxyServicesAssetsMixin<T extends typeof OxyServicesBase>(Base: T) {
67
+ return class extends Base {
68
+ constructor(...args: any[]) {
69
+ super(...(args as [any]));
70
+ }
71
+
72
+ /**
73
+ * Service-token request, implemented by the auth mixin earlier in the
74
+ * composition pipeline (see `mixins/index.ts`). The assets mixin is typed
75
+ * against `OxyServicesBase`, which does not carry the auth mixin's methods,
76
+ * so this `declare` surfaces the inherited runtime method to TypeScript
77
+ * without re-implementing it. Used by
78
+ * {@link getServiceAssetMetadataByIds} to authenticate the server-to-server
79
+ * `/assets/service/by-ids` bulk fetch with a bearer service token.
80
+ */
81
+ declare makeServiceRequest: <R = unknown>(
82
+ method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE',
83
+ url: string,
84
+ data?: unknown,
85
+ userId?: string,
86
+ ) => Promise<R>;
87
+
88
+ /**
89
+ * Delete file
90
+ */
91
+ async deleteFile(fileId: string): Promise<any> {
92
+ try {
93
+ return await this.makeRequest('DELETE', `/assets/${encodeURIComponent(fileId)}`, undefined, { cache: false });
94
+ } catch (error) {
95
+ throw this.handleError(error);
96
+ }
97
+ }
98
+
99
+ /**
100
+ * Build a synchronous, `<img src>`-ready URL for a **PUBLIC** Oxy asset.
101
+ *
102
+ * ## Contract — read before calling
103
+ *
104
+ * This is a pure string builder. It performs no network call and therefore
105
+ * has **no knowledge of the asset's visibility**. It always produces the
106
+ * public form: `${cloudURL}/<id>[?variant=…]`, which the CDN serves from
107
+ * the public media origin only.
108
+ *
109
+ * Consequently:
110
+ * - Call it ONLY when the asset is known to be `public` — e.g. avatars and
111
+ * profile banners, which {@link uploadAvatar} / {@link uploadProfileBanner}
112
+ * upload with `visibility: 'public'`.
113
+ * - For an asset that may be `private` or `unlisted` — anything uploaded
114
+ * through the generic {@link assetUpload} path, whose server-side default
115
+ * is private — this URL resolves to a hard **404**. Use
116
+ * {@link getFileDownloadUrlAsync}, which asks the API for a URL scoped to
117
+ * the current caller.
118
+ * - It must never guess visibility, and must never embed the caller's
119
+ * bearer token: the returned string is rendered into DOM attributes,
120
+ * browser network panels, HTTP caches, and logs.
121
+ *
122
+ * Passing `expiresIn` switches to the API-origin stream form
123
+ * (`${baseURL}/assets/<id>/stream?…`) WITHOUT any credential. That form
124
+ * still only serves what an unauthenticated request may see — it is not a
125
+ * synchronous private-asset path, and none exists: authorization for a
126
+ * private asset requires the round-trip in {@link getFileDownloadUrlAsync}.
127
+ */
128
+ getFileDownloadUrl(fileId: string, variant?: string, expiresIn?: number): string {
129
+ // Never embed the in-memory bearer token: this URL is rendered into DOM
130
+ // attributes, browser network panels, caches, and logs. Public assets get
131
+ // the clean CDN origin; private/authorized access goes through
132
+ // `getFileDownloadUrlAsync`.
133
+ if (!expiresIn) {
134
+ const variantQs = variant ? `?variant=${encodeURIComponent(variant)}` : '';
135
+ return `${this.getCloudURL()}/${encodeURIComponent(fileId)}${variantQs}`;
136
+ }
137
+
138
+ const base = this.getBaseURL();
139
+ const params = new URLSearchParams();
140
+ if (variant) params.set('variant', variant);
141
+ params.set('expiresIn', String(expiresIn));
142
+ params.set('fallback', 'placeholderVisible');
143
+
144
+ const qs = params.toString();
145
+ return `${base}/assets/${encodeURIComponent(fileId)}/stream${qs ? `?${qs}` : ''}`;
146
+ }
147
+
148
+ /**
149
+ * Resolve an asset id to a URL that is valid for the CURRENT caller,
150
+ * whatever the asset's visibility.
151
+ *
152
+ * Asks the API (`GET /assets/:id/url`) rather than guessing: a `public`
153
+ * asset resolves to the CDN form, while a `private`/`unlisted` asset the
154
+ * caller may read resolves to an API-origin stream URL carrying a scoped,
155
+ * short-lived media token. The returned URL is passed through **unchanged**
156
+ * — the SDK never rewrites, re-signs, or strips it.
157
+ *
158
+ * ## Failure behaviour — no CDN fallback
159
+ *
160
+ * Throws {@link AssetUrlResolutionError} when the API returns no URL or the
161
+ * request fails (including 401/403/404). It deliberately does NOT fall back
162
+ * to {@link getFileDownloadUrl}: that builder only produces the public CDN
163
+ * form, so falling back would hand the caller a URL that renders as a hard
164
+ * 404 for every private asset and would silently swallow the real failure.
165
+ * A caller that knows an asset is public should call the synchronous
166
+ * builder directly instead of relying on a fallback here.
167
+ *
168
+ * The resolved URL is cached per identity for well under the media token's
169
+ * lifetime — see {@link getAssetUrlCacheTTL}.
170
+ */
171
+ async getFileDownloadUrlAsync(fileId: string, variant?: string, expiresIn?: number): Promise<string> {
172
+ let url: string | null;
173
+ try {
174
+ url = await this.fetchAssetDownloadUrl(
175
+ fileId,
176
+ variant,
177
+ this.getAssetUrlCacheTTL(expiresIn),
178
+ expiresIn
179
+ );
180
+ } catch (error: unknown) {
181
+ throw new AssetUrlResolutionError(fileId, variant, extractErrorStatus(error), error);
182
+ }
183
+
184
+ if (!url) {
185
+ throw new AssetUrlResolutionError(fileId, variant, undefined);
186
+ }
187
+
188
+ return url;
189
+ }
190
+
191
+ /**
192
+ * List user files
193
+ */
194
+ async listUserFiles(limit?: number, offset?: number): Promise<{ files: any[]; total: number; hasMore: boolean }> {
195
+ try {
196
+ const paramsObj: any = {};
197
+ if (limit) paramsObj.limit = limit;
198
+ if (offset) paramsObj.offset = offset;
199
+ return await this.makeRequest('GET', '/assets', paramsObj, {
200
+ cache: false,
201
+ });
202
+ } catch (error) {
203
+ throw this.handleError(error);
204
+ }
205
+ }
206
+
207
+ /**
208
+ * Get account storage usage (server-side usage aggregated from assets)
209
+ */
210
+ async getAccountStorageUsage(): Promise<AccountStorageUsageResponse> {
211
+ try {
212
+ return await this.makeRequest<AccountStorageUsageResponse>('GET', '/storage/usage', undefined, {
213
+ cache: false,
214
+ });
215
+ } catch (error) {
216
+ throw this.handleError(error);
217
+ }
218
+ }
219
+
220
+ /**
221
+ * Get file content as text
222
+ */
223
+ async getFileContentAsText(fileId: string, variant?: string): Promise<string> {
224
+ try {
225
+ const downloadUrl = await this.fetchAssetDownloadUrl(
226
+ fileId,
227
+ variant,
228
+ this.getAssetUrlCacheTTL()
229
+ );
230
+
231
+ if (!downloadUrl) {
232
+ throw new Error('No download URL returned for asset');
233
+ }
234
+
235
+ return await this.fetchAssetContent(downloadUrl, 'text');
236
+ } catch (error) {
237
+ throw this.handleError(error);
238
+ }
239
+ }
240
+
241
+ /**
242
+ * Get file content as blob
243
+ */
244
+ async getFileContentAsBlob(fileId: string, variant?: string): Promise<Blob> {
245
+ try {
246
+ const downloadUrl = await this.fetchAssetDownloadUrl(
247
+ fileId,
248
+ variant,
249
+ this.getAssetUrlCacheTTL()
250
+ );
251
+
252
+ if (!downloadUrl) {
253
+ throw new Error('No download URL returned for asset');
254
+ }
255
+
256
+ return await this.fetchAssetContent(downloadUrl, 'blob');
257
+ } catch (error) {
258
+ throw this.handleError(error);
259
+ }
260
+ }
261
+
262
+ /**
263
+ * Resolve access + a caller-scoped URL for many assets — each with its OWN
264
+ * requested variant — in ONE round trip via `POST /assets/batch-access`.
265
+ *
266
+ * `requests` is a per-file `{ fileId, variant? }` list (a `variant` of
267
+ * `undefined` asks for the original). `options.expiresIn` sets the requested
268
+ * media-token / signed-URL lifetime (seconds); `options.context` is the
269
+ * server-side access-check context. Entries with a blank `fileId` are
270
+ * dropped and exact `(fileId, variant)` duplicates are collapsed before the
271
+ * request; an empty effective list performs no network call.
272
+ *
273
+ * The server caps the batch at 100 entries — callers that page beyond that
274
+ * must chunk. Returns the raw per-file envelope (see
275
+ * {@link BatchFileAccessResponse}); most callers want {@link getFileDownloadUrls},
276
+ * which flattens it to just the usable URLs.
277
+ */
278
+ async getBatchFileAccess(
279
+ requests: Array<{ fileId: string; variant?: string }>,
280
+ options?: { expiresIn?: number; context?: string },
281
+ ): Promise<BatchFileAccessResponse> {
282
+ const files = dedupeFileAccessRequests(requests);
283
+ if (files.length === 0) {
284
+ return { results: {} };
285
+ }
286
+
287
+ const body: { files: Array<{ fileId: string; variant?: string }>; expiresIn?: number; context?: string } = {
288
+ files,
289
+ };
290
+ if (typeof options?.expiresIn === 'number') body.expiresIn = options.expiresIn;
291
+ if (typeof options?.context === 'string') body.context = options.context;
292
+
293
+ try {
294
+ return await this.makeRequest<BatchFileAccessResponse>('POST', '/assets/batch-access', body, {
295
+ cache: false,
296
+ });
297
+ } catch (error) {
298
+ throw this.handleError(error);
299
+ }
300
+ }
301
+
302
+ /**
303
+ * Resolve many assets — each with its OWN variant — to caller-scoped,
304
+ * `<img src>`-ready URLs in one round trip. The batch counterpart of
305
+ * {@link getFileDownloadUrlAsync}, built to resolve a whole grid page at once.
306
+ *
307
+ * `requests` is a per-file `{ fileId, variant? }` list (e.g. `poster` for a
308
+ * video, `thumb` for an image); the per-file variant RULE lives in the
309
+ * caller — core just forwards what it is given. `options.expiresIn` /
310
+ * `options.context` are passed through to the endpoint.
311
+ *
312
+ * Each returned URL is the API's own scoped form, passed through unchanged:
313
+ * the public CDN URL for a public asset, or an API-origin
314
+ * `/assets/:id/stream?…&mt=<media token>` URL for a private asset the caller
315
+ * may read. Ids the caller cannot access (or that do not exist) are simply
316
+ * OMITTED from the returned map — there is NO public-CDN fallback, so a grid
317
+ * never renders a known-404 URL. Callers detect a miss by the absent key
318
+ * (the map never contains an empty-string value). Keyed by `fileId`.
319
+ */
320
+ async getFileDownloadUrls(
321
+ requests: Array<{ fileId: string; variant?: string }>,
322
+ options?: { expiresIn?: number; context?: string },
323
+ ): Promise<Record<string, string>> {
324
+ const response = await this.getBatchFileAccess(requests, options);
325
+ const urls: Record<string, string> = {};
326
+ for (const [id, result] of Object.entries(response.results ?? {})) {
327
+ if (result.allowed && result.url) {
328
+ urls[id] = result.url;
329
+ }
330
+ }
331
+ return urls;
332
+ }
333
+
334
+ /**
335
+ * Resolve many Oxy asset ids to their content-addressed metadata in one
336
+ * round-trip per chunk via `POST /assets/service/by-ids` (body `{ ids }`).
337
+ *
338
+ * Returns each asset's `sha256`, `mime`, byte `size`, and `status` — built
339
+ * for server-to-server callers (e.g. Mention's MTN Protocol blob-ref
340
+ * resolution) that need the content hash for an asset id. Ids are
341
+ * deduplicated and validated (empty/blank ids dropped) before being split
342
+ * into chunks of {@link SERVICE_ASSET_METADATA_CHUNK_SIZE} (the server-side
343
+ * cap). The server omits unknown/deleted ids from each chunk's `data`, so
344
+ * the merged result may be shorter than the requested id list and the caller
345
+ * is expected to map by `id`.
346
+ *
347
+ * **Service-token auth (required).** `/assets/service/by-ids` is guarded by
348
+ * `serviceAuthMiddleware` + the `files:read` scope and is called via
349
+ * `makeServiceRequest`, which attaches `Authorization: Bearer <serviceToken>`
350
+ * (the same client that calls `POST /assets/service/cache`). The calling
351
+ * client MUST be service-configured (`configureServiceAuth(apiKey,
352
+ * apiSecret)`) before invoking this method; otherwise `getServiceToken()`
353
+ * throws because no credentials are available. A plain user-session request
354
+ * is rejected by the route's service-auth guard.
355
+ *
356
+ * FAILURE IS NOT ABSENCE. The server legitimately omits unknown/deleted ids,
357
+ * so a short result is normal — which means a chunk that FAILED (a 429, a
358
+ * timeout, a 5xx) is indistinguishable from "those assets don't exist" if it
359
+ * simply contributes nothing. This method used to swallow a failed chunk and
360
+ * return the rest, so every caller silently read a throttled request as "no
361
+ * metadata": a backfill counted the asset as needing no update, and the
362
+ * signed-record builder embedded a media item with no hash. Both reported
363
+ * success while writing nothing, and the MTN chain's records are immutable.
364
+ *
365
+ * So a failed chunk THROWS {@link ServiceAssetMetadataError} by default,
366
+ * carrying the ids it could not resolve. A caller that genuinely wants
367
+ * best-effort opts in with `{ partial: true }` and gets the old behaviour
368
+ * explicitly. An empty/whitespace-only input resolves immediately with `[]`
369
+ * and performs no network call.
370
+ *
371
+ * Not cached at the SDK layer: it's a POST keyed on a multi-id body (low hit
372
+ * rate), mirroring the sibling service/POST methods which never cache.
373
+ */
374
+ async getServiceAssetMetadataByIds(
375
+ ids: string[],
376
+ options: { partial?: boolean } = {},
377
+ ): Promise<ServiceAssetMetadata[]> {
378
+ const uniqueIds = Array.from(
379
+ new Set(ids.filter((id): id is string => typeof id === 'string' && id.trim().length > 0)),
380
+ );
381
+ if (uniqueIds.length === 0) {
382
+ return [];
383
+ }
384
+
385
+ const chunks: string[][] = [];
386
+ for (let i = 0; i < uniqueIds.length; i += SERVICE_ASSET_METADATA_CHUNK_SIZE) {
387
+ chunks.push(uniqueIds.slice(i, i + SERVICE_ASSET_METADATA_CHUNK_SIZE));
388
+ }
389
+
390
+ // Chunks stay independent so one failure never cancels work already in
391
+ // flight; the failures are collected and re-raised together below.
392
+ const unresolvedIds: string[] = [];
393
+ const statuses: number[] = [];
394
+ let firstError: unknown;
395
+
396
+ const settled = await Promise.all(
397
+ chunks.map(async (chunk): Promise<ServiceAssetMetadata[]> => {
398
+ try {
399
+ const entries = await this.makeServiceRequest<ServiceAssetMetadata[]>(
400
+ 'POST',
401
+ '/assets/service/by-ids',
402
+ { ids: chunk },
403
+ );
404
+ return Array.isArray(entries) ? entries : [];
405
+ } catch (error: unknown) {
406
+ const status = extractErrorStatus(error);
407
+ logger.warn('getServiceAssetMetadataByIds: chunk failed', {
408
+ method: 'getServiceAssetMetadataByIds',
409
+ chunkSize: chunk.length,
410
+ status,
411
+ partial: options.partial === true,
412
+ error: error instanceof Error ? error.message : String(error),
413
+ });
414
+ unresolvedIds.push(...chunk);
415
+ if (typeof status === 'number') statuses.push(status);
416
+ firstError ??= error;
417
+ return [];
418
+ }
419
+ }),
420
+ );
421
+
422
+ if (unresolvedIds.length > 0 && options.partial !== true) {
423
+ throw new ServiceAssetMetadataError(unresolvedIds, statuses, firstError);
424
+ }
425
+
426
+ return settled.flat();
427
+ }
428
+
429
+ /**
430
+ * Reverse content-address lookup: resolve many content `sha256` digests to
431
+ * the servable Oxy asset holding each, in one round-trip per chunk via
432
+ * `POST /assets/service/by-sha256` (body `{ sha256s }`).
433
+ *
434
+ * This is the INVERSE of {@link getServiceAssetMetadataByIds}: given a
435
+ * record's `blob.sha256`, it returns the asset's `id`, `mime`, byte `size`,
436
+ * `status`, and — for active, public, CDN-reachable assets only — a public
437
+ * `url` (`cloud.oxy.so`). Built for server-to-server callers (e.g. Mention's
438
+ * MTN materializer / node-blob sync) that hold a content hash and need to
439
+ * map it back to a servable asset. Hashes are lowercased, validated against
440
+ * a 64-char hex pattern (malformed entries dropped client-side), and
441
+ * deduplicated before being split into chunks of
442
+ * {@link SERVICE_ASSET_METADATA_BY_SHA_CHUNK_SIZE} (the server-side cap). The
443
+ * server omits unknown/deleted hashes from each chunk's `data`, so the
444
+ * merged result may be shorter than the requested list and the caller is
445
+ * expected to map by `sha256`.
446
+ *
447
+ * **Service-token auth (required).** `/assets/service/by-sha256` is guarded
448
+ * by `serviceAuthMiddleware` + the `files:read` scope and is called via
449
+ * `makeServiceRequest` (`Authorization: Bearer <serviceToken>`, `cache:false`).
450
+ * The calling client MUST be service-configured (`configureServiceAuth`)
451
+ * before invoking; a plain user-session request is rejected by the route's
452
+ * service-auth guard.
453
+ *
454
+ * Resilience: chunks are independent. A failed chunk is logged and skipped —
455
+ * every entry that resolved is still returned. An empty input (or one whose
456
+ * every value is malformed) resolves immediately with `[]` and performs no
457
+ * network call.
458
+ *
459
+ * Not cached at the SDK layer: it's a POST keyed on a multi-hash body (low
460
+ * hit rate), mirroring the sibling service/POST methods which never cache.
461
+ */
462
+ async getServiceAssetMetadataBySha256(sha256s: string[]): Promise<ServiceAssetMetadataBySha[]> {
463
+ const uniqueShas = Array.from(
464
+ new Set(
465
+ sha256s
466
+ .filter((sha): sha is string => typeof sha === 'string')
467
+ .map((sha) => sha.trim().toLowerCase())
468
+ .filter((sha) => SHA256_HEX_PATTERN.test(sha)),
469
+ ),
470
+ );
471
+ if (uniqueShas.length === 0) {
472
+ return [];
473
+ }
474
+
475
+ const chunks: string[][] = [];
476
+ for (let i = 0; i < uniqueShas.length; i += SERVICE_ASSET_METADATA_BY_SHA_CHUNK_SIZE) {
477
+ chunks.push(uniqueShas.slice(i, i + SERVICE_ASSET_METADATA_BY_SHA_CHUNK_SIZE));
478
+ }
479
+
480
+ // Run chunks concurrently; a single chunk failure must not sink the rest.
481
+ const settled = await Promise.all(
482
+ chunks.map(async (chunk): Promise<ServiceAssetMetadataBySha[]> => {
483
+ try {
484
+ const entries = await this.makeServiceRequest<ServiceAssetMetadataBySha[]>(
485
+ 'POST',
486
+ '/assets/service/by-sha256',
487
+ { sha256s: chunk },
488
+ );
489
+ return Array.isArray(entries) ? entries : [];
490
+ } catch (error: unknown) {
491
+ logger.warn('getServiceAssetMetadataBySha256: chunk failed, continuing with remaining chunks', {
492
+ method: 'getServiceAssetMetadataBySha256',
493
+ chunkSize: chunk.length,
494
+ status: extractErrorStatus(error),
495
+ error: error instanceof Error ? error.message : String(error),
496
+ });
497
+ return [];
498
+ }
499
+ }),
500
+ );
501
+
502
+ return settled.flat();
503
+ }
504
+
505
+ /**
506
+ * Upload raw file data
507
+ */
508
+ async uploadRawFile(file: AssetUploadInput, visibility?: 'private' | 'public' | 'unlisted', metadata?: Record<string, any>): Promise<any> {
509
+ return this.assetUpload(file, visibility, metadata);
510
+ }
511
+
512
+ /**
513
+ * Upload file using Central Asset Service.
514
+ *
515
+ * Accepts either a web File/Blob or a React Native file descriptor
516
+ * ({uri, type, name, size}). RN descriptors are passed directly to
517
+ * FormData.append, which handles them natively.
518
+ */
519
+ async assetUpload(file: AssetUploadInput, visibility?: 'private' | 'public' | 'unlisted', metadata?: Record<string, any>, onProgress?: (progress: number) => void): Promise<any> {
520
+ const fileName = 'name' in file && file.name ? file.name : 'unknown';
521
+ const fileSize = 'size' in file && file.size ? file.size : 0;
522
+
523
+ try {
524
+ const formData = new FormData();
525
+
526
+ if (typeof File !== 'undefined' && file instanceof File) {
527
+ formData.append('file', file, fileName);
528
+ } else if (typeof Blob !== 'undefined' && file instanceof Blob) {
529
+ formData.append('file', file, fileName);
530
+ } else if ('uri' in file && typeof (file as RNFileDescriptor).uri === 'string') {
531
+ const descriptor = file as RNFileDescriptor;
532
+
533
+ if (isReactNative()) {
534
+ // React Native file descriptor — RN's FormData handles {uri, type, name} natively.
535
+ // It reads the file from disk during the multipart request — no in-JS Blob
536
+ // conversion (which would fail on Hermes for ArrayBuffer-backed Blobs).
537
+ formData.append('file', descriptor as unknown as Blob, fileName);
538
+ } else {
539
+ // Web (browser/Node): the browser's FormData cannot read bytes from a plain
540
+ // { uri } object — it would serialize "[object Object]" and the server would
541
+ // store a 0-byte asset. Materialize the uri into a real Blob first. `fetch`
542
+ // resolves blob:, data:, and http(s): uris on web, so all picker outputs work.
543
+ const res = await fetch(descriptor.uri);
544
+ if (!res.ok) {
545
+ throw new Error(`Failed to read file from uri (status ${res.status})`);
546
+ }
547
+ const fetched = await res.blob();
548
+ // Preserve the descriptor's declared MIME type when the fetched blob has none.
549
+ const blob =
550
+ fetched.type === '' && descriptor.type
551
+ ? new Blob([fetched], { type: descriptor.type })
552
+ : fetched;
553
+ if (blob.size === 0) {
554
+ throw new Error('Cannot upload an empty file');
555
+ }
556
+ formData.append('file', blob, fileName);
557
+ }
558
+ } else {
559
+ throw new Error('Unsupported file input: expected File, Blob, or { uri, type?, name?, size? } descriptor');
560
+ }
561
+ if (visibility) {
562
+ formData.append('visibility', visibility);
563
+ }
564
+ if (metadata) {
565
+ formData.append('metadata', JSON.stringify(metadata));
566
+ }
567
+
568
+ const response = await this.getClient().request<{ file: any }>({
569
+ method: 'POST',
570
+ url: '/assets/upload',
571
+ data: formData,
572
+ cache: false,
573
+ });
574
+
575
+ if (onProgress && response) {
576
+ onProgress(100);
577
+ }
578
+
579
+ return response;
580
+ } catch (error) {
581
+ logger.error('File upload error', error, { component: 'OxyServices.assets' });
582
+
583
+ let errorMessage = 'File upload failed';
584
+
585
+ if (error instanceof Error) {
586
+ errorMessage = error.message || errorMessage;
587
+ } else if (error && typeof error === 'object') {
588
+ const errObj = error as Record<string, unknown>;
589
+ if ('message' in errObj) {
590
+ errorMessage = String(errObj.message) || errorMessage;
591
+ } else if (typeof errObj.error === 'string') {
592
+ errorMessage = errObj.error;
593
+ } else if (errObj.data && typeof errObj.data === 'object') {
594
+ const dataObj = errObj.data as Record<string, unknown>;
595
+ if (dataObj.message) {
596
+ errorMessage = String(dataObj.message);
597
+ }
598
+ }
599
+ } else if (error) {
600
+ errorMessage = String(error) || errorMessage;
601
+ }
602
+
603
+ const contextError = error as Error & { fileContext?: Record<string, unknown> };
604
+ if (!contextError.fileContext) {
605
+ contextError.fileContext = {
606
+ fileName,
607
+ fileSize,
608
+ };
609
+ }
610
+
611
+ if (error instanceof Error && error.message) {
612
+ const handledError = this.handleError(contextError);
613
+ if (!handledError.message || handledError.message.trim() === 'An unexpected error occurred') {
614
+ handledError.message = errorMessage;
615
+ }
616
+ throw handledError;
617
+ }
618
+
619
+ const newError: Error & { fileContext?: Record<string, unknown> } = new Error(errorMessage);
620
+ newError.fileContext = contextError.fileContext;
621
+ throw this.handleError(newError);
622
+ }
623
+ }
624
+
625
+ /**
626
+ * Link asset to an entity
627
+ */
628
+ async assetLink(fileId: string, app: string, entityType: string, entityId: string, visibility?: 'private' | 'public' | 'unlisted', webhookUrl?: string): Promise<any> {
629
+ try {
630
+ const body: any = { app, entityType, entityId };
631
+ if (visibility) body.visibility = visibility;
632
+ if (webhookUrl) body.webhookUrl = webhookUrl;
633
+ return await this.makeRequest('POST', `/assets/${fileId}/links`, body, { cache: false });
634
+ } catch (error) {
635
+ throw this.handleError(error);
636
+ }
637
+ }
638
+
639
+ /**
640
+ * Unlink asset from an entity
641
+ */
642
+ async assetUnlink(fileId: string, app: string, entityType: string, entityId: string): Promise<any> {
643
+ try {
644
+ return await this.makeRequest('DELETE', `/assets/${fileId}/links`, {
645
+ app,
646
+ entityType,
647
+ entityId
648
+ }, { cache: false });
649
+ } catch (error) {
650
+ throw this.handleError(error);
651
+ }
652
+ }
653
+
654
+ /**
655
+ * Get asset metadata
656
+ */
657
+ async assetGet(fileId: string): Promise<any> {
658
+ try {
659
+ return await this.makeRequest('GET', `/assets/${fileId}`, undefined, {
660
+ cache: true,
661
+ cacheTTL: 5 * 60 * 1000,
662
+ });
663
+ } catch (error) {
664
+ throw this.handleError(error);
665
+ }
666
+ }
667
+
668
+ /**
669
+ * Get asset URL (CDN or signed URL)
670
+ */
671
+ async assetGetUrl(fileId: string, variant?: string, expiresIn?: number): Promise<AssetUrlResponse> {
672
+ try {
673
+ const params: any = {};
674
+ if (variant) params.variant = variant;
675
+ if (expiresIn) params.expiresIn = expiresIn;
676
+
677
+ return await this.makeRequest<AssetUrlResponse>('GET', `/assets/${fileId}/url`, params, {
678
+ cache: true,
679
+ cacheTTL: this.getAssetUrlCacheTTL(expiresIn),
680
+ });
681
+ } catch (error) {
682
+ throw this.handleError(error);
683
+ }
684
+ }
685
+
686
+ /**
687
+ * Restore asset from trash
688
+ */
689
+ async assetRestore(fileId: string): Promise<any> {
690
+ try {
691
+ const result = await this.makeRequest('POST', `/assets/${fileId}/restore`, undefined, { cache: false });
692
+ // The asset metadata (trash state) changed — bust its cached read.
693
+ this.clearCacheEntry(`GET:/assets/${fileId}`);
694
+ return result;
695
+ } catch (error) {
696
+ throw this.handleError(error);
697
+ }
698
+ }
699
+
700
+ /**
701
+ * Delete asset with optional force
702
+ */
703
+ async assetDelete(fileId: string, force = false): Promise<any> {
704
+ try {
705
+ const params: any = force ? { force: 'true' } : undefined;
706
+ const result = await this.makeRequest('DELETE', `/assets/${fileId}`, params, { cache: false });
707
+ // Bust the cached metadata and every cached URL variant for the asset.
708
+ this.clearCacheEntry(`GET:/assets/${fileId}`);
709
+ this.clearCacheByPrefix(`GET:/assets/${fileId}/url`);
710
+ return result;
711
+ } catch (error) {
712
+ throw this.handleError(error);
713
+ }
714
+ }
715
+
716
+ /**
717
+ * Get list of available variants for an asset
718
+ */
719
+ async assetGetVariants(fileId: string): Promise<AssetVariant[]> {
720
+ try {
721
+ const assetData = await this.assetGet(fileId);
722
+ return assetData.file?.variants || [];
723
+ } catch (error) {
724
+ throw this.handleError(error);
725
+ }
726
+ }
727
+
728
+ /**
729
+ * Update asset visibility
730
+ */
731
+ async assetUpdateVisibility(fileId: string, visibility: 'private' | 'public' | 'unlisted'): Promise<any> {
732
+ try {
733
+ const result = await this.makeRequest('PATCH', `/assets/${fileId}/visibility`, {
734
+ visibility
735
+ }, { cache: false });
736
+ // Visibility changes both the asset metadata and the resolved URL
737
+ // (public CDN vs signed). Bust the metadata read and every cached URL
738
+ // variant (keyed on variant/expiresIn params).
739
+ this.clearCacheEntry(`GET:/assets/${fileId}`);
740
+ this.clearCacheByPrefix(`GET:/assets/${fileId}/url`);
741
+ return result;
742
+ } catch (error) {
743
+ throw this.handleError(error);
744
+ }
745
+ }
746
+
747
+ async uploadAvatar(file: AssetUploadInput, userId: string, app = 'profiles'): Promise<any> {
748
+ try {
749
+ const asset = await this.assetUpload(file, 'public');
750
+ await this.assetLink(asset.file.id, app, 'avatar', userId, 'public');
751
+ return asset;
752
+ } catch (error) {
753
+ throw this.handleError(error);
754
+ }
755
+ }
756
+
757
+ async uploadProfileBanner(file: AssetUploadInput, userId: string, app = 'profiles'): Promise<any> {
758
+ try {
759
+ const asset = await this.assetUpload(file, 'public');
760
+ await this.assetLink(asset.file.id, app, 'profile-banner', userId, 'public');
761
+ return asset;
762
+ } catch (error) {
763
+ throw this.handleError(error);
764
+ }
765
+ }
766
+
767
+ /**
768
+ * How long a resolved asset URL may stay in the SDK's GET cache, in ms.
769
+ *
770
+ * A resolved private-asset URL dies the instant its scoped media token
771
+ * expires (~{@link ASSET_MEDIA_TOKEN_TTL_MS}). Caching it for its full
772
+ * nominal lifetime would leave a window where the cache serves an
773
+ * already-dead URL (clock skew, render-pipeline latency, an image request
774
+ * queued behind others). So the TTL is (a) never longer than the token's
775
+ * lifetime and (b) discounted to {@link ASSET_URL_CACHE_LIFETIME_FRACTION}
776
+ * of that bound — comfortably below the token TTL by construction.
777
+ */
778
+ public getAssetUrlCacheTTL(expiresIn?: number): number {
779
+ const requestedLifetimeMs = (expiresIn ?? DEFAULT_ASSET_URL_EXPIRES_IN_SECONDS) * 1000;
780
+ const boundedLifetimeMs = Math.min(requestedLifetimeMs, ASSET_MEDIA_TOKEN_TTL_MS);
781
+ return Math.floor(boundedLifetimeMs * ASSET_URL_CACHE_LIFETIME_FRACTION);
782
+ }
783
+
784
+ public async fetchAssetDownloadUrl(
785
+ fileId: string,
786
+ variant?: string,
787
+ cacheTTL?: number,
788
+ expiresIn?: number
789
+ ): Promise<string | null> {
790
+ const params: any = {};
791
+ if (variant) params.variant = variant;
792
+ if (expiresIn) params.expiresIn = expiresIn;
793
+
794
+ const urlRes = await this.makeRequest<{ url: string }>(
795
+ 'GET',
796
+ `/assets/${encodeURIComponent(fileId)}/url`,
797
+ Object.keys(params).length ? params : undefined,
798
+ {
799
+ cache: true,
800
+ // Cap the cached URL well below the media token's lifetime. The
801
+ // response body is a scoped, expiring URL; over-caching it serves a
802
+ // dead URL after the token expires (see getAssetUrlCacheTTL).
803
+ cacheTTL: cacheTTL ?? this.getAssetUrlCacheTTL(expiresIn),
804
+ }
805
+ );
806
+
807
+ return urlRes?.url || null;
808
+ }
809
+
810
+ public async fetchAssetContent(url: string, type: 'text'): Promise<string>;
811
+ public async fetchAssetContent(url: string, type: 'blob'): Promise<Blob>;
812
+ public async fetchAssetContent(url: string, type: 'text' | 'blob') {
813
+ const response = await fetch(url, {
814
+ credentials: shouldSendAssetCredentials(url, this.getBaseURL()) ? 'include' : 'omit',
815
+ });
816
+ if (!response?.ok) {
817
+ throw new Error(`Failed to fetch asset content (status ${response?.status})`);
818
+ }
819
+ return type === 'text' ? response.text() : response.blob();
820
+ }
821
+ };
822
+ }
823
+
824
+ /**
825
+ * Normalize the per-file batch-access request list: drop entries with a blank
826
+ * `fileId` and collapse exact `(fileId, variant)` duplicates (first occurrence
827
+ * wins, preserving order). Two entries for the SAME `fileId` with DIFFERENT
828
+ * variants are intentionally kept — but note the response is keyed by `fileId`,
829
+ * so a caller that needs two variants of one file must issue separate calls.
830
+ */
831
+ function dedupeFileAccessRequests(
832
+ requests: Array<{ fileId: string; variant?: string }>,
833
+ ): Array<{ fileId: string; variant?: string }> {
834
+ const seen = new Set<string>();
835
+ const out: Array<{ fileId: string; variant?: string }> = [];
836
+ for (const req of requests) {
837
+ if (typeof req?.fileId !== 'string' || req.fileId.trim().length === 0) {
838
+ continue;
839
+ }
840
+ const key = `${req.fileId}\u0000${req.variant ?? ''}`;
841
+ if (seen.has(key)) {
842
+ continue;
843
+ }
844
+ seen.add(key);
845
+ out.push(req.variant === undefined ? { fileId: req.fileId } : { fileId: req.fileId, variant: req.variant });
846
+ }
847
+ return out;
848
+ }
849
+
850
+ /**
851
+ * Only send ambient credentials (cookies) when the asset URL is same-origin with
852
+ * the configured API base. Caller-supplied cross-origin asset URLs must not leak
853
+ * the user's cookies to arbitrary hosts.
854
+ */
855
+ function shouldSendAssetCredentials(url: string, baseURL: string): boolean {
856
+ try {
857
+ return new URL(url).origin === new URL(baseURL).origin;
858
+ } catch {
859
+ return false;
860
+ }
861
+ }