@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,467 @@
1
+ /**
2
+ * Shared security headers (Helmet + Content-Security-Policy) for Oxy backends.
3
+ *
4
+ * WHY THIS EXISTS
5
+ * ---------------
6
+ * A CSP only governs an origin that serves DOCUMENTS; on a JSON API it governs
7
+ * no browsing context. The Oxy origins that serve HTML through Cloudflare have
8
+ * so far either hand-written their own policy or shipped none at all, and two
9
+ * bugs follow from that:
10
+ *
11
+ * 1. THE CLOUDFLARE INSIGHTS BEACON IS BLOCKED BY A HAND-WRITTEN POLICY.
12
+ * Cloudflare injects `<script src="https://static.cloudflareinsights.com/
13
+ * beacon.min.js/...">` into HTML it proxies. No application code loads it,
14
+ * so it cannot be allowlisted from the app side any other way, and an
15
+ * origin whose policy says `script-src 'self'` logs
16
+ * `Loading the script 'https://static.cloudflareinsights.com/beacon.min.js'
17
+ * violates the following Content Security Policy directive: "script-src
18
+ * 'self'"` and collects nothing. The beacon needs BOTH hosts, and they are
19
+ * different halves of the same feature: `static.cloudflareinsights.com`
20
+ * serves the script (`script-src`), `cloudflareinsights.com` receives the
21
+ * measurements (`connect-src`). Allowing only the script leaves the beacon
22
+ * loading but unable to report, which looks fixed and is not. Verified in
23
+ * production 2026-07-29: `mention.earth` serves HTML behind Cloudflare with
24
+ * the beacon injected and `script-src 'self'` — blocked; `oxy.so` had
25
+ * already allowlisted the same two hosts in its own static `_headers`,
26
+ * independently, which is the divergence this baseline exists to end.
27
+ *
28
+ * 2. AN EXPLICIT DIRECTIVE SILENTLY REPLACES HELMET'S DEFAULT.
29
+ * Writing `scriptSrc: ['https://example.com']` drops `'self'` — the page's
30
+ * own bundle stops loading (or, worse, only some lazily-loaded chunk does,
31
+ * so it ships). This helper makes that structurally impossible: callers can
32
+ * only ADD sources to the Oxy baseline, never replace a directive, and they
33
+ * cannot pass their own `contentSecurityPolicy` through to Helmet at all
34
+ * (the option is typed `never`).
35
+ *
36
+ * 3. A STATIC EXPO EXPORT SHIPS AN INLINE SCRIPT THE BASELINE FORBIDS.
37
+ * `web.output: 'static'` makes Expo Router emit
38
+ * `<script type="module">globalThis.__EXPO_ROUTER_HYDRATE__=true;</script>`,
39
+ * which is what tells the client entry to call `hydrateRoot` instead of
40
+ * `createRoot().render()`. Nothing in app code puts it there, so — like the
41
+ * Cloudflare beacon above — an app cannot allowlist it from the app side.
42
+ * Measured on `accounts.oxy.so` 2026-08-21: blocked, so every visit threw
43
+ * away the server-rendered markup and re-rendered from scratch, with only a
44
+ * console error to show for it. The hashes are therefore DERIVED from the
45
+ * built output rather than hand-written (see {@link extractInlineScripts}):
46
+ * a hash pasted into config is correct exactly until the build changes one
47
+ * byte, and then it fails the same silent way.
48
+ *
49
+ * WHAT IT PROVIDES
50
+ * ----------------
51
+ * `createOxySecurityHeaders(options)` returns the Helmet middleware with the
52
+ * Oxy-wide CSP baseline applied, plus per-app extensions merged (and deduped)
53
+ * into it. Everything Helmet does that is NOT the CSP (HSTS, frameguard,
54
+ * referrer policy, CORP/COOP, …) is passed straight through, so an app keeps
55
+ * full control of those.
56
+ *
57
+ * `buildOxyCspDirectives(extensions)` is the same resolution as a pure
58
+ * function, for the Oxy document origins that are NOT Express — a Next.js
59
+ * `headers()`, a Cloudflare Pages `_headers` generator — so one policy can
60
+ * cover them without a second implementation.
61
+ *
62
+ * SCOPE: mount this on backends that serve HTML. A JSON-only API gains nothing
63
+ * from a source-list CSP; harden those with the non-CSP headers instead
64
+ * (`hsts`, `noSniff`, `frameguard`, CORP) rather than adding directives that
65
+ * apply to no document.
66
+ *
67
+ * Node/Express-only: exported solely from `@oxy.so/core/server`.
68
+ */
69
+
70
+ import { createHash } from 'node:crypto';
71
+ import type { RequestHandler } from 'express';
72
+ import helmet, { type HelmetOptions } from 'helmet';
73
+
74
+ /** CSP keyword for "this origin". Always present in every open baseline directive. */
75
+ const SELF = "'self'";
76
+
77
+ /** CSP keyword for a fully closed directive. Meaningless alongside any other source. */
78
+ const NONE = "'none'";
79
+
80
+ /**
81
+ * Cloudflare Web Analytics. Injected at the edge into proxied HTML — no Oxy app
82
+ * loads it, and no Oxy app should have to know these hostnames. Both are
83
+ * required: the script host, and the host the beacon reports to.
84
+ */
85
+ const CLOUDFLARE_INSIGHTS_SCRIPT_ORIGIN = 'https://static.cloudflareinsights.com';
86
+ const CLOUDFLARE_INSIGHTS_REPORT_ORIGIN = 'https://cloudflareinsights.com';
87
+
88
+ /**
89
+ * Oxy platform origins. Every Oxy web origin runs the SDK, which calls the Oxy
90
+ * API over HTTPS and Socket.IO, and resolves all canonical media through the
91
+ * Oxy CDN (`getFileDownloadUrl` → `cloud.oxy.so`).
92
+ */
93
+ const OXY_API_ORIGIN = 'https://api.oxy.so';
94
+ const OXY_API_WEBSOCKET_ORIGIN = 'wss://api.oxy.so';
95
+ const OXY_CDN_ORIGIN = 'https://cloud.oxy.so';
96
+
97
+ /** The CSP directives an Oxy app may extend, in Helmet's camelCase spelling. */
98
+ export type OxyCspDirective =
99
+ | 'baseUri'
100
+ | 'connectSrc'
101
+ | 'defaultSrc'
102
+ | 'fontSrc'
103
+ | 'formAction'
104
+ | 'frameAncestors'
105
+ | 'frameSrc'
106
+ | 'imgSrc'
107
+ | 'manifestSrc'
108
+ | 'mediaSrc'
109
+ | 'objectSrc'
110
+ | 'scriptSrc'
111
+ | 'scriptSrcAttr'
112
+ | 'scriptSrcElem'
113
+ | 'styleSrc'
114
+ | 'styleSrcElem'
115
+ | 'workerSrc';
116
+
117
+ /**
118
+ * Per-app ADDITIONS to the Oxy baseline, keyed by directive. Values are merged
119
+ * into the baseline and deduped — they never replace it, so `'self'` (and the
120
+ * Cloudflare beacon hosts) cannot be lost. Extending a directive the baseline
121
+ * does not define seeds it with `'self'` first, for the same reason.
122
+ */
123
+ export type OxyCspExtensions = Partial<Record<OxyCspDirective, readonly string[]>>;
124
+
125
+ /**
126
+ * The Oxy-wide CSP baseline. Deliberately the floor every Oxy origin needs, not
127
+ * a superset of what any one app allows — permissive sources an individual app
128
+ * wants (`https:` images, `blob:` media, embed hosts, LiveKit) are that app's
129
+ * extension, so each widening stays visible at its call site.
130
+ *
131
+ * `style-src` carries `'unsafe-inline'` because react-native-web injects its
132
+ * stylesheet as inline `<style>` at runtime; without it every Oxy web app
133
+ * renders unstyled.
134
+ */
135
+ export const OXY_CSP_BASELINE: Readonly<Partial<Record<OxyCspDirective, readonly string[]>>> =
136
+ Object.freeze({
137
+ defaultSrc: Object.freeze([SELF]),
138
+ baseUri: Object.freeze([SELF]),
139
+ formAction: Object.freeze([SELF]),
140
+ frameAncestors: Object.freeze([NONE]),
141
+ objectSrc: Object.freeze([NONE]),
142
+ scriptSrc: Object.freeze([SELF, CLOUDFLARE_INSIGHTS_SCRIPT_ORIGIN]),
143
+ scriptSrcAttr: Object.freeze([NONE]),
144
+ styleSrc: Object.freeze([SELF, "'unsafe-inline'"]),
145
+ imgSrc: Object.freeze([SELF, 'data:', OXY_CDN_ORIGIN]),
146
+ mediaSrc: Object.freeze([SELF, OXY_CDN_ORIGIN]),
147
+ fontSrc: Object.freeze([SELF, 'data:']),
148
+ connectSrc: Object.freeze([
149
+ SELF,
150
+ CLOUDFLARE_INSIGHTS_REPORT_ORIGIN,
151
+ OXY_API_ORIGIN,
152
+ OXY_API_WEBSOCKET_ORIGIN,
153
+ OXY_CDN_ORIGIN,
154
+ ]),
155
+ });
156
+
157
+ /** `connectSrc` → `connect-src`. Total over `OxyCspDirective` (all are camelCase ASCII). */
158
+ function toHeaderDirectiveName(directive: OxyCspDirective): string {
159
+ return directive.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`);
160
+ }
161
+
162
+ /**
163
+ * A source containing `;` or `,` would silently terminate the directive (or the
164
+ * whole policy) and hand the rest of the string to the browser as new
165
+ * directives. Helmet rejects these too; we reject them here so the pure builder
166
+ * is equally safe, and so the failure names the offending directive.
167
+ */
168
+ function assertValidSource(directive: OxyCspDirective, source: string): void {
169
+ if (typeof source !== 'string' || source.length === 0) {
170
+ throw new TypeError(`Oxy CSP: ${toHeaderDirectiveName(directive)} received an empty source.`);
171
+ }
172
+ if (source.includes(';') || source.includes(',')) {
173
+ throw new TypeError(
174
+ `Oxy CSP: ${toHeaderDirectiveName(directive)} source ${JSON.stringify(source)} may not contain ";" or ",".`,
175
+ );
176
+ }
177
+ }
178
+
179
+ /** Order-preserving, first-seen-wins dedupe. */
180
+ function dedupe(sources: readonly string[]): string[] {
181
+ return [...new Set(sources)];
182
+ }
183
+
184
+ /**
185
+ * Resolve the effective CSP directives: the Oxy baseline, with each app
186
+ * extension merged in and deduped.
187
+ *
188
+ * Merge rules:
189
+ * - A baseline directive is EXTENDED, never replaced — `'self'` and the
190
+ * Cloudflare beacon hosts always survive.
191
+ * - A directive absent from the baseline is seeded with `'self'`, so adding
192
+ * (say) an embed host to `frame-src` cannot lock the origin out of itself.
193
+ * - A directive whose baseline is exactly `'none'` is CLOSED: extending it
194
+ * drops the sentinel, because `'none'` alongside any other source is
195
+ * meaningless per the CSP spec. This is how an app that must be framable
196
+ * opts back in with `frameAncestors: ["'self'"]`.
197
+ *
198
+ * @example
199
+ * ```ts
200
+ * buildOxyCspDirectives({ frameSrc: ['https://player.vimeo.com'] });
201
+ * // → { ..., 'frame-src': ["'self'", 'https://player.vimeo.com'], ... }
202
+ * ```
203
+ */
204
+ export function buildOxyCspDirectives(extensions: OxyCspExtensions = {}): Record<string, string[]> {
205
+ const directiveNames = new Set<OxyCspDirective>([
206
+ ...(Object.keys(OXY_CSP_BASELINE) as OxyCspDirective[]),
207
+ ...(Object.keys(extensions) as OxyCspDirective[]),
208
+ ]);
209
+
210
+ const resolved: Record<string, string[]> = {};
211
+
212
+ for (const directive of directiveNames) {
213
+ const extras = extensions[directive] ?? [];
214
+ for (const source of extras) {
215
+ assertValidSource(directive, source);
216
+ }
217
+
218
+ const baseline = OXY_CSP_BASELINE[directive] ?? [SELF];
219
+ const isClosed = baseline.length === 1 && baseline[0] === NONE;
220
+ const merged = isClosed && extras.length > 0 ? extras : [...baseline, ...extras];
221
+
222
+ resolved[toHeaderDirectiveName(directive)] = dedupe(merged);
223
+ }
224
+
225
+ // Valueless directive: rewrite stray `http://` subresources to HTTPS rather
226
+ // than failing them, which matters for federated/user-supplied URLs.
227
+ resolved['upgrade-insecure-requests'] = [];
228
+
229
+ return resolved;
230
+ }
231
+
232
+ /**
233
+ * Serialize resolved CSP directives into the single-line header value browsers
234
+ * and Cloudflare `_headers` expect. Valueless directives (e.g.
235
+ * `upgrade-insecure-requests`) emit the name alone.
236
+ */
237
+ export function formatOxyCspPolicy(directives: Record<string, string[]>): string {
238
+ return Object.entries(directives)
239
+ .map(([name, sources]) => (sources.length === 0 ? name : `${name} ${sources.join(' ')}`))
240
+ .join('; ');
241
+ }
242
+
243
+ /**
244
+ * The source list one directive carries in a serialized policy, or `[]` when
245
+ * the policy does not name that directive. The inverse of
246
+ * {@link formatOxyCspPolicy}, and the reason it lives here rather than beside
247
+ * either caller: the post-deploy gate parses the policy the ORIGIN serves while
248
+ * the unit test parses the one the middleware renders, so a copy in each would
249
+ * let the header shape change with the test still green and the gate reading
250
+ * `[]` — reporting every script blocked, which reads as a broken app rather
251
+ * than as a broken parser.
252
+ *
253
+ * A directive present with no sources (`upgrade-insecure-requests`) and a
254
+ * directive absent entirely both answer `[]`. Callers that need to tell those
255
+ * apart are asking a different question than "what is allowed here".
256
+ */
257
+ export function cspSourcesFor(policy: string, directive: string): string[] {
258
+ const segment = policy
259
+ .split(';')
260
+ .map((entry) => entry.trim())
261
+ .find((entry) => entry === directive || entry.startsWith(`${directive} `));
262
+ return segment === undefined ? [] : segment.split(/\s+/).slice(1);
263
+ }
264
+
265
+ /**
266
+ * Index of the `>` that closes a tag whose attribute region starts at `from`,
267
+ * or `-1` if the document ends first. Quote-aware: a `>` inside an attribute
268
+ * VALUE does not close the tag.
269
+ *
270
+ * No HTML any Oxy build currently emits contains such an attribute, so this is
271
+ * not load-bearing today — it is here because the same scanner reads the SERVED
272
+ * document in the post-deploy gate, and what an edge injects into that document
273
+ * is not ours to constrain. Getting it wrong is not a parse error: the body
274
+ * window shifts, the hash is computed over the wrong bytes, and the script is
275
+ * blocked exactly as if no hash had been derived at all.
276
+ */
277
+ function findTagEnd(html: string, from: number): number {
278
+ let quote: '"' | "'" | null = null;
279
+ for (let index = from; index < html.length; index += 1) {
280
+ const character = html[index];
281
+ if (quote !== null) {
282
+ if (character === quote) quote = null;
283
+ continue;
284
+ }
285
+ if (character === '"' || character === "'") {
286
+ quote = character;
287
+ continue;
288
+ }
289
+ if (character === '>') return index;
290
+ }
291
+ return -1;
292
+ }
293
+
294
+ /**
295
+ * Every inline `<script>` body in an HTML document, in document order. A
296
+ * `<script src=…>` is a URL the source list already governs and is skipped.
297
+ *
298
+ * Scanned rather than matched with one regex because the two failure modes are
299
+ * not symmetric: an EXTRA body costs a redundant hash nobody notices, while a
300
+ * MISSED body silently reinstates the exact breakage this exists to prevent.
301
+ * So the scan errs toward finding them — it walks the open tag quote-aware
302
+ * instead of letting a `>` inside an attribute value truncate it.
303
+ *
304
+ * The type attribute is deliberately not consulted. Whether a given `type`
305
+ * executes is a browser decision (and it changes: `importmap` and
306
+ * `speculationrules` were both once inert), and pinning the exact bytes of a
307
+ * data block we ship ourselves weakens nothing.
308
+ */
309
+ export function extractInlineScripts(html: string): string[] {
310
+ const lowered = html.toLowerCase();
311
+ const bodies: string[] = [];
312
+ const openTag = /<script\b/gi;
313
+
314
+ let match = openTag.exec(html);
315
+ while (match !== null) {
316
+ const attributesStart = match.index + match[0].length;
317
+ const attributesEnd = findTagEnd(html, attributesStart);
318
+ if (attributesEnd < 0) break;
319
+
320
+ const bodyStart = attributesEnd + 1;
321
+ const bodyEnd = lowered.indexOf('</script', bodyStart);
322
+ if (bodyEnd < 0) break;
323
+
324
+ if (!/\bsrc\s*=/i.test(html.slice(attributesStart, attributesEnd))) {
325
+ bodies.push(html.slice(bodyStart, bodyEnd));
326
+ }
327
+
328
+ openTag.lastIndex = bodyEnd;
329
+ match = openTag.exec(html);
330
+ }
331
+
332
+ return bodies;
333
+ }
334
+
335
+ /**
336
+ * The `'sha256-…'` source that allows one inline script, hashed over its exact
337
+ * bytes as CSP specifies — no trimming, no normalization. One byte of
338
+ * whitespace either way is a different hash and the script stays blocked.
339
+ */
340
+ export function inlineScriptCspHash(source: string): string {
341
+ return `'sha256-${createHash('sha256').update(source, 'utf8').digest('base64')}'`;
342
+ }
343
+
344
+ /**
345
+ * Ceiling on how many derived inline-script hashes may enter one `_headers`
346
+ * block. Nothing in an Oxy app authors an inline script, so the realistic
347
+ * count is the ONE Expo Router hydration flag — deduped across every route's
348
+ * HTML, because it is byte-identical in all of them.
349
+ *
350
+ * The ceiling exists because one future change breaks that: a route loader
351
+ * makes Expo emit a SECOND inline script, `__EXPO_ROUTER_LOADER_DATA__`, whose
352
+ * bytes differ per route. The hashes stay CORRECT (they are derived from the
353
+ * same build that ships), but the count becomes the route count and the policy
354
+ * grows without bound on every response. That is a decision to take
355
+ * deliberately, so it arrives as a red build rather than a quietly enormous
356
+ * header.
357
+ */
358
+ const MAX_INLINE_SCRIPT_HASHES = 8;
359
+
360
+ export interface OxyPagesHeadersOptions {
361
+ /** Per-app additions merged into {@link OXY_CSP_BASELINE}. */
362
+ csp?: OxyCspExtensions;
363
+ /**
364
+ * Emit `Strict-Transport-Security` (default `true`). Cloudflare Pages serves
365
+ * HTTPS only, so static deploys should keep this on.
366
+ */
367
+ hsts?: boolean;
368
+ /**
369
+ * The BUILT HTML documents this `_headers` will be served alongside. Every
370
+ * inline script found in them is allowed by hash, added to `script-src`.
371
+ *
372
+ * Passing the built output — rather than hand-writing a hash into
373
+ * `oxy.pages-headers.json` — is the whole point: a pasted hash is correct
374
+ * until the generator changes one byte of that script, and then the script is
375
+ * blocked again with nothing but a console error to show for it.
376
+ */
377
+ html?: readonly string[];
378
+ }
379
+
380
+ /**
381
+ * Build a Cloudflare Pages `_headers` block for an Oxy HTML origin. Uses the
382
+ * same CSP resolution as {@link createOxySecurityHeaders} plus the non-CSP
383
+ * hardening headers Helmet would add on an Express HTML backend.
384
+ *
385
+ * Adding a hash to `script-src` does not narrow it: per CSP Level 3 a hash is
386
+ * an additional source, so `'self'` and the beacon host keep matching external
387
+ * scripts. (It WOULD neutralize `'unsafe-inline'` in the same directive — which
388
+ * is why this hashes scripts only. `style-src` keeps `'unsafe-inline'` for
389
+ * react-native-web's runtime stylesheet, and a style hash would silently switch
390
+ * that off and render every Oxy web app unstyled.)
391
+ */
392
+ export function buildOxyPagesHeaders(options: OxyPagesHeadersOptions = {}): string {
393
+ const hashes = [
394
+ ...new Set((options.html ?? []).flatMap(extractInlineScripts).map(inlineScriptCspHash)),
395
+ ];
396
+ if (hashes.length > MAX_INLINE_SCRIPT_HASHES) {
397
+ throw new RangeError(
398
+ `Oxy CSP: ${hashes.length} distinct inline scripts in the built HTML exceeds the ${MAX_INLINE_SCRIPT_HASHES}-hash ceiling. A per-route inline data block (e.g. an Expo Router loader) is the likely cause; allow it deliberately rather than by raising this.`,
399
+ );
400
+ }
401
+
402
+ const csp = formatOxyCspPolicy(
403
+ buildOxyCspDirectives(
404
+ hashes.length === 0
405
+ ? options.csp
406
+ : { ...options.csp, scriptSrc: [...(options.csp?.scriptSrc ?? []), ...hashes] },
407
+ ),
408
+ );
409
+ const lines = [
410
+ '/*',
411
+ ` Content-Security-Policy: ${csp}`,
412
+ ' X-Frame-Options: DENY',
413
+ ' X-Content-Type-Options: nosniff',
414
+ ' Referrer-Policy: strict-origin-when-cross-origin',
415
+ ];
416
+ if (options.hsts !== false) {
417
+ lines.push(' Strict-Transport-Security: max-age=31536000; includeSubDomains; preload');
418
+ }
419
+ lines.push('');
420
+ return lines.join('\n');
421
+ }
422
+
423
+ export interface OxySecurityHeadersOptions {
424
+ /**
425
+ * Per-app additions to the Oxy CSP baseline. Merged, deduped, never
426
+ * replacing — see {@link buildOxyCspDirectives}.
427
+ */
428
+ csp?: OxyCspExtensions;
429
+ /**
430
+ * Everything Helmet does that is not the CSP: `hsts`, `frameguard`,
431
+ * `referrerPolicy`, `crossOriginResourcePolicy`, … Passed straight through.
432
+ *
433
+ * `contentSecurityPolicy` is typed `never` on purpose: the CSP is owned by
434
+ * this helper so the baseline cannot be replaced (nor the `'self'` guarantee
435
+ * bypassed) by an app that hands Helmet its own directive block. Extend it
436
+ * through `csp` instead.
437
+ */
438
+ helmet?: HelmetOptions & { contentSecurityPolicy?: never };
439
+ }
440
+
441
+ /**
442
+ * Build the shared Oxy security-headers middleware: Helmet with the Oxy CSP
443
+ * baseline plus this app's extensions.
444
+ *
445
+ * @example
446
+ * ```ts
447
+ * app.use(createOxySecurityHeaders({
448
+ * csp: {
449
+ * connectSrc: ['https://api.example.com', 'wss://api.example.com'],
450
+ * frameSrc: ['https://player.vimeo.com'],
451
+ * },
452
+ * helmet: { crossOriginResourcePolicy: { policy: 'cross-origin' } },
453
+ * }));
454
+ * ```
455
+ */
456
+ export function createOxySecurityHeaders(options: OxySecurityHeadersOptions = {}): RequestHandler {
457
+ const { csp, helmet: helmetOptions } = options;
458
+ const directives = buildOxyCspDirectives(csp);
459
+
460
+ return helmet({
461
+ ...helmetOptions,
462
+ // `useDefaults: false`: the baseline above is the whole policy, so what the
463
+ // browser receives is exactly what `buildOxyCspDirectives` returns — no
464
+ // silent union with Helmet's defaults that tests would never see.
465
+ contentSecurityPolicy: { useDefaults: false, directives },
466
+ });
467
+ }
@@ -0,0 +1,199 @@
1
+ /**
2
+ * Oxy user-invalidation publish/consume helpers for Oxy backends.
3
+ *
4
+ * WHY THIS EXISTS
5
+ * ---------------
6
+ * Every Oxy backend caches Oxy identity, and none of them find out when it
7
+ * changes. The `OxyServices` GET response cache holds `GET /users/:id` and
8
+ * `GET /profiles/username/:name` for five minutes; it is swept when THIS process
9
+ * writes the profile (the `evictOxyIdentityCache` calls in the user and accounts
10
+ * mixins) and never when somebody else does — which is the normal case, since
11
+ * profiles are edited in Oxy's own apps. So an avatar or display-name change is
12
+ * invisible to every consuming backend for up to five minutes, per process.
13
+ *
14
+ * oxy-api broadcasts {@link OXY_USER_INVALIDATION_CHANNEL} on the shared Valkey
15
+ * when a user's identity changes. This module is the consumer half: it parses
16
+ * and validates the event, sweeps the SDK's own cache, and hands the event to
17
+ * app-specific eviction. Wiring it is two lines and every backend that does so
18
+ * stops serving stale identity.
19
+ *
20
+ * WHY THE TRANSPORT IS THE CALLER'S JOB
21
+ * -------------------------------------
22
+ * This module deliberately does NOT take a Redis client. ioredis and node-redis
23
+ * disagree about how to subscribe — node-redis passes the listener to
24
+ * `subscribe(channel, listener)`, ioredis takes `subscribe(channel)` and then
25
+ * emits `'message'` on the client — and a helper that accepted "a client" would
26
+ * have to sniff which library it was handed. That kind of detection is exactly
27
+ * what breaks silently when a consumer upgrades a client library.
28
+ *
29
+ * So the split is: this module owns parsing, validation, dispatch and the
30
+ * never-throw guarantee (the parts that are easy to get wrong and identical
31
+ * everywhere), and the caller owns its own client's two-line subscribe idiom
32
+ * (trivial, but library-specific).
33
+ *
34
+ * // node-redis
35
+ * await subscriber.subscribe(
36
+ * OXY_USER_INVALIDATION_CHANNEL,
37
+ * createOxyUserInvalidationHandler({ oxy: oxyClient }),
38
+ * );
39
+ *
40
+ * // ioredis
41
+ * const handle = createOxyUserInvalidationHandler({ oxy: oxyClient });
42
+ * await subscriber.subscribe(OXY_USER_INVALIDATION_CHANNEL);
43
+ * subscriber.on('message', (_channel, raw) => handle(raw));
44
+ *
45
+ * Subscribe on EVERY task, not just an elected leader. The SDK cache this sweeps
46
+ * is per-process in-memory, so a leader-only subscriber would leave every other
47
+ * task stale — and leader-gating would add a failure mode (leader down means no
48
+ * invalidation anywhere) to a signal whose whole point is that losing it is
49
+ * merely slow, never wrong.
50
+ *
51
+ * Node-only; exported solely from `@oxy.so/core/server`.
52
+ */
53
+
54
+ import {
55
+ OXY_USER_INVALIDATION_CHANNEL,
56
+ isPublishedOxyUserChangeReason,
57
+ oxyUserInvalidationEventSchema,
58
+ type OxyUserChangeReason,
59
+ type OxyUserInvalidationEvent,
60
+ } from '@oxy.so/contracts';
61
+ import {
62
+ evictOxyIdentityCache,
63
+ type OxyIdentityCacheEvictor,
64
+ } from '../utils/identityCacheSweep';
65
+
66
+ /**
67
+ * The publish surface of a Redis client. Both `ioredis` and `node-redis`
68
+ * satisfy this structurally, so neither library is a dependency here.
69
+ */
70
+ export interface OxyInvalidationPublisher {
71
+ publish(channel: string, message: string): unknown;
72
+ }
73
+
74
+ /**
75
+ * Broadcast that an Oxy user's record changed.
76
+ *
77
+ * Returns `true` when a message was put on the wire and `false` when the reason
78
+ * is not a broadcast one ({@link isPublishedOxyUserChangeReason}) — the latter is
79
+ * a deliberate no-op, not a failure. Suppressing at the publisher rather than
80
+ * letting every subscriber discard matters at bulk-follow scale, where a single
81
+ * call moves up to 200 edges.
82
+ *
83
+ * NEVER THROWS AND NEVER RETURNS A REJECTED PROMISE. This is called from inside
84
+ * cache invalidation, which itself runs after a successful database write on the
85
+ * request path: a publish failure must not turn a completed profile update into
86
+ * a 500. A dropped message costs a consumer its TTL and nothing more.
87
+ *
88
+ * @param publisher - A connected Redis client. Must NOT be a client currently in
89
+ * subscriber mode — Redis forbids `PUBLISH` on a subscribed
90
+ * connection, so pass the publisher half of a pub/sub pair.
91
+ * @param userId - The Oxy user whose record changed.
92
+ * @param reason - How the record changed. See {@link OxyUserChangeReason}.
93
+ * @param onError - Optional diagnostic sink for a failed publish.
94
+ */
95
+ export function publishOxyUserInvalidation(
96
+ publisher: OxyInvalidationPublisher,
97
+ userId: string,
98
+ reason: OxyUserChangeReason,
99
+ onError?: (error: unknown) => void,
100
+ ): boolean {
101
+ if (!userId || !isPublishedOxyUserChangeReason(reason)) {
102
+ return false;
103
+ }
104
+
105
+ const event: OxyUserInvalidationEvent = { userId, reason, at: Date.now() };
106
+
107
+ try {
108
+ const result = publisher.publish(OXY_USER_INVALIDATION_CHANNEL, JSON.stringify(event));
109
+ // node-redis returns a promise; ioredis returns a promise too, but a mocked
110
+ // or synchronous client may return anything. Only attach a rejection handler
111
+ // when there is actually something thenable to reject.
112
+ if (isPromiseLike(result)) {
113
+ Promise.resolve(result).catch((error: unknown) => onError?.(error));
114
+ }
115
+ return true;
116
+ } catch (error) {
117
+ onError?.(error);
118
+ return false;
119
+ }
120
+ }
121
+
122
+ function isPromiseLike(value: unknown): value is PromiseLike<unknown> {
123
+ return (
124
+ typeof value === 'object' &&
125
+ value !== null &&
126
+ typeof (value as { then?: unknown }).then === 'function'
127
+ );
128
+ }
129
+
130
+ /** Options for {@link createOxyUserInvalidationHandler}. */
131
+ export interface OxyUserInvalidationHandlerOptions {
132
+ /**
133
+ * The backend's `OxyServices` instance. When supplied, its GET response cache
134
+ * is swept for the invalidated user — this is the whole reason a backend that
135
+ * has no cache of its own still benefits from subscribing.
136
+ */
137
+ oxy?: OxyIdentityCacheEvictor;
138
+ /**
139
+ * App-specific eviction (e.g. a Redis identity cache the app maintains itself).
140
+ * May be async; a rejection is routed to `onError` and never escapes.
141
+ */
142
+ onInvalidate?: (event: OxyUserInvalidationEvent) => void | Promise<void>;
143
+ /** Diagnostic sink for an unparseable message or a failing `onInvalidate`. */
144
+ onError?: (error: unknown, raw: string) => void;
145
+ }
146
+
147
+ /**
148
+ * Build the message handler for {@link OXY_USER_INVALIDATION_CHANNEL}.
149
+ *
150
+ * The returned function NEVER THROWS and never returns a rejected promise. It
151
+ * runs inside the Redis client's message dispatch, where an exception either
152
+ * takes down the subscriber connection or surfaces as an unhandled rejection —
153
+ * and losing the subscription is strictly worse than losing one message, because
154
+ * it is silent and permanent.
155
+ *
156
+ * A message that fails schema validation is dropped, not retried: the payload is
157
+ * produced by a contract both sides compile against, so a malformed one means a
158
+ * version skew or an unrelated publisher on the channel, neither of which a retry
159
+ * fixes.
160
+ */
161
+ export function createOxyUserInvalidationHandler(
162
+ options: OxyUserInvalidationHandlerOptions = {},
163
+ ): (raw: string) => void {
164
+ const { oxy, onInvalidate, onError } = options;
165
+
166
+ return (raw: string): void => {
167
+ let event: OxyUserInvalidationEvent;
168
+ try {
169
+ const parsed = oxyUserInvalidationEventSchema.safeParse(JSON.parse(raw));
170
+ if (!parsed.success) {
171
+ onError?.(parsed.error, raw);
172
+ return;
173
+ }
174
+ event = parsed.data;
175
+ } catch (error) {
176
+ onError?.(error, raw);
177
+ return;
178
+ }
179
+
180
+ if (oxy) {
181
+ try {
182
+ evictOxyIdentityCache(oxy, event.userId);
183
+ } catch (error) {
184
+ // A cache sweep must never cost us the app-specific eviction below.
185
+ onError?.(error, raw);
186
+ }
187
+ }
188
+
189
+ if (!onInvalidate) return;
190
+ try {
191
+ const result = onInvalidate(event);
192
+ if (isPromiseLike(result)) {
193
+ Promise.resolve(result).catch((error: unknown) => onError?.(error, raw));
194
+ }
195
+ } catch (error) {
196
+ onError?.(error, raw);
197
+ }
198
+ };
199
+ }