@byline/host-tanstack-start 4.19.0 → 5.1.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 (322) hide show
  1. package/dist/admin-shell/chrome/admin-app-bar.d.ts +7 -0
  2. package/dist/admin-shell/chrome/admin-app-bar.js +10 -2
  3. package/dist/admin-shell/chrome/session-change-boundary.d.ts +14 -0
  4. package/dist/admin-shell/chrome/session-change-boundary.js +87 -0
  5. package/dist/admin-shell/chrome/session-change-boundary.test.d.ts +8 -0
  6. package/dist/admin-shell/chrome/sign-in-page.d.ts +2 -1
  7. package/dist/admin-shell/chrome/sign-in-page.js +60 -3
  8. package/dist/admin-shell/collections/edit.d.ts +1 -1
  9. package/dist/admin-shell/collections/edit.js +151 -31
  10. package/dist/admin-shell/collections/history.js +4 -2
  11. package/dist/admin-shell/collections/list.js +30 -6
  12. package/dist/admin-shell/collections/restore-version-modal.d.ts +3 -1
  13. package/dist/admin-shell/collections/restore-version-modal.js +9 -4
  14. package/dist/admin-shell/collections/tree-list.js +16 -6
  15. package/dist/admin-shell/collections/version-history.d.ts +2 -1
  16. package/dist/admin-shell/collections/version-history.js +11 -2
  17. package/dist/admin-shell/document-mutation-notice.d.ts +6 -0
  18. package/dist/admin-shell/document-mutation-notice.js +53 -0
  19. package/dist/admin-shell/document-mutation-state.d.ts +22 -0
  20. package/dist/admin-shell/document-mutation-state.js +71 -0
  21. package/dist/admin-shell/document-mutation-state.test.d.ts +1 -0
  22. package/dist/admin-shell/scheduled-publications/list.js +15 -5
  23. package/dist/admin-shell/singletons/history.js +4 -2
  24. package/dist/admin-shell/singletons/view.d.ts +1 -1
  25. package/dist/admin-shell/singletons/view.js +98 -19
  26. package/dist/integrations/byline-admin-services.js +7 -1
  27. package/dist/integrations/byline-field-services.js +2 -9
  28. package/dist/integrations/client-ip.d.ts +19 -0
  29. package/dist/integrations/client-ip.js +34 -0
  30. package/dist/integrations/session-coordination.d.ts +24 -0
  31. package/dist/integrations/session-coordination.js +85 -0
  32. package/dist/integrations/session-middleware.d.ts +11 -0
  33. package/dist/integrations/session-middleware.js +94 -0
  34. package/dist/integrations/session-request-kind.d.ts +9 -0
  35. package/dist/integrations/session-request-kind.js +10 -0
  36. package/dist/integrations/session-request-middleware.d.ts +9 -0
  37. package/dist/integrations/session-request-middleware.js +7 -0
  38. package/dist/integrations/sign-in-body.d.ts +12 -0
  39. package/dist/integrations/sign-in-body.js +52 -0
  40. package/dist/integrations/sign-in-middleware.d.ts +9 -0
  41. package/dist/integrations/sign-in-middleware.js +10 -0
  42. package/dist/routes/create-admin-layout-route.js +46 -31
  43. package/dist/routes/create-collection-edit-route.js +1 -1
  44. package/dist/routes/create-collection-list-route.d.ts +0 -7
  45. package/dist/routes/create-collection-list-route.js +7 -9
  46. package/dist/routes/create-sign-in-route.js +4 -2
  47. package/dist/routes/create-singleton-route.d.ts +2 -0
  48. package/dist/routes/create-singleton-route.js +4 -3
  49. package/dist/server-fns/admin-account/change-password.d.ts +3 -1
  50. package/dist/server-fns/admin-account/change-password.js +9 -3
  51. package/dist/server-fns/admin-account/get.d.ts +3 -1
  52. package/dist/server-fns/admin-account/get.js +4 -1
  53. package/dist/server-fns/admin-account/index.d.ts +5 -5
  54. package/dist/server-fns/admin-account/update.d.ts +3 -1
  55. package/dist/server-fns/admin-account/update.js +4 -1
  56. package/dist/server-fns/admin-activity/get.d.ts +3 -1
  57. package/dist/server-fns/admin-activity/get.js +4 -1
  58. package/dist/server-fns/admin-permissions/get-role-abilities.d.ts +3 -1
  59. package/dist/server-fns/admin-permissions/get-role-abilities.js +4 -1
  60. package/dist/server-fns/admin-permissions/index.d.ts +4 -4
  61. package/dist/server-fns/admin-permissions/list-registered.d.ts +3 -1
  62. package/dist/server-fns/admin-permissions/list-registered.js +4 -1
  63. package/dist/server-fns/admin-permissions/set-role-abilities.d.ts +3 -1
  64. package/dist/server-fns/admin-permissions/set-role-abilities.js +4 -1
  65. package/dist/server-fns/admin-permissions/who-has.d.ts +3 -1
  66. package/dist/server-fns/admin-permissions/who-has.js +4 -1
  67. package/dist/server-fns/admin-roles/create.d.ts +3 -1
  68. package/dist/server-fns/admin-roles/create.js +4 -1
  69. package/dist/server-fns/admin-roles/delete.d.ts +3 -1
  70. package/dist/server-fns/admin-roles/delete.js +4 -1
  71. package/dist/server-fns/admin-roles/get.d.ts +3 -1
  72. package/dist/server-fns/admin-roles/get.js +4 -1
  73. package/dist/server-fns/admin-roles/index.d.ts +6 -6
  74. package/dist/server-fns/admin-roles/list.d.ts +3 -1
  75. package/dist/server-fns/admin-roles/list.js +4 -1
  76. package/dist/server-fns/admin-roles/reorder.d.ts +3 -1
  77. package/dist/server-fns/admin-roles/reorder.js +4 -1
  78. package/dist/server-fns/admin-roles/update.d.ts +3 -1
  79. package/dist/server-fns/admin-roles/update.js +4 -1
  80. package/dist/server-fns/admin-users/create.d.ts +3 -1
  81. package/dist/server-fns/admin-users/create.js +4 -1
  82. package/dist/server-fns/admin-users/delete.d.ts +3 -1
  83. package/dist/server-fns/admin-users/delete.js +4 -1
  84. package/dist/server-fns/admin-users/disable.d.ts +3 -1
  85. package/dist/server-fns/admin-users/disable.js +4 -1
  86. package/dist/server-fns/admin-users/enable.d.ts +3 -1
  87. package/dist/server-fns/admin-users/enable.js +4 -1
  88. package/dist/server-fns/admin-users/get-user-roles.d.ts +3 -1
  89. package/dist/server-fns/admin-users/get-user-roles.js +4 -1
  90. package/dist/server-fns/admin-users/get.d.ts +3 -1
  91. package/dist/server-fns/admin-users/get.js +4 -1
  92. package/dist/server-fns/admin-users/index.d.ts +10 -10
  93. package/dist/server-fns/admin-users/list.d.ts +3 -1
  94. package/dist/server-fns/admin-users/list.js +4 -1
  95. package/dist/server-fns/admin-users/set-password.d.ts +3 -1
  96. package/dist/server-fns/admin-users/set-password.js +4 -1
  97. package/dist/server-fns/admin-users/set-user-roles.d.ts +3 -1
  98. package/dist/server-fns/admin-users/set-user-roles.js +4 -1
  99. package/dist/server-fns/admin-users/update.d.ts +3 -1
  100. package/dist/server-fns/admin-users/update.js +4 -1
  101. package/dist/server-fns/ai/execute.d.ts +3 -1
  102. package/dist/server-fns/ai/execute.js +4 -1
  103. package/dist/server-fns/ai/index.d.ts +1 -1
  104. package/dist/server-fns/analytics/maintenance.d.ts +6 -2
  105. package/dist/server-fns/analytics/maintenance.js +7 -2
  106. package/dist/server-fns/analytics/queries.d.ts +18 -6
  107. package/dist/server-fns/analytics/queries.js +19 -6
  108. package/dist/server-fns/auth/current-user.d.ts +1 -0
  109. package/dist/server-fns/auth/current-user.js +5 -2
  110. package/dist/server-fns/auth/renew.d.ts +13 -0
  111. package/dist/server-fns/auth/renew.js +45 -0
  112. package/dist/server-fns/auth/sign-in.d.ts +2 -1
  113. package/dist/server-fns/auth/sign-in.js +88 -17
  114. package/dist/server-fns/auth/sign-out.d.ts +3 -1
  115. package/dist/server-fns/auth/sign-out.js +38 -6
  116. package/dist/server-fns/collections/audit.d.ts +3 -1
  117. package/dist/server-fns/collections/audit.js +4 -1
  118. package/dist/server-fns/collections/copy-to-locale.d.ts +5 -1
  119. package/dist/server-fns/collections/copy-to-locale.js +7 -2
  120. package/dist/server-fns/collections/create.d.ts +4 -1
  121. package/dist/server-fns/collections/create.js +8 -3
  122. package/dist/server-fns/collections/delete-locale.d.ts +5 -1
  123. package/dist/server-fns/collections/delete-locale.js +7 -2
  124. package/dist/server-fns/collections/delete-outcome.d.ts +7 -3
  125. package/dist/server-fns/collections/delete-outcome.js +4 -0
  126. package/dist/server-fns/collections/delete.d.ts +5 -1
  127. package/dist/server-fns/collections/delete.js +8 -3
  128. package/dist/server-fns/collections/duplicate.d.ts +5 -1
  129. package/dist/server-fns/collections/duplicate.js +8 -3
  130. package/dist/server-fns/collections/get.d.ts +1 -0
  131. package/dist/server-fns/collections/get.js +12 -6
  132. package/dist/server-fns/collections/history.d.ts +3 -1
  133. package/dist/server-fns/collections/history.js +4 -1
  134. package/dist/server-fns/collections/index.d.ts +21 -21
  135. package/dist/server-fns/collections/list.d.ts +3 -1
  136. package/dist/server-fns/collections/list.js +15 -4
  137. package/dist/server-fns/collections/reindex.d.ts +3 -1
  138. package/dist/server-fns/collections/reindex.js +4 -1
  139. package/dist/server-fns/collections/reorder.d.ts +10 -2
  140. package/dist/server-fns/collections/reorder.js +23 -76
  141. package/dist/server-fns/collections/restore-version.d.ts +6 -1
  142. package/dist/server-fns/collections/restore-version.js +8 -2
  143. package/dist/server-fns/collections/save-outcome.d.ts +1 -0
  144. package/dist/server-fns/collections/save-outcome.js +1 -0
  145. package/dist/server-fns/collections/scheduled-publication.d.ts +64 -7
  146. package/dist/server-fns/collections/scheduled-publication.js +42 -21
  147. package/dist/server-fns/collections/set-list-view-preference.d.ts +3 -1
  148. package/dist/server-fns/collections/set-list-view-preference.js +4 -1
  149. package/dist/server-fns/collections/stats.js +4 -1
  150. package/dist/server-fns/collections/status.d.ts +14 -2
  151. package/dist/server-fns/collections/status.js +18 -5
  152. package/dist/server-fns/collections/tree-mutation.d.ts +7 -6
  153. package/dist/server-fns/collections/tree-mutation.js +17 -5
  154. package/dist/server-fns/collections/tree.d.ts +29 -6
  155. package/dist/server-fns/collections/tree.js +32 -17
  156. package/dist/server-fns/collections/update.d.ts +18 -6
  157. package/dist/server-fns/collections/update.js +39 -20
  158. package/dist/server-fns/collections/upload.d.ts +11 -1
  159. package/dist/server-fns/collections/upload.js +6 -2
  160. package/dist/server-fns/document-mutation-errors.d.ts +5 -0
  161. package/dist/server-fns/document-mutation-errors.js +50 -0
  162. package/dist/server-fns/editable-revision.d.ts +9 -0
  163. package/dist/server-fns/editable-revision.js +11 -0
  164. package/dist/server-fns/i18n/set-locale.d.ts +3 -1
  165. package/dist/server-fns/i18n/set-locale.js +18 -3
  166. package/dist/server-fns/preview/disable.d.ts +3 -1
  167. package/dist/server-fns/preview/disable.js +4 -1
  168. package/dist/server-fns/preview/enable.d.ts +3 -1
  169. package/dist/server-fns/preview/enable.js +4 -1
  170. package/dist/server-fns/singleton-document-read.d.ts +1 -1
  171. package/dist/server-fns/singleton-document-read.js +7 -3
  172. package/dist/server-fns/singletons/cancel-scheduled-publish.d.ts +9 -2
  173. package/dist/server-fns/singletons/cancel-scheduled-publish.js +7 -1
  174. package/dist/server-fns/singletons/change-status.d.ts +5 -1
  175. package/dist/server-fns/singletons/change-status.js +7 -1
  176. package/dist/server-fns/singletons/confirm-scheduled-publish.d.ts +8 -2
  177. package/dist/server-fns/singletons/confirm-scheduled-publish.js +7 -2
  178. package/dist/server-fns/singletons/copy-to-locale.d.ts +5 -1
  179. package/dist/server-fns/singletons/copy-to-locale.js +7 -2
  180. package/dist/server-fns/singletons/find-by-version.d.ts +3 -1
  181. package/dist/server-fns/singletons/find-by-version.js +4 -1
  182. package/dist/server-fns/singletons/get-scheduled-publish.d.ts +3 -1
  183. package/dist/server-fns/singletons/get-scheduled-publish.js +4 -1
  184. package/dist/server-fns/singletons/get.d.ts +6 -4
  185. package/dist/server-fns/singletons/get.js +4 -1
  186. package/dist/server-fns/singletons/history.d.ts +3 -1
  187. package/dist/server-fns/singletons/history.js +4 -1
  188. package/dist/server-fns/singletons/restore-version.d.ts +5 -1
  189. package/dist/server-fns/singletons/restore-version.js +9 -3
  190. package/dist/server-fns/singletons/schedule-publish.d.ts +8 -2
  191. package/dist/server-fns/singletons/schedule-publish.js +7 -2
  192. package/dist/server-fns/singletons/unpublish.d.ts +5 -1
  193. package/dist/server-fns/singletons/unpublish.js +7 -1
  194. package/dist/server-fns/singletons/update.d.ts +6 -4
  195. package/dist/server-fns/singletons/update.js +8 -4
  196. package/dist/server-fns/structural-receipt.d.ts +4 -0
  197. package/dist/server-fns/structural-receipt.js +31 -0
  198. package/package.json +20 -20
  199. package/src/admin-shell/chrome/admin-app-bar.tsx +11 -6
  200. package/src/admin-shell/chrome/session-change-boundary.test.tsx +78 -0
  201. package/src/admin-shell/chrome/session-change-boundary.tsx +94 -0
  202. package/src/admin-shell/chrome/sign-in-page.tsx +68 -3
  203. package/src/admin-shell/collections/edit.tsx +173 -54
  204. package/src/admin-shell/collections/history.tsx +11 -2
  205. package/src/admin-shell/collections/list.tsx +22 -6
  206. package/src/admin-shell/collections/restore-version-modal.tsx +12 -3
  207. package/src/admin-shell/collections/save-navigation.test.tsx +156 -8
  208. package/src/admin-shell/collections/tree-list.tsx +16 -6
  209. package/src/admin-shell/collections/version-history.tsx +13 -3
  210. package/src/admin-shell/document-mutation-notice.tsx +45 -0
  211. package/src/admin-shell/document-mutation-state.test.tsx +146 -0
  212. package/src/admin-shell/document-mutation-state.ts +95 -0
  213. package/src/admin-shell/scheduled-publications/list.tsx +16 -6
  214. package/src/admin-shell/singletons/history.test.tsx +3 -1
  215. package/src/admin-shell/singletons/history.tsx +10 -2
  216. package/src/admin-shell/singletons/view.test.tsx +59 -18
  217. package/src/admin-shell/singletons/view.tsx +110 -29
  218. package/src/integrations/byline-admin-services.ts +7 -1
  219. package/src/integrations/byline-field-services.ts +2 -3
  220. package/src/integrations/client-ip.test.node.ts +45 -0
  221. package/src/integrations/client-ip.ts +54 -0
  222. package/src/integrations/framework-fetch-shape.test.node.ts +56 -0
  223. package/src/integrations/session-client.test.node.ts +121 -0
  224. package/src/integrations/session-coordination.test.node.ts +77 -0
  225. package/src/integrations/session-coordination.ts +96 -0
  226. package/src/integrations/session-middleware.test.node.ts +87 -0
  227. package/src/integrations/session-middleware.ts +107 -0
  228. package/src/integrations/session-request-kind.ts +18 -0
  229. package/src/integrations/session-request-middleware.ts +17 -0
  230. package/src/integrations/sign-in-body.test.node.ts +59 -0
  231. package/src/integrations/sign-in-body.ts +50 -0
  232. package/src/integrations/sign-in-middleware.ts +24 -0
  233. package/src/routes/create-admin-layout-route.tsx +48 -30
  234. package/src/routes/create-collection-edit-route.tsx +1 -0
  235. package/src/routes/create-collection-list-route.tsx +13 -2
  236. package/src/routes/create-sign-in-route.tsx +10 -2
  237. package/src/routes/create-singleton-route.test.node.ts +10 -2
  238. package/src/routes/create-singleton-route.tsx +6 -4
  239. package/src/server-fns/admin-account/change-password.test.node.ts +69 -0
  240. package/src/server-fns/admin-account/change-password.ts +13 -2
  241. package/src/server-fns/admin-account/get.ts +2 -0
  242. package/src/server-fns/admin-account/update.ts +2 -0
  243. package/src/server-fns/admin-activity/get.ts +2 -0
  244. package/src/server-fns/admin-permissions/get-role-abilities.ts +2 -0
  245. package/src/server-fns/admin-permissions/list-registered.ts +5 -4
  246. package/src/server-fns/admin-permissions/set-role-abilities.ts +2 -0
  247. package/src/server-fns/admin-permissions/who-has.ts +2 -0
  248. package/src/server-fns/admin-roles/create.ts +2 -0
  249. package/src/server-fns/admin-roles/delete.ts +2 -0
  250. package/src/server-fns/admin-roles/get.ts +2 -0
  251. package/src/server-fns/admin-roles/list.ts +5 -4
  252. package/src/server-fns/admin-roles/reorder.ts +2 -0
  253. package/src/server-fns/admin-roles/update.ts +2 -0
  254. package/src/server-fns/admin-users/create.ts +2 -0
  255. package/src/server-fns/admin-users/delete.ts +2 -0
  256. package/src/server-fns/admin-users/disable.ts +2 -0
  257. package/src/server-fns/admin-users/enable.ts +2 -0
  258. package/src/server-fns/admin-users/get-user-roles.ts +2 -0
  259. package/src/server-fns/admin-users/get.ts +2 -0
  260. package/src/server-fns/admin-users/list.ts +2 -0
  261. package/src/server-fns/admin-users/set-password.ts +2 -0
  262. package/src/server-fns/admin-users/set-user-roles.ts +2 -0
  263. package/src/server-fns/admin-users/update.ts +2 -0
  264. package/src/server-fns/ai/execute.ts +3 -0
  265. package/src/server-fns/analytics/maintenance.ts +4 -0
  266. package/src/server-fns/analytics/queries.ts +14 -8
  267. package/src/server-fns/auth/current-user.ts +7 -3
  268. package/src/server-fns/auth/renew.test.node.ts +85 -0
  269. package/src/server-fns/auth/renew.ts +54 -0
  270. package/src/server-fns/auth/sign-in.test.node.ts +114 -0
  271. package/src/server-fns/auth/sign-in.ts +80 -27
  272. package/src/server-fns/auth/sign-out.test.node.ts +85 -0
  273. package/src/server-fns/auth/sign-out.ts +44 -19
  274. package/src/server-fns/collections/audit.ts +2 -0
  275. package/src/server-fns/collections/copy-to-locale.ts +43 -36
  276. package/src/server-fns/collections/create.ts +56 -50
  277. package/src/server-fns/collections/delete-locale.ts +43 -32
  278. package/src/server-fns/collections/delete-outcome.test.node.ts +16 -0
  279. package/src/server-fns/collections/delete-outcome.ts +27 -16
  280. package/src/server-fns/collections/delete.ts +46 -38
  281. package/src/server-fns/collections/duplicate.ts +40 -32
  282. package/src/server-fns/collections/get.ts +11 -4
  283. package/src/server-fns/collections/history.ts +2 -0
  284. package/src/server-fns/collections/list.ts +10 -3
  285. package/src/server-fns/collections/reindex.ts +2 -0
  286. package/src/server-fns/collections/reorder.ts +34 -124
  287. package/src/server-fns/collections/restore-version.ts +49 -39
  288. package/src/server-fns/collections/revision-input.test.node.ts +122 -0
  289. package/src/server-fns/collections/save-outcome.test.node.ts +3 -0
  290. package/src/server-fns/collections/save-outcome.ts +2 -0
  291. package/src/server-fns/collections/scheduled-publication.ts +149 -115
  292. package/src/server-fns/collections/set-list-view-preference.ts +2 -0
  293. package/src/server-fns/collections/stats.ts +2 -0
  294. package/src/server-fns/collections/status.ts +89 -64
  295. package/src/server-fns/collections/tree-mutation.test.node.ts +87 -29
  296. package/src/server-fns/collections/tree-mutation.ts +28 -11
  297. package/src/server-fns/collections/tree.ts +156 -122
  298. package/src/server-fns/collections/update.ts +113 -86
  299. package/src/server-fns/collections/upload-singleton.test.node.ts +10 -7
  300. package/src/server-fns/collections/upload.ts +91 -77
  301. package/src/server-fns/document-mutation-errors.ts +67 -0
  302. package/src/server-fns/document-mutation-transport.test.node.ts +211 -0
  303. package/src/server-fns/editable-revision.test.node.ts +23 -0
  304. package/src/server-fns/editable-revision.ts +18 -0
  305. package/src/server-fns/i18n/set-locale.ts +19 -3
  306. package/src/server-fns/preview/disable.ts +8 -4
  307. package/src/server-fns/preview/enable.ts +9 -5
  308. package/src/server-fns/singleton-document-read.ts +7 -3
  309. package/src/server-fns/singletons/cancel-scheduled-publish.ts +12 -3
  310. package/src/server-fns/singletons/change-status.ts +12 -3
  311. package/src/server-fns/singletons/confirm-scheduled-publish.ts +14 -6
  312. package/src/server-fns/singletons/copy-to-locale.ts +22 -15
  313. package/src/server-fns/singletons/find-by-version.ts +2 -0
  314. package/src/server-fns/singletons/get-scheduled-publish.ts +2 -0
  315. package/src/server-fns/singletons/get.ts +5 -3
  316. package/src/server-fns/singletons/history.ts +2 -0
  317. package/src/server-fns/singletons/restore-version.ts +19 -12
  318. package/src/server-fns/singletons/schedule-publish.ts +20 -7
  319. package/src/server-fns/singletons/singletons.test.node.ts +69 -25
  320. package/src/server-fns/singletons/unpublish.ts +12 -3
  321. package/src/server-fns/singletons/update.ts +23 -16
  322. package/src/server-fns/structural-receipt.ts +41 -0
@@ -5,10 +5,14 @@
5
5
  *
6
6
  * Copyright (c) Infonomic Company Limited
7
7
  */
8
- export declare const restoreSingletonVersion: import("@tanstack/react-start").RequiredFetcher<undefined, (input: {
8
+ export declare const restoreSingletonVersion: import("@tanstack/react-start").RequiredFetcher<readonly [import("@tanstack/react-start").FunctionMiddlewareAfterServer<{}, unknown, undefined, undefined, {
9
+ bylineExpectedSessionId: string | null;
10
+ }, undefined, undefined>], (input: {
11
+ expectedRevision: number;
9
12
  singleton: string;
10
13
  versionId: string;
11
14
  }) => {
15
+ expectedRevision: number;
12
16
  singleton: string;
13
17
  versionId: string;
14
18
  }, Promise<import("@byline/core").SingletonSaveResult>>;
@@ -1,16 +1,22 @@
1
1
  import { createServerFn } from "@tanstack/react-start";
2
2
  import { getAdminBylineClient } from "@byline/client/server";
3
+ import { adminSessionMiddleware } from "../../integrations/session-middleware.js";
3
4
  import { toCommittedDocumentHookFailureResponse } from "../collections/save-outcome.js";
5
+ import { withDocumentMutationErrors } from "../document-mutation-errors.js";
4
6
  import { serialise } from "../serialise.js";
5
7
  const restoreSingletonVersion = createServerFn({
6
8
  method: 'POST'
7
- }).validator((input)=>input).handler(async ({ data })=>{
9
+ }).middleware([
10
+ adminSessionMiddleware
11
+ ]).validator((input)=>input).handler(withDocumentMutationErrors(async ({ data })=>{
8
12
  try {
9
- return serialise(await getAdminBylineClient().singleton(data.singleton).restoreVersion(data.versionId));
13
+ return serialise(await getAdminBylineClient().singleton(data.singleton).restoreVersion(data.versionId, {
14
+ expectedRevision: data.expectedRevision
15
+ }));
10
16
  } catch (error) {
11
17
  const committedFailure = toCommittedDocumentHookFailureResponse(error);
12
18
  if (null != committedFailure) return committedFailure;
13
19
  throw error;
14
20
  }
15
- });
21
+ }));
16
22
  export { restoreSingletonVersion };
@@ -5,12 +5,18 @@
5
5
  *
6
6
  * Copyright (c) Infonomic Company Limited
7
7
  */
8
- export declare const scheduleSingletonPublish: import("@tanstack/react-start").RequiredFetcher<undefined, (input: {
8
+ export declare const scheduleSingletonPublish: import("@tanstack/react-start").RequiredFetcher<readonly [import("@tanstack/react-start").FunctionMiddlewareAfterServer<{}, unknown, undefined, undefined, {
9
+ bylineExpectedSessionId: string | null;
10
+ }, undefined, undefined>], (input: {
11
+ expectedRevision: number;
9
12
  singleton: string;
10
13
  publishAt: string;
11
14
  expectedVersionId: string;
12
15
  }) => {
16
+ expectedRevision: number;
13
17
  singleton: string;
14
18
  publishAt: string;
15
19
  expectedVersionId: string;
16
- }, Promise<import("@byline/client").DocumentPublishScheduleInfo>>;
20
+ }, Promise<import("@byline/client").DocumentPublishScheduleInfo & {
21
+ revision: number;
22
+ }>>;
@@ -1,10 +1,15 @@
1
1
  import { createServerFn } from "@tanstack/react-start";
2
2
  import { getAdminBylineClient } from "@byline/client/server";
3
+ import { adminSessionMiddleware } from "../../integrations/session-middleware.js";
4
+ import { withDocumentMutationErrors } from "../document-mutation-errors.js";
3
5
  import { serialise } from "../serialise.js";
4
6
  const scheduleSingletonPublish = createServerFn({
5
7
  method: 'POST'
6
- }).validator((input)=>input).handler(async ({ data })=>serialise(await getAdminBylineClient().singleton(data.singleton).schedulePublish({
8
+ }).middleware([
9
+ adminSessionMiddleware
10
+ ]).validator((input)=>input).handler(withDocumentMutationErrors(async ({ data })=>serialise(await getAdminBylineClient().singleton(data.singleton).schedulePublish({
7
11
  publishAt: data.publishAt,
12
+ expectedRevision: data.expectedRevision,
8
13
  expectedVersionId: data.expectedVersionId
9
- })));
14
+ }))));
10
15
  export { scheduleSingletonPublish };
@@ -5,8 +5,12 @@
5
5
  *
6
6
  * Copyright (c) Infonomic Company Limited
7
7
  */
8
- export declare const unpublishSingleton: import("@tanstack/react-start").RequiredFetcher<undefined, (input: {
8
+ export declare const unpublishSingleton: import("@tanstack/react-start").RequiredFetcher<readonly [import("@tanstack/react-start").FunctionMiddlewareAfterServer<{}, unknown, undefined, undefined, {
9
+ bylineExpectedSessionId: string | null;
10
+ }, undefined, undefined>], (input: {
11
+ expectedRevision: number;
9
12
  singleton: string;
10
13
  }) => {
14
+ expectedRevision: number;
11
15
  singleton: string;
12
16
  }, Promise<import("@byline/core").UnpublishResult>>;
@@ -1,7 +1,13 @@
1
1
  import { createServerFn } from "@tanstack/react-start";
2
2
  import { getAdminBylineClient } from "@byline/client/server";
3
+ import { adminSessionMiddleware } from "../../integrations/session-middleware.js";
4
+ import { withDocumentMutationErrors } from "../document-mutation-errors.js";
3
5
  import { serialise } from "../serialise.js";
4
6
  const unpublishSingleton = createServerFn({
5
7
  method: 'POST'
6
- }).validator((input)=>input).handler(async ({ data })=>serialise(await getAdminBylineClient().singleton(data.singleton).unpublish()));
8
+ }).middleware([
9
+ adminSessionMiddleware
10
+ ]).validator((input)=>input).handler(withDocumentMutationErrors(async ({ data })=>serialise(await getAdminBylineClient().singleton(data.singleton).unpublish({
11
+ expectedRevision: data.expectedRevision
12
+ }))));
7
13
  export { unpublishSingleton };
@@ -5,10 +5,12 @@
5
5
  *
6
6
  * Copyright (c) Infonomic Company Limited
7
7
  */
8
- export interface UpdateSingletonInput {
8
+ import type { SingletonSavePrecondition } from '@byline/core';
9
+ export type UpdateSingletonInput = SingletonSavePrecondition & {
9
10
  singleton: string;
10
11
  data: Record<string, any>;
11
12
  locale?: string;
12
- expectedVersionId?: string;
13
- }
14
- export declare const updateSingleton: import("@tanstack/react-start").RequiredFetcher<undefined, (input: UpdateSingletonInput) => UpdateSingletonInput, Promise<import("@byline/core").SingletonSaveResult>>;
13
+ };
14
+ export declare const updateSingleton: import("@tanstack/react-start").RequiredFetcher<readonly [import("@tanstack/react-start").FunctionMiddlewareAfterServer<{}, unknown, undefined, undefined, {
15
+ bylineExpectedSessionId: string | null;
16
+ }, undefined, undefined>], (input: UpdateSingletonInput) => UpdateSingletonInput, Promise<import("@byline/core").UpdateSingletonResult>>;
@@ -1,15 +1,19 @@
1
1
  import { createServerFn } from "@tanstack/react-start";
2
2
  import { getAdminBylineClient } from "@byline/client/server";
3
+ import { adminSessionMiddleware } from "../../integrations/session-middleware.js";
3
4
  import { toCommittedDocumentHookFailureResponse } from "../collections/save-outcome.js";
5
+ import { withDocumentMutationErrors } from "../document-mutation-errors.js";
4
6
  import { serialise } from "../serialise.js";
5
7
  const updateSingleton = createServerFn({
6
8
  method: 'POST'
7
- }).validator((input)=>input).handler(async ({ data })=>{
8
- const { singleton, data: fields, locale, expectedVersionId } = data;
9
+ }).middleware([
10
+ adminSessionMiddleware
11
+ ]).validator((input)=>input).handler(withDocumentMutationErrors(async ({ data })=>{
12
+ const { singleton, data: fields, locale, ...precondition } = data;
9
13
  try {
10
14
  const result = await getAdminBylineClient().singleton(singleton).update(fields, {
11
15
  locale,
12
- expectedVersionId
16
+ ...precondition
13
17
  });
14
18
  return serialise(result);
15
19
  } catch (error) {
@@ -17,5 +21,5 @@ const updateSingleton = createServerFn({
17
21
  if (null != committedFailure) return committedFailure;
18
22
  throw error;
19
23
  }
20
- });
24
+ }));
21
25
  export { updateSingleton };
@@ -0,0 +1,4 @@
1
+ import { type StructuralMutationReceipt } from '@byline/core';
2
+ /** Copy only the authorized receipt fields emitted by the lifecycle service. */
3
+ export declare function structuralReceipt(value: unknown): StructuralMutationReceipt | null;
4
+ export declare function committedTreeReceipt(error: unknown): StructuralMutationReceipt | null;
@@ -0,0 +1,31 @@
1
+ import { ErrorCodes, isDocumentRevision } from "@byline/core";
2
+ function structuralReceipt(value) {
3
+ if ('object' != typeof value || null === value) return null;
4
+ const documentId = Reflect.get(value, 'documentId');
5
+ const revision = Reflect.get(value, 'revision');
6
+ const suspended = Reflect.get(value, 'scheduledPublicationsNeedReconfirmation');
7
+ const affected = Reflect.get(value, 'affectedDocuments');
8
+ if ('string' != typeof documentId || !isDocumentRevision(revision) || 'boolean' != typeof suspended || !Array.isArray(affected)) return null;
9
+ const affectedDocuments = [];
10
+ for (const row of affected){
11
+ if ('object' != typeof row || null === row) return null;
12
+ const id = Reflect.get(row, 'documentId');
13
+ const token = Reflect.get(row, 'revision');
14
+ if ('string' != typeof id || !isDocumentRevision(token)) return null;
15
+ affectedDocuments.push({
16
+ documentId: id,
17
+ revision: token
18
+ });
19
+ }
20
+ return {
21
+ documentId,
22
+ revision,
23
+ affectedDocuments,
24
+ scheduledPublicationsNeedReconfirmation: suspended
25
+ };
26
+ }
27
+ function committedTreeReceipt(error) {
28
+ if ('object' != typeof error || null === error || Reflect.get(error, 'code') !== ErrorCodes.TREE_HOOK_COMMITTED) return null;
29
+ return structuralReceipt(Reflect.get(error, 'details'));
30
+ }
31
+ export { committedTreeReceipt, structuralReceipt };
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "private": false,
4
4
  "type": "module",
5
5
  "license": "MPL-2.0",
6
- "version": "4.19.0",
6
+ "version": "5.1.0",
7
7
  "engines": {
8
8
  "node": ">=20.9.0"
9
9
  },
@@ -124,16 +124,16 @@
124
124
  "clsx": "^2.1.1",
125
125
  "react-json-view-lite": "^2.5.0",
126
126
  "react-swipeable": "^7.0.2",
127
- "zod": "^4.4.3",
128
- "@byline/admin": "4.19.0",
129
- "@byline/analytics": "4.19.0",
130
- "@byline/analytics-agent": "4.19.0",
131
- "@byline/ai": "4.19.0",
132
- "@byline/core": "4.19.0",
133
- "@byline/client": "4.19.0",
134
- "@byline/auth": "4.19.0",
135
- "@byline/i18n": "4.19.0",
136
- "@byline/ui": "4.19.0"
127
+ "zod": "^4.5.4",
128
+ "@byline/admin": "5.1.0",
129
+ "@byline/analytics-agent": "5.1.0",
130
+ "@byline/analytics": "5.1.0",
131
+ "@byline/client": "5.1.0",
132
+ "@byline/ai": "5.1.0",
133
+ "@byline/auth": "5.1.0",
134
+ "@byline/core": "5.1.0",
135
+ "@byline/i18n": "5.1.0",
136
+ "@byline/ui": "5.1.0"
137
137
  },
138
138
  "peerDependencies": {
139
139
  "@tanstack/react-router": "^1.167.0",
@@ -143,15 +143,15 @@
143
143
  "vite": "^8.0.0"
144
144
  },
145
145
  "devDependencies": {
146
- "@biomejs/biome": "2.5.10",
146
+ "@biomejs/biome": "2.5.12",
147
147
  "@rsbuild/plugin-react": "^2.1.0",
148
- "@rslib/core": "^0.23.2",
149
- "@tanstack/react-router": "^1.170.32",
150
- "@tanstack/react-start": "^1.168.49",
151
- "@types/node": "^26.4.0",
148
+ "@rslib/core": "^1.0.0",
149
+ "@tanstack/react-router": "^1.170.33",
150
+ "@tanstack/react-start": "^1.168.50",
151
+ "@types/node": "^26.5.0",
152
152
  "@types/react": "19.2.18",
153
- "@types/react-dom": "19.2.5",
154
- "@vitejs/plugin-react": "^6.1.0",
153
+ "@types/react-dom": "19.2.7",
154
+ "@vitejs/plugin-react": "^6.1.1",
155
155
  "jsdom": "^30.0.1",
156
156
  "npm-run-all": "^4.1.5",
157
157
  "react": "^19.2.8",
@@ -159,7 +159,7 @@
159
159
  "typescript": "^7.0.2",
160
160
  "typescript-plugin-css-modules": "^5.2.0",
161
161
  "vite": "^8.2.2",
162
- "vitest": "^4.1.11"
162
+ "vitest": "^5.0.0"
163
163
  },
164
164
  "publishConfig": {
165
165
  "access": "public",
@@ -174,7 +174,7 @@
174
174
  "build": "run-s build:*",
175
175
  "build:clean": "node scripts/clean.js dist build",
176
176
  "build:code": "rslib build",
177
- "typecheck": "tsc --noEmit",
177
+ "typecheck": "tsc --noEmit && tsc --noEmit -p tsconfig.revision-tests.json",
178
178
  "clean": "node scripts/clean.js node_modules dist build types .turbo",
179
179
  "lint": "biome check --write --unsafe --diagnostic-level=error",
180
180
  "test": "vitest run --mode=jsdom && vitest run --mode=node",
@@ -1,5 +1,3 @@
1
- 'use client'
2
-
3
1
  /**
4
2
  * This Source Code is subject to the terms of the Mozilla Public
5
3
  * License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -22,6 +20,8 @@ import { Branding } from './branding.js'
22
20
  import { Breadcrumbs } from './breadcrumbs/breadcrumbs.js'
23
21
  import { useBreadcrumbs } from './breadcrumbs/breadcrumbs-provider.js'
24
22
 
23
+ ;('use client')
24
+
25
25
  interface AdminAppBarProps {
26
26
  user: CurrentAdminUser
27
27
  }
@@ -37,16 +37,18 @@ export function AdminAppBar({ user }: AdminAppBarProps) {
37
37
  const { breadCrumbSettings } = useBreadcrumbs()
38
38
  const { t } = useTranslation('byline-admin')
39
39
  const [signingOut, setSigningOut] = useState(false)
40
+ const [signOutError, setSignOutError] = useState(false)
40
41
 
41
42
  async function handleSignOut() {
42
43
  if (signingOut) return
43
44
  setSigningOut(true)
45
+ setSignOutError(false)
44
46
  try {
45
47
  await adminSignOut()
46
- } catch (err) {
47
- // Even on transport failure, the server-side handler clears the
48
- // cookies best-effort. Navigate to sign-in regardless.
49
- console.warn('sign-out request failed', err)
48
+ } catch {
49
+ setSignOutError(true)
50
+ setSigningOut(false)
51
+ return
50
52
  }
51
53
  // Full-page navigation so the admin guard re-runs with cleared cookies.
52
54
  window.location.assign(getSignInRoutePath())
@@ -71,6 +73,9 @@ export function AdminAppBar({ user }: AdminAppBarProps) {
71
73
  {displayNameFor(user)}
72
74
  </span>
73
75
  </span>
76
+ {signOutError && (
77
+ <span role="alert">Sign-out could not be confirmed. Please try again.</span>
78
+ )}
74
79
  <Button size="xs" intent="secondary" onClick={handleSignOut} disabled={signingOut}>
75
80
  {signingOut ? t('common.actions.signingOut') : t('common.actions.signOut')}
76
81
  </Button>
@@ -0,0 +1,78 @@
1
+ /**
2
+ * This Source Code is subject to the terms of the Mozilla Public
3
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
4
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5
+ *
6
+ * Copyright (c) Infonomic Company Limited
7
+ */
8
+
9
+ import { act } from 'react'
10
+
11
+ import { createRoot, type Root } from 'react-dom/client'
12
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
13
+
14
+ const mocks = vi.hoisted(() => ({ current: vi.fn() }))
15
+ vi.mock('../../server-fns/auth/current-user.js', () => ({ getCurrentAdminUser: mocks.current }))
16
+ vi.mock('../../routes/sign-in-path.js', () => ({ getSignInRoutePath: () => '/sign-in' }))
17
+
18
+ import { acceptSession, expectedSession } from '../../integrations/session-coordination.js'
19
+ import { SessionChangeBoundary } from './session-change-boundary.js'
20
+
21
+ const user = (sessionId: string, email: string) => ({
22
+ sessionId,
23
+ email,
24
+ id: email,
25
+ given_name: null,
26
+ family_name: null,
27
+ is_super_admin: false,
28
+ abilities: [],
29
+ })
30
+ let root: Root
31
+ let container: HTMLDivElement
32
+ beforeEach(() => {
33
+ vi.clearAllMocks()
34
+ vi.stubGlobal('IS_REACT_ACT_ENVIRONMENT', true)
35
+ vi.stubGlobal('BroadcastChannel', undefined)
36
+ window.sessionStorage.clear()
37
+ acceptSession('expected-B')
38
+ container = document.createElement('div')
39
+ document.body.appendChild(container)
40
+ root = createRoot(container)
41
+ })
42
+ afterEach(() => {
43
+ act(() => root.unmount())
44
+ container.remove()
45
+ vi.unstubAllGlobals()
46
+ })
47
+ async function render() {
48
+ await act(async () => {
49
+ root.render(
50
+ <SessionChangeBoundary user={user('late-A', 'x@example.test')}>
51
+ <button type="button">Publish</button>
52
+ </SessionChangeBoundary>
53
+ )
54
+ })
55
+ }
56
+ describe('session-change acknowledgement', () => {
57
+ it('blocks business UI on the first post-sign-in render when cookies select a different login', async () => {
58
+ mocks.current.mockResolvedValue(user('late-A', 'x@example.test'))
59
+ await render()
60
+ expect(container.textContent).not.toContain('Publish')
61
+ expect(container.querySelector('[role="alertdialog"]')).not.toBeNull()
62
+ expect(container.textContent).toContain('Active account: x@example.test')
63
+ expect(expectedSession()).toBe('expected-B')
64
+ })
65
+ it('requires renewed acknowledgement when the server account changes again', async () => {
66
+ mocks.current.mockResolvedValue(user('late-A', 'x@example.test'))
67
+ await render()
68
+ mocks.current.mockResolvedValue(user('new-C', 'z@example.test'))
69
+ await act(async () => {
70
+ container.querySelector('button')?.click()
71
+ })
72
+ expect(container.textContent).toContain(
73
+ 'The active session changed again. Review the account before continuing.'
74
+ )
75
+ expect(expectedSession()).toBe('expected-B')
76
+ expect(container.textContent).not.toContain('Publish')
77
+ })
78
+ })
@@ -0,0 +1,94 @@
1
+ /**
2
+ * This Source Code is subject to the terms of the Mozilla Public
3
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
4
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5
+ *
6
+ * Copyright (c) Infonomic Company Limited
7
+ */
8
+
9
+ import { type ReactNode, useEffect, useState, useSyncExternalStore } from 'react'
10
+
11
+ import {
12
+ acceptSession,
13
+ expectedSession,
14
+ flagSessionChanged,
15
+ observeSession,
16
+ sessionEpoch,
17
+ sessionIsChanged,
18
+ startSessionNotifications,
19
+ subscribeSessionChange,
20
+ } from '../../integrations/session-coordination.js'
21
+ import { getSignInRoutePath } from '../../routes/sign-in-path.js'
22
+ import { type CurrentAdminUser, getCurrentAdminUser } from '../../server-fns/auth/current-user.js'
23
+
24
+ /** Blocks the page until the user explicitly acknowledges freshly verified login identity. */
25
+ export function SessionChangeBoundary({
26
+ user,
27
+ children,
28
+ }: {
29
+ user?: CurrentAdminUser
30
+ children: ReactNode
31
+ }) {
32
+ const epoch = useSyncExternalStore(subscribeSessionChange, sessionEpoch, () => 0)
33
+ const changed = sessionIsChanged()
34
+ const [current, setCurrent] = useState(user)
35
+ const [error, setError] = useState<string>()
36
+ const [busy, setBusy] = useState(false)
37
+ const mismatch =
38
+ typeof window !== 'undefined' &&
39
+ !!user &&
40
+ !!expectedSession() &&
41
+ expectedSession() !== user.sessionId
42
+ useEffect(() => {
43
+ startSessionNotifications()
44
+ if (user) {
45
+ try {
46
+ observeSession(user.sessionId)
47
+ } catch {
48
+ /* Interstitial owns this mismatch. */
49
+ }
50
+ }
51
+ }, [user])
52
+ useEffect(() => {
53
+ if (!changed && !mismatch) return
54
+ void getCurrentAdminUser()
55
+ .then((verified) => {
56
+ if (sessionEpoch() !== epoch) return
57
+ setCurrent(verified)
58
+ setError(undefined)
59
+ })
60
+ .catch(() => setError('The session is no longer available. Please sign in again.'))
61
+ }, [changed, mismatch, epoch])
62
+ if (!changed && !mismatch) return children
63
+ async function acknowledge() {
64
+ setBusy(true)
65
+ try {
66
+ const verified = await getCurrentAdminUser()
67
+ // Require a second acknowledgement if the identity changed while this interstitial was open.
68
+ if (!current || current.sessionId !== verified.sessionId) {
69
+ setCurrent(verified)
70
+ setError('The active session changed again. Review the account before continuing.')
71
+ return
72
+ }
73
+ acceptSession(verified.sessionId)
74
+ window.location.reload() // Discard queued edits and old page state; never replay them.
75
+ } catch {
76
+ flagSessionChanged()
77
+ setError('Unable to verify the active session. Please sign in again.')
78
+ } finally {
79
+ setBusy(false)
80
+ }
81
+ }
82
+ return (
83
+ <main role="alertdialog" aria-modal="true" aria-labelledby="session-change-title">
84
+ <h1 id="session-change-title">Your session has changed</h1>
85
+ <p>Your previous work has not been retried. Check the active account before continuing.</p>
86
+ {current && <p>Active account: {current.email}</p>}
87
+ {error && <p role="alert">{error}</p>}
88
+ <button type="button" disabled={busy || !current} onClick={acknowledge}>
89
+ Continue as this account
90
+ </button>
91
+ <a href={`${getSignInRoutePath()}?reauthenticate=1`}>Sign in again</a>
92
+ </main>
93
+ )
94
+ }
@@ -6,6 +6,8 @@
6
6
  * Copyright (c) Infonomic Company Limited
7
7
  */
8
8
 
9
+ import { useEffect, useState } from 'react'
10
+
9
11
  import { SignInForm } from '@byline/admin/auth/components/sign-in-form'
10
12
  import { BylineAdminServicesProvider } from '@byline/admin/services'
11
13
  import { getAdminConfig } from '@byline/core'
@@ -15,10 +17,20 @@ import cx from 'clsx'
15
17
 
16
18
  import { buildLocaleDefinitions } from '../../i18n/locale-definitions.js'
17
19
  import { bylineAdminServices } from '../../integrations/byline-admin-services.js'
20
+ import {
21
+ coordinateAuthAction,
22
+ expectedSession,
23
+ flagSessionChanged,
24
+ observeSession,
25
+ renewSingleFlight,
26
+ } from '../../integrations/session-coordination.js'
27
+ import { renewAdminSession } from '../../server-fns/auth/renew.js'
18
28
  import { setAdminLocaleFn } from '../../server-fns/i18n/index.js'
29
+ import { SessionChangeBoundary } from './session-change-boundary.js'
19
30
  import styles from './sign-in-page.module.css'
20
31
 
21
32
  interface SignInPageProps {
33
+ reauthenticate?: boolean
22
34
  redirectTo: string
23
35
  activeLocale: LocaleCode
24
36
  homeUrl?: string
@@ -44,14 +56,54 @@ interface SignInPageProps {
44
56
  * Threads an optional host-owned `homeUrl` into `SignInForm` so the form's
45
57
  * action row can render a plain "Home" link beside the submit button.
46
58
  */
47
- export function SignInPage({ redirectTo, activeLocale, homeUrl }: SignInPageProps) {
59
+ export function SignInPage({
60
+ redirectTo,
61
+ activeLocale,
62
+ homeUrl,
63
+ reauthenticate = false,
64
+ }: SignInPageProps) {
65
+ const [checking, setChecking] = useState(!reauthenticate)
66
+ const [bootstrapError, setBootstrapError] = useState(false)
67
+ useEffect(() => {
68
+ if (reauthenticate) return
69
+ let active = true
70
+ void renewSingleFlight(() =>
71
+ coordinateAuthAction(async () => {
72
+ const result = await renewAdminSession({
73
+ data: { expectedSessionId: expectedSession() ?? undefined },
74
+ })
75
+ observeSession(result.sessionId)
76
+ })
77
+ )
78
+ .then(() => {
79
+ if (active) window.location.replace(redirectTo)
80
+ })
81
+ .catch((error) => {
82
+ if (error?.code === 'ERR_SESSION_CHANGED') flagSessionChanged()
83
+ else if (
84
+ ![
85
+ 'ERR_UNAUTHENTICATED',
86
+ 'ERR_INVALID_TOKEN',
87
+ 'ERR_REVOKED_TOKEN',
88
+ 'ERR_ACCOUNT_DISABLED',
89
+ ].includes(error?.code)
90
+ )
91
+ setBootstrapError(true)
92
+ })
93
+ .finally(() => {
94
+ if (active) setChecking(false)
95
+ })
96
+ return () => {
97
+ active = false
98
+ }
99
+ }, [redirectTo, reauthenticate])
48
100
  const { i18n } = getAdminConfig()
49
101
  const localeDefinitions = buildLocaleDefinitions(i18n.admin.locales, i18n.admin.localeDefinitions)
50
102
  const handleSetLocale = async (next: LocaleCode) => {
51
103
  await setAdminLocaleFn({ data: { locale: next } })
52
104
  window.location.reload()
53
105
  }
54
- return (
106
+ const content = (
55
107
  <I18nProvider
56
108
  bundle={i18n.translations ?? {}}
57
109
  activeLocale={activeLocale}
@@ -61,14 +113,27 @@ export function SignInPage({ redirectTo, activeLocale, homeUrl }: SignInPageProp
61
113
  >
62
114
  <BylineAdminServicesProvider services={bylineAdminServices}>
63
115
  <main className={cx('byline-sign-in-page', styles.main)}>
116
+ <noscript>JavaScript is required to sign in or renew your admin session.</noscript>
64
117
  <div className={cx('byline-sign-in-page-bar', styles.bar)}>
65
118
  <LanguageMenu />
66
119
  </div>
67
120
  <div className={cx('byline-sign-in-page-inner', styles.inner)}>
68
- <SignInForm redirectTo={redirectTo} homeUrl={homeUrl} />
121
+ {bootstrapError ? (
122
+ <div role="alert">
123
+ <p>Unable to check your session. Please try again.</p>
124
+ <button type="button" onClick={() => window.location.reload()}>
125
+ Try again
126
+ </button>
127
+ </div>
128
+ ) : checking ? (
129
+ <p role="status">Checking your session…</p>
130
+ ) : (
131
+ <SignInForm redirectTo={redirectTo} homeUrl={homeUrl} />
132
+ )}
69
133
  </div>
70
134
  </main>
71
135
  </BylineAdminServicesProvider>
72
136
  </I18nProvider>
73
137
  )
138
+ return reauthenticate ? content : <SessionChangeBoundary>{content}</SessionChangeBoundary>
74
139
  }