akanjs 3.0.2 → 3.1.0-rc.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 (378) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/base/baseEnv.ts +0 -4
  3. package/base/primitiveRegistry.ts +2 -2
  4. package/client/cookie.ts +5 -1
  5. package/client/csrLocation.ts +122 -0
  6. package/client/csrTypes.ts +32 -4
  7. package/client/device.ts +2 -1
  8. package/client/frameConfig.ts +78 -7
  9. package/client/index.ts +3 -0
  10. package/client/makePageProto.tsx +3 -2
  11. package/client/native/external-view.ts +1 -0
  12. package/client/route/RootLayoutDefinition.ts +7 -1
  13. package/client/route/RouteHead.ts +108 -0
  14. package/client/route/index.ts +1 -0
  15. package/client/router.ts +36 -23
  16. package/client/rscNavigation.ts +8 -2
  17. package/client/viewportMeta.ts +60 -0
  18. package/client/webManifest.ts +18 -0
  19. package/common/Logger.ts +7 -2
  20. package/common/PipelineNames.ts +31 -0
  21. package/common/PipelinePattern.ts +70 -0
  22. package/common/PipelinePlanSchema.ts +490 -0
  23. package/common/backoff.ts +14 -0
  24. package/common/index.ts +39 -0
  25. package/common/pipelinePlan.ts +259 -0
  26. package/common/routeConvention.ts +1 -0
  27. package/common/types.ts +17 -0
  28. package/constant/constantRegistry.ts +26 -6
  29. package/dictionary/base.dictionary.ts +8 -0
  30. package/dictionary/trans.ts +9 -1
  31. package/document/into.ts +5 -0
  32. package/fetch/client/fetchClient.ts +135 -31
  33. package/fetch/client/httpClient.ts +45 -0
  34. package/fetch/client/wsClient.ts +125 -16
  35. package/fetch/fetchType/buildFetch.type.ts +1 -0
  36. package/index.ts +243 -10
  37. package/package.json +1 -5
  38. package/server/akanApp.ts +5 -2
  39. package/server/akanServer.ts +44 -8
  40. package/server/artifact/builderRpc.ts +8 -3
  41. package/server/artifact/ipcTypes.ts +5 -2
  42. package/server/di/diLifecycle.ts +39 -3
  43. package/server/di/utils.ts +28 -1
  44. package/server/ed25519Key.ts +48 -0
  45. package/server/index.ts +1 -0
  46. package/server/machine/index.ts +2 -0
  47. package/server/machine/machineAccount.ts +27 -0
  48. package/server/machine/machineToken.ts +151 -0
  49. package/server/mcp/McpApps.ts +256 -0
  50. package/server/mcp/McpDispatcher.ts +31 -7
  51. package/server/mcp/McpRouter.ts +209 -46
  52. package/server/ops/opsToken.ts +3 -10
  53. package/server/resolver/database.resolver.ts +3 -0
  54. package/server/resolver/signal.resolver.ts +40 -3
  55. package/server/routeElementComposer.tsx +78 -2
  56. package/server/routeState.ts +6 -0
  57. package/server/routeTreeBuilder.ts +26 -9
  58. package/server/routing/apiRouter.ts +6 -3
  59. package/server/routing/roomReplay.ts +88 -0
  60. package/server/rscClient.tsx +53 -8
  61. package/server/rscWorker.tsx +141 -27
  62. package/server/rscWorkerHost.ts +18 -5
  63. package/server/sitemap.ts +14 -0
  64. package/server/ssrFromRscRenderer.tsx +12 -6
  65. package/server/ssrTypes.ts +3 -1
  66. package/server/systemPageDocument.tsx +38 -30
  67. package/server/types.tsx +2 -0
  68. package/server/webRouter.ts +74 -5
  69. package/service/injectInfo.ts +9 -1
  70. package/service/ipcTypes.ts +2 -1
  71. package/service/predefinedAdaptor/database.adaptor.ts +1 -146
  72. package/service/predefinedAdaptor/queue.adaptor.ts +44 -6
  73. package/service/predefinedAdaptor/queueBackoff.ts +12 -0
  74. package/service/predefinedAdaptor/solidQueue.adaptor.ts +17 -13
  75. package/service/predefinedAdaptor/sql/SqlDocumentStore.ts +44 -2
  76. package/service/predefinedAdaptor/sql/dialect/postgres.ts +3 -0
  77. package/service/predefinedAdaptor/sql/dialect/sqlite.ts +5 -2
  78. package/service/predefinedAdaptor/sql/search/fts5.ts +1 -1
  79. package/service/predefinedAdaptor/sql/types.ts +8 -6
  80. package/service/predefinedAdaptor/sql/values.ts +0 -23
  81. package/service/serve.ts +5 -1
  82. package/signal/agent.signal.ts +1 -1
  83. package/signal/base.signal.ts +2 -2
  84. package/signal/endpointIdempotency.ts +53 -0
  85. package/signal/internalArg.ts +14 -0
  86. package/signal/mcp/McpDocument.ts +38 -1
  87. package/signal/mcp/mcpProtocol.ts +18 -0
  88. package/signal/signalContext.ts +58 -30
  89. package/signal/signalRegistry.ts +14 -4
  90. package/signal/types.ts +18 -3
  91. package/store/agent/StoreSurfaceSource.ts +1 -1
  92. package/store/agentic/StToolBuilder.ts +3 -0
  93. package/test/conformance.ts +9 -33
  94. package/types/base/baseEnv.d.ts +0 -4
  95. package/types/base/primitiveRegistry.d.ts +1 -1
  96. package/types/client/csrLocation.d.ts +32 -0
  97. package/types/client/csrTypes.d.ts +40 -4
  98. package/types/client/frameConfig.d.ts +14 -2
  99. package/types/client/index.d.ts +3 -0
  100. package/types/client/native/external-view.d.ts +1 -0
  101. package/types/client/route/RootLayoutDefinition.d.ts +2 -1
  102. package/types/client/route/RouteHead.d.ts +13 -0
  103. package/types/client/route/index.d.ts +1 -0
  104. package/types/client/router.d.ts +6 -0
  105. package/types/client/rscNavigation.d.ts +4 -0
  106. package/types/client/viewportMeta.d.ts +18 -0
  107. package/types/client/webManifest.d.ts +4 -0
  108. package/types/common/PipelineNames.d.ts +6 -0
  109. package/types/common/PipelinePattern.d.ts +14 -0
  110. package/types/common/PipelinePlanSchema.d.ts +9 -0
  111. package/types/common/backoff.d.ts +10 -0
  112. package/types/common/index.d.ts +5 -0
  113. package/types/common/pipelinePlan.d.ts +224 -0
  114. package/types/common/types.d.ts +16 -0
  115. package/types/constant/constantRegistry.d.ts +5 -2
  116. package/types/dictionary/base.dictionary.d.ts +1 -1
  117. package/types/dictionary/dictionary.d.ts +8 -8
  118. package/types/document/into.d.ts +9 -0
  119. package/types/fetch/client/fetchClient.d.ts +31 -4
  120. package/types/fetch/client/httpClient.d.ts +8 -0
  121. package/types/fetch/client/wsClient.d.ts +26 -2
  122. package/types/fetch/fetchType/buildFetch.type.d.ts +1 -0
  123. package/types/index.d.ts +230 -9
  124. package/types/server/akanServer.d.ts +16 -1
  125. package/types/server/artifact/builderRpc.d.ts +7 -2
  126. package/types/server/artifact/ipcTypes.d.ts +5 -1
  127. package/types/server/di/utils.d.ts +1 -0
  128. package/types/server/ed25519Key.d.ts +13 -0
  129. package/types/server/index.d.ts +1 -0
  130. package/types/server/machine/index.d.ts +2 -0
  131. package/types/server/machine/machineAccount.d.ts +5 -0
  132. package/types/server/machine/machineToken.d.ts +29 -0
  133. package/types/server/machine.d.ts +1 -0
  134. package/types/server/mcp/McpApps.d.ts +50 -0
  135. package/types/server/mcp/McpDispatcher.d.ts +4 -1
  136. package/types/server/mcp/McpRouter.d.ts +4 -0
  137. package/types/server/resolver/signal.resolver.d.ts +2 -1
  138. package/types/server/routeElementComposer.d.ts +24 -4
  139. package/types/server/routeState.d.ts +4 -1
  140. package/types/server/routing/roomReplay.d.ts +24 -0
  141. package/types/server/rscWorkerHost.d.ts +5 -2
  142. package/types/server/sitemap.d.ts +2 -0
  143. package/types/server/ssrFromRscRenderer.d.ts +3 -3
  144. package/types/server/ssrTypes.d.ts +4 -1
  145. package/types/server/systemPageDocument.d.ts +2 -0
  146. package/types/server/types.d.ts +4 -1
  147. package/types/server/webRouter.d.ts +3 -1
  148. package/types/service/ipcTypes.d.ts +2 -0
  149. package/types/service/predefinedAdaptor/database.adaptor.d.ts +1 -24
  150. package/types/service/predefinedAdaptor/queue.adaptor.d.ts +10 -3
  151. package/types/service/predefinedAdaptor/queueBackoff.d.ts +4 -0
  152. package/types/service/predefinedAdaptor/solidQueue.adaptor.d.ts +3 -3
  153. package/types/service/predefinedAdaptor/sql/SqlDocumentStore.d.ts +6 -1
  154. package/types/service/predefinedAdaptor/sql/dialect/postgres.d.ts +1 -0
  155. package/types/service/predefinedAdaptor/sql/dialect/sqlite.d.ts +1 -0
  156. package/types/service/predefinedAdaptor/sql/search/fts5.d.ts +1 -1
  157. package/types/service/predefinedAdaptor/sql/types.d.ts +9 -5
  158. package/types/service/predefinedAdaptor/sql/values.d.ts +0 -3
  159. package/types/service/serve.d.ts +3 -0
  160. package/types/signal/endpointIdempotency.d.ts +7 -0
  161. package/types/signal/internalArg.d.ts +10 -0
  162. package/types/signal/mcp/McpDocument.d.ts +8 -0
  163. package/types/signal/mcp/mcpProtocol.d.ts +17 -0
  164. package/types/signal/signalContext.d.ts +5 -3
  165. package/types/signal/signalRegistry.d.ts +4 -4
  166. package/types/signal/types.d.ts +21 -1
  167. package/types/store/agentic/StToolBuilder.d.ts +2 -0
  168. package/types/test/conformance.d.ts +7 -10
  169. package/types/ui/Agent/Chat.d.ts +5 -1
  170. package/types/ui/Agent/EmbedApproval.d.ts +1 -0
  171. package/types/ui/Agent/index.d.ts +3 -0
  172. package/types/ui/Agent/useKeyboardFrame.d.ts +2 -0
  173. package/types/ui/Agent/useScreenActions.d.ts +18 -0
  174. package/types/ui/BottomSheet.d.ts +1 -0
  175. package/types/ui/Constant/Doc.d.ts +11 -17
  176. package/types/ui/Constant/index.d.ts +7 -2
  177. package/types/ui/Constant/schemaDoc.d.ts +11 -0
  178. package/types/ui/Dialog/Modal.d.ts +6 -1
  179. package/types/ui/Dialog/index.d.ts +1 -1
  180. package/types/ui/Dialog/modalSurface.d.ts +12 -0
  181. package/types/ui/Layout/BottomTab.d.ts +11 -2
  182. package/types/ui/Layout/FrameSlotFallback.d.ts +9 -0
  183. package/types/ui/Layout/Navbar.d.ts +4 -1
  184. package/types/ui/Layout/TopInset.d.ts +1 -1
  185. package/types/ui/Layout/index.d.ts +3 -3
  186. package/types/ui/Link/Action.d.ts +5 -1
  187. package/types/ui/Link/Anchor.d.ts +5 -3
  188. package/types/ui/Link/activePath.d.ts +13 -0
  189. package/types/ui/Link/index.d.ts +1 -1
  190. package/types/ui/Modal.d.ts +6 -1
  191. package/types/ui/Model/EditModal.d.ts +6 -1
  192. package/types/ui/Model/New.d.ts +6 -1
  193. package/types/ui/Model/ViewEditModal.d.ts +6 -1
  194. package/types/ui/Model/ViewModal.d.ts +6 -1
  195. package/types/ui/Model/newFormOrigin.d.ts +7 -0
  196. package/types/ui/Portal.d.ts +3 -1
  197. package/types/ui/Reference/origin.d.ts +12 -0
  198. package/types/ui/Signal/Doc.d.ts +23 -18
  199. package/types/ui/Signal/Listener.d.ts +2 -6
  200. package/types/ui/Signal/Object.d.ts +6 -10
  201. package/types/ui/Signal/Response.d.ts +3 -6
  202. package/types/ui/Signal/RestApi.d.ts +11 -15
  203. package/types/ui/Signal/WebSocket.d.ts +11 -21
  204. package/types/ui/Signal/endpointEntries.d.ts +8 -0
  205. package/types/ui/Signal/index.d.ts +35 -12
  206. package/types/ui/Signal/signalText.d.ts +178 -0
  207. package/types/ui/System/CSR.d.ts +2 -2
  208. package/types/ui/System/Client.d.ts +4 -3
  209. package/types/ui/System/Common.d.ts +27 -6
  210. package/types/ui/System/SSR.d.ts +6 -6
  211. package/types/ui/Tooltip.d.ts +4 -2
  212. package/types/ui/index.d.ts +2 -1
  213. package/types/ui/overlayLayer.d.ts +6 -0
  214. package/types/ui/overlayPosition.d.ts +17 -0
  215. package/types/vendor/@akanjs/native/packages/cli/src/api.d.ts +14 -3
  216. package/types/vendor/@akanjs/native/packages/cli/src/commands/update.d.ts +1 -1
  217. package/types/vendor/@akanjs/native/packages/cli/src/config.d.ts +30 -5
  218. package/types/vendor/@akanjs/native/packages/cli/src/lib/compat.d.ts +8 -0
  219. package/types/vendor/@akanjs/native/packages/cli/src/lib/desktop-server.d.ts +5 -0
  220. package/types/vendor/@akanjs/native/packages/cli/src/lib/distribution.d.ts +22 -0
  221. package/types/vendor/@akanjs/native/packages/cli/src/lib/project.d.ts +4 -0
  222. package/types/vendor/@akanjs/native/packages/cli/src/lib/publish.d.ts +16 -10
  223. package/types/vendor/@akanjs/native/packages/cli/src/lib/updates.d.ts +51 -7
  224. package/types/vendor/@akanjs/native/packages/cli/src/lib/vectors.d.ts +1 -1
  225. package/types/vendor/@akanjs/native/packages/cli/src/platforms/windows-installer.d.ts +7 -0
  226. package/types/vendor/@akanjs/native/packages/cli/src/platforms/windows.d.ts +5 -0
  227. package/types/vendor/@akanjs/native/packages/core/src/kernel.d.ts +36 -0
  228. package/types/vendor/@akanjs/native/packages/desktop/src/plugin.d.ts +2 -0
  229. package/types/vendor/@akanjs/native/packages/desktop/src/server.d.ts +11 -0
  230. package/types/vendor/@akanjs/native/plugins/external-view/src/desktop.d.ts +17 -0
  231. package/types/vendor/@akanjs/native/plugins/external-view/src/index.d.ts +132 -0
  232. package/types/vendor/@akanjs/native/plugins/external-view/src.d.ts +1 -0
  233. package/types/vendor/@akanjs/native/plugins/updates/src/index.d.ts +8 -0
  234. package/types/vendor/@akanjs/native/plugins/window/src/desktop.d.ts +4 -1
  235. package/types/vendor/@akanjs/native/plugins/window/src/index.d.ts +49 -0
  236. package/types/vendor/use-agentic/types.d.ts +2 -0
  237. package/types/webkit/CsrStack.d.ts +1 -1
  238. package/types/webkit/McpAppAgent.d.ts +14 -0
  239. package/types/webkit/McpAppApprovals.d.ts +8 -0
  240. package/types/webkit/McpAppBridge.d.ts +101 -0
  241. package/types/webkit/McpAppView.d.ts +21 -0
  242. package/types/webkit/index.d.ts +1 -0
  243. package/types/webkit/useLocation.d.ts +1 -1
  244. package/types/webkit/useVisualViewportFrame.d.ts +13 -0
  245. package/ui/Agent/Chat.tsx +29 -10
  246. package/ui/Agent/Composer.tsx +2 -1
  247. package/ui/Agent/EmbedApproval.tsx +17 -0
  248. package/ui/Agent/RichInput.tsx +1 -1
  249. package/ui/Agent/index.ts +2 -0
  250. package/ui/Agent/useKeyboardFrame.ts +10 -0
  251. package/ui/Agent/useScreenActions.ts +45 -0
  252. package/ui/BottomSheet.tsx +82 -53
  253. package/ui/Constant/Doc.tsx +113 -60
  254. package/ui/Constant/index.ts +2 -2
  255. package/ui/Constant/schemaDoc.ts +86 -26
  256. package/ui/Dialog/LegacyModal.tsx +53 -52
  257. package/ui/Dialog/Modal.tsx +93 -49
  258. package/ui/Dialog/modalSurface.ts +61 -0
  259. package/ui/DragAction.tsx +1 -1
  260. package/ui/DraggableList.tsx +1 -1
  261. package/ui/Dropdown.tsx +4 -6
  262. package/ui/KeyboardAvoiding.tsx +1 -1
  263. package/ui/Layout/BottomInset.tsx +20 -14
  264. package/ui/Layout/BottomTab.tsx +42 -16
  265. package/ui/Layout/FrameSlotFallback.tsx +59 -0
  266. package/ui/Layout/Navbar.tsx +37 -11
  267. package/ui/Layout/Sider.tsx +1 -1
  268. package/ui/Layout/TopInset.tsx +15 -14
  269. package/ui/Link/Action.tsx +15 -5
  270. package/ui/Link/Anchor.tsx +25 -13
  271. package/ui/Link/activePath.ts +25 -0
  272. package/ui/Link/index.tsx +1 -1
  273. package/ui/Load/Pagination.tsx +1 -1
  274. package/ui/Load/Units.tsx +1 -1
  275. package/ui/Loading/ProgressBar.tsx +1 -1
  276. package/ui/Modal.tsx +9 -0
  277. package/ui/Model/EditModal.tsx +16 -1
  278. package/ui/Model/New.tsx +25 -2
  279. package/ui/Model/NewWrapper_Client.tsx +2 -0
  280. package/ui/Model/ViewEditModal.tsx +9 -0
  281. package/ui/Model/ViewModal.tsx +16 -1
  282. package/ui/Model/newFormOrigin.ts +45 -0
  283. package/ui/Popconfirm.tsx +5 -7
  284. package/ui/Portal.tsx +4 -7
  285. package/ui/Reference/origin.ts +31 -0
  286. package/ui/ScreenNavigator.tsx +1 -1
  287. package/ui/Select.tsx +2 -7
  288. package/ui/Signal/Arg.tsx +2 -2
  289. package/ui/Signal/Doc.tsx +125 -63
  290. package/ui/Signal/Endpoint.tsx +11 -10
  291. package/ui/Signal/Listener.tsx +7 -10
  292. package/ui/Signal/Object.tsx +7 -14
  293. package/ui/Signal/Response.tsx +24 -24
  294. package/ui/Signal/RestApi.tsx +63 -52
  295. package/ui/Signal/WebSocket.tsx +65 -72
  296. package/ui/Signal/endpointEntries.ts +25 -0
  297. package/ui/Signal/index.ts +23 -6
  298. package/ui/Signal/signalText.ts +59 -0
  299. package/ui/System/CSR.tsx +34 -8
  300. package/ui/System/Client.tsx +36 -5
  301. package/ui/System/Common.tsx +76 -32
  302. package/ui/System/SSR.tsx +73 -31
  303. package/ui/Toast.tsx +1 -1
  304. package/ui/Tooltip.tsx +84 -30
  305. package/ui/index.ts +11 -1
  306. package/ui/overlayLayer.ts +25 -2
  307. package/ui/overlayPosition.ts +90 -0
  308. package/ui/recipe/inputRecipe.ts +8 -1
  309. package/ui/styles.css +152 -0
  310. package/vendor/@akanjs/native/native/android/src/com/akanjs/runtime/AkanNativeActivity.kt +22 -2
  311. package/vendor/@akanjs/native/native/android/src/com/akanjs/runtime/AkanNativeKernel.kt +37 -0
  312. package/vendor/@akanjs/native/native/android/src/com/akanjs/runtime/AkanNativeUpdateReports.kt +169 -0
  313. package/vendor/@akanjs/native/native/android/src/com/akanjs/runtime/AkanNativeUpdates.kt +110 -38
  314. package/vendor/@akanjs/native/native/android/src/com/akanjs/runtime/AkanNativeVectors.kt +20 -0
  315. package/vendor/@akanjs/native/native/desktop/Cargo.lock +16 -16
  316. package/vendor/@akanjs/native/native/desktop/src/external.rs +651 -0
  317. package/vendor/@akanjs/native/native/desktop/src/json.rs +23 -0
  318. package/vendor/@akanjs/native/native/desktop/src/lib.rs +228 -5
  319. package/vendor/@akanjs/native/native/ios/Sources/AkanNativeApp.swift +2 -1
  320. package/vendor/@akanjs/native/native/ios/Sources/AkanNativeKernel.swift +47 -1
  321. package/vendor/@akanjs/native/native/ios/Sources/AkanNativeUpdateReports.swift +145 -0
  322. package/vendor/@akanjs/native/native/ios/Sources/AkanNativeUpdates.swift +112 -43
  323. package/vendor/@akanjs/native/native/ios/Sources/AkanNativeVectors.swift +18 -0
  324. package/vendor/@akanjs/native/native/ios/Sources/AkanNativeViewController.swift +24 -2
  325. package/vendor/@akanjs/native/packages/cli/src/api.ts +22 -6
  326. package/vendor/@akanjs/native/packages/cli/src/commands/build.ts +24 -8
  327. package/vendor/@akanjs/native/packages/cli/src/commands/update.ts +11 -9
  328. package/vendor/@akanjs/native/packages/cli/src/config.ts +35 -7
  329. package/vendor/@akanjs/native/packages/cli/src/lib/compat.ts +2 -0
  330. package/vendor/@akanjs/native/packages/cli/src/lib/configkeys.ts +6 -4
  331. package/vendor/@akanjs/native/packages/cli/src/lib/desktop-server.ts +11 -1
  332. package/vendor/@akanjs/native/packages/cli/src/lib/distribution.ts +73 -0
  333. package/vendor/@akanjs/native/packages/cli/src/lib/project.ts +15 -3
  334. package/vendor/@akanjs/native/packages/cli/src/lib/publish.ts +70 -23
  335. package/vendor/@akanjs/native/packages/cli/src/lib/updates.ts +159 -23
  336. package/vendor/@akanjs/native/packages/cli/src/lib/vectors.ts +2 -0
  337. package/vendor/@akanjs/native/packages/cli/src/platforms/android.ts +9 -4
  338. package/vendor/@akanjs/native/packages/cli/src/platforms/desktop-update.ts +1 -1
  339. package/vendor/@akanjs/native/packages/cli/src/platforms/desktop.ts +4 -2
  340. package/vendor/@akanjs/native/packages/cli/src/platforms/ios.ts +9 -4
  341. package/vendor/@akanjs/native/packages/cli/src/platforms/windows-installer.ts +25 -11
  342. package/vendor/@akanjs/native/packages/cli/src/platforms/windows.ts +14 -3
  343. package/vendor/@akanjs/native/packages/core/src/kernel.ts +49 -0
  344. package/vendor/@akanjs/native/packages/core/vectors/updates.json +31 -0
  345. package/vendor/@akanjs/native/packages/desktop/src/main.ts +3 -0
  346. package/vendor/@akanjs/native/packages/desktop/src/plugin.ts +2 -0
  347. package/vendor/@akanjs/native/packages/desktop/src/server.ts +70 -6
  348. package/vendor/@akanjs/native/plugins/external-view/native-plugin.json +23 -0
  349. package/vendor/@akanjs/native/plugins/external-view/src/desktop.ts +321 -0
  350. package/vendor/@akanjs/native/plugins/external-view/src/index.ts +106 -0
  351. package/vendor/@akanjs/native/plugins/updates/android/UpdatesPlugin.kt +133 -41
  352. package/vendor/@akanjs/native/plugins/updates/ios/UpdatesPlugin.swift +101 -36
  353. package/vendor/@akanjs/native/plugins/updates/native-plugin.json +4 -2
  354. package/vendor/@akanjs/native/plugins/updates/src/desktop.ts +15 -1
  355. package/vendor/@akanjs/native/plugins/updates/src/index.ts +7 -1
  356. package/vendor/@akanjs/native/plugins/window/native-plugin.json +4 -0
  357. package/vendor/@akanjs/native/plugins/window/src/desktop.ts +68 -1
  358. package/vendor/@akanjs/native/plugins/window/src/index.ts +47 -0
  359. package/vendor/use-agentic/ToolRunner.ts +8 -1
  360. package/vendor/use-agentic/types.ts +2 -0
  361. package/webkit/CsrRouteTable.ts +1 -0
  362. package/webkit/CsrStack.ts +9 -2
  363. package/webkit/McpAppAgent.ts +138 -0
  364. package/webkit/McpAppApprovals.ts +56 -0
  365. package/webkit/McpAppBridge.ts +291 -0
  366. package/webkit/McpAppView.ts +258 -0
  367. package/webkit/bootCsr.tsx +54 -5
  368. package/webkit/index.ts +1 -0
  369. package/webkit/useCsrValues.ts +16 -10
  370. package/webkit/useFrameRuntime.ts +10 -1
  371. package/webkit/useLocation.ts +4 -3
  372. package/webkit/useVisualViewportFrame.tsx +52 -0
  373. package/service/predefinedAdaptor/sql/driver/libsql.ts +0 -45
  374. package/types/service/predefinedAdaptor/sql/driver/libsql.d.ts +0 -20
  375. package/types/ui/Agent/useKeyboardInset.d.ts +0 -1
  376. package/types/ui/ServerPortal.d.ts +0 -15
  377. package/ui/Agent/useKeyboardInset.ts +0 -19
  378. package/ui/ServerPortal.tsx +0 -49
@@ -89,3 +89,93 @@ export const useOverlayPosition = ({
89
89
 
90
90
  return position;
91
91
  };
92
+
93
+ export type OverlaySide = "top" | "right" | "bottom" | "left";
94
+
95
+ export interface AnchoredPosition {
96
+ top: number;
97
+ left: number;
98
+ /** The side the panel landed on: the asked one, or its opposite when that has more room. */
99
+ side: OverlaySide;
100
+ /** The trigger's centre along the panel's edge, held clear of its corners, for the pointer. */
101
+ arrowOffset: number;
102
+ }
103
+
104
+ const oppositeSide = { top: "bottom", bottom: "top", left: "right", right: "left" } as const;
105
+
106
+ /** Like {@link useOverlayPosition}, for a panel centred on any side of its trigger, as a tooltip is. */
107
+ export const useAnchoredPosition = ({
108
+ opened,
109
+ triggerRef,
110
+ panelRef,
111
+ side,
112
+ gap = TRIGGER_GAP + 2,
113
+ }: {
114
+ opened: boolean;
115
+ triggerRef: RefObject<HTMLElement | null>;
116
+ panelRef: RefObject<HTMLElement | null>;
117
+ side: OverlaySide;
118
+ gap?: number;
119
+ }) => {
120
+ const [position, setPosition] = useState<AnchoredPosition | null>(null);
121
+
122
+ const measure = useCallback(() => {
123
+ const trigger = triggerRef.current?.getBoundingClientRect();
124
+ const panel = panelRef.current;
125
+ if (!trigger || !panel) return;
126
+ const { offsetHeight: height, offsetWidth: width } = panel;
127
+ const room = {
128
+ top: trigger.top - gap - VIEWPORT_MARGIN,
129
+ bottom: window.innerHeight - trigger.bottom - gap - VIEWPORT_MARGIN,
130
+ left: trigger.left - gap - VIEWPORT_MARGIN,
131
+ right: window.innerWidth - trigger.right - gap - VIEWPORT_MARGIN,
132
+ };
133
+ const vertical = side === "top" || side === "bottom";
134
+ const need = vertical ? height : width;
135
+ const landed = room[side] >= need || room[side] >= room[oppositeSide[side]] ? side : oppositeSide[side];
136
+ const centerX = (trigger.left + trigger.right) / 2;
137
+ const centerY = (trigger.top + trigger.bottom) / 2;
138
+ const next: AnchoredPosition = vertical
139
+ ? {
140
+ top: landed === "top" ? trigger.top - gap - height : trigger.bottom + gap,
141
+ left: clamp(centerX - width / 2, VIEWPORT_MARGIN, window.innerWidth - VIEWPORT_MARGIN - width),
142
+ side: landed,
143
+ arrowOffset: 0,
144
+ }
145
+ : {
146
+ top: clamp(centerY - height / 2, VIEWPORT_MARGIN, window.innerHeight - VIEWPORT_MARGIN - height),
147
+ left: landed === "left" ? trigger.left - gap - width : trigger.right + gap,
148
+ side: landed,
149
+ arrowOffset: 0,
150
+ };
151
+ next.arrowOffset = vertical
152
+ ? clamp(centerX - next.left, POINTER_INSET / 2, width - POINTER_INSET / 2)
153
+ : clamp(centerY - next.top, POINTER_INSET / 2, height - POINTER_INSET / 2);
154
+ setPosition((prev) =>
155
+ prev &&
156
+ prev.top === next.top &&
157
+ prev.left === next.left &&
158
+ prev.side === next.side &&
159
+ prev.arrowOffset === next.arrowOffset
160
+ ? prev
161
+ : next,
162
+ );
163
+ }, [gap, panelRef, side, triggerRef]);
164
+
165
+ useLayoutEffect(() => {
166
+ if (opened) measure();
167
+ else setPosition(null);
168
+ }, [opened, measure]);
169
+
170
+ useEffect(() => {
171
+ if (!opened) return;
172
+ window.addEventListener("scroll", measure, true);
173
+ window.addEventListener("resize", measure);
174
+ return () => {
175
+ window.removeEventListener("scroll", measure, true);
176
+ window.removeEventListener("resize", measure);
177
+ };
178
+ }, [opened, measure]);
179
+
180
+ return position;
181
+ };
@@ -12,7 +12,14 @@ export const inputRecipe = recipe(
12
12
  select:
13
13
  "appearance-none bg-[image:linear-gradient(to_top_right,transparent_50%,currentColor_50%,currentColor_calc(50%+2px),transparent_calc(50%+2px)),linear-gradient(to_bottom_right,transparent_calc(50%-2px),currentColor_calc(50%-2px),currentColor_50%,transparent_50%)] bg-[length:5px_6px] bg-[position:calc(100%-14px)_50%,calc(100%-9px)_50%] bg-no-repeat pr-7 pl-3",
14
14
  },
15
- size: { xs: "text-xs", sm: "text-sm", md: "text-sm", lg: "text-base", xl: "text-lg" },
15
+
16
+ size: {
17
+ xs: "text-xs pointer-coarse:text-base",
18
+ sm: "text-sm pointer-coarse:text-base",
19
+ md: "text-sm pointer-coarse:text-base",
20
+ lg: "text-base",
21
+ xl: "text-lg",
22
+ },
16
23
  tone: {
17
24
  default: "",
18
25
  primary: "border-primary",
package/ui/styles.css CHANGED
@@ -274,6 +274,158 @@ body.akan-mobile-document {
274
274
  padding-inline: 2rem;
275
275
  }
276
276
 
277
+ /* A frame whose insets change with width carries one var per breakpoint (`topInset: { base, md }`, SSR only);
278
+ these pick the one in force. The breakpoints are Tailwind's defaults, which `ResponsiveInset` keys on. */
279
+ [data-akan-frame-responsive] {
280
+ --akan-top-inset: var(--akan-top-inset-base);
281
+ --akan-bottom-inset: var(--akan-bottom-inset-base);
282
+ --akan-top-inset-visibility: var(--akan-top-inset-visibility-base);
283
+ --akan-bottom-inset-visibility: var(--akan-bottom-inset-visibility-base);
284
+ }
285
+ @media (width >= 40rem) {
286
+ [data-akan-frame-responsive] {
287
+ --akan-top-inset: var(--akan-top-inset-sm);
288
+ --akan-bottom-inset: var(--akan-bottom-inset-sm);
289
+ --akan-top-inset-visibility: var(--akan-top-inset-visibility-sm);
290
+ --akan-bottom-inset-visibility: var(--akan-bottom-inset-visibility-sm);
291
+ }
292
+ }
293
+ @media (width >= 48rem) {
294
+ [data-akan-frame-responsive] {
295
+ --akan-top-inset: var(--akan-top-inset-md);
296
+ --akan-bottom-inset: var(--akan-bottom-inset-md);
297
+ --akan-top-inset-visibility: var(--akan-top-inset-visibility-md);
298
+ --akan-bottom-inset-visibility: var(--akan-bottom-inset-visibility-md);
299
+ }
300
+ }
301
+ @media (width >= 64rem) {
302
+ [data-akan-frame-responsive] {
303
+ --akan-top-inset: var(--akan-top-inset-lg);
304
+ --akan-bottom-inset: var(--akan-bottom-inset-lg);
305
+ --akan-top-inset-visibility: var(--akan-top-inset-visibility-lg);
306
+ --akan-bottom-inset-visibility: var(--akan-bottom-inset-visibility-lg);
307
+ }
308
+ }
309
+ @media (width >= 80rem) {
310
+ [data-akan-frame-responsive] {
311
+ --akan-top-inset: var(--akan-top-inset-xl);
312
+ --akan-bottom-inset: var(--akan-bottom-inset-xl);
313
+ --akan-top-inset-visibility: var(--akan-top-inset-visibility-xl);
314
+ --akan-bottom-inset-visibility: var(--akan-bottom-inset-visibility-xl);
315
+ }
316
+ }
317
+ @media (width >= 96rem) {
318
+ [data-akan-frame-responsive] {
319
+ --akan-top-inset: var(--akan-top-inset-2xl);
320
+ --akan-bottom-inset: var(--akan-bottom-inset-2xl);
321
+ --akan-top-inset-visibility: var(--akan-top-inset-visibility-2xl);
322
+ --akan-bottom-inset-visibility: var(--akan-bottom-inset-visibility-2xl);
323
+ }
324
+ }
325
+
326
+ /* Route transitions on the web: an RSC navigation tags itself `akan-<transition>-<push|pop>` from the route's
327
+ `.config({ transition })`, and the page content's ViewTransition takes that class. The rest of the page holds. */
328
+ :root:active-view-transition-type(akan-nav)::view-transition-old(root),
329
+ :root:active-view-transition-type(akan-nav)::view-transition-new(root) {
330
+ animation: none;
331
+ }
332
+ ::view-transition-old(.akan-stack-push) {
333
+ animation: 260ms cubic-bezier(0.32, 0.72, 0, 1) both akan-vt-out-left;
334
+ }
335
+ ::view-transition-new(.akan-stack-push) {
336
+ animation: 260ms cubic-bezier(0.32, 0.72, 0, 1) both akan-vt-in-right;
337
+ }
338
+ ::view-transition-old(.akan-stack-pop) {
339
+ animation: 260ms cubic-bezier(0.32, 0.72, 0, 1) both akan-vt-out-right;
340
+ }
341
+ ::view-transition-new(.akan-stack-pop) {
342
+ animation: 260ms cubic-bezier(0.32, 0.72, 0, 1) both akan-vt-in-left;
343
+ }
344
+ ::view-transition-old(.akan-bottomUp-push),
345
+ ::view-transition-new(.akan-bottomUp-pop) {
346
+ animation: none;
347
+ }
348
+ ::view-transition-new(.akan-bottomUp-push) {
349
+ animation: 280ms cubic-bezier(0.32, 0.72, 0, 1) both akan-vt-in-up;
350
+ }
351
+ ::view-transition-old(.akan-bottomUp-pop) {
352
+ animation: 240ms ease-in both akan-vt-out-down;
353
+ }
354
+ ::view-transition-old(.akan-fade-push),
355
+ ::view-transition-old(.akan-fade-pop) {
356
+ animation: 180ms ease-in both akan-vt-fade-out;
357
+ }
358
+ ::view-transition-new(.akan-fade-push),
359
+ ::view-transition-new(.akan-fade-pop) {
360
+ animation: 180ms ease-out both akan-vt-fade-in;
361
+ }
362
+ ::view-transition-old(.akan-scaleOut-push) {
363
+ animation: 220ms ease-in both akan-vt-shrink-out;
364
+ }
365
+ ::view-transition-new(.akan-scaleOut-push) {
366
+ animation: 220ms ease-out both akan-vt-fade-in;
367
+ }
368
+ ::view-transition-old(.akan-scaleOut-pop) {
369
+ animation: 200ms ease-in both akan-vt-fade-out;
370
+ }
371
+ ::view-transition-new(.akan-scaleOut-pop) {
372
+ animation: 220ms ease-out both akan-vt-grow-in;
373
+ }
374
+ @keyframes akan-vt-in-right {
375
+ from {
376
+ transform: translateX(100%);
377
+ }
378
+ }
379
+ @keyframes akan-vt-out-left {
380
+ to {
381
+ transform: translateX(-30%);
382
+ opacity: 0.6;
383
+ }
384
+ }
385
+ @keyframes akan-vt-in-left {
386
+ from {
387
+ transform: translateX(-30%);
388
+ opacity: 0.6;
389
+ }
390
+ }
391
+ @keyframes akan-vt-out-right {
392
+ to {
393
+ transform: translateX(100%);
394
+ }
395
+ }
396
+ @keyframes akan-vt-in-up {
397
+ from {
398
+ transform: translateY(100%);
399
+ }
400
+ }
401
+ @keyframes akan-vt-out-down {
402
+ to {
403
+ transform: translateY(100%);
404
+ }
405
+ }
406
+ @keyframes akan-vt-fade-in {
407
+ from {
408
+ opacity: 0;
409
+ }
410
+ }
411
+ @keyframes akan-vt-fade-out {
412
+ to {
413
+ opacity: 0;
414
+ }
415
+ }
416
+ @keyframes akan-vt-shrink-out {
417
+ to {
418
+ transform: scale(0.94);
419
+ opacity: 0;
420
+ }
421
+ }
422
+ @keyframes akan-vt-grow-in {
423
+ from {
424
+ transform: scale(0.94);
425
+ opacity: 0;
426
+ }
427
+ }
428
+
277
429
  /* Always-included keyframes (non-theme animations) */
278
430
  @keyframes smaller {
279
431
  0% {
@@ -221,6 +221,11 @@ class AkanNativeActivity : Activity(), AkanNativePluginContext.Host {
221
221
  if (debuggable && intent.hasExtra(EXTRA_ENV)) webView.reload()
222
222
  }
223
223
 
224
+ override fun onResume() {
225
+ super.onResume()
226
+ if (AkanNativeFeatures.UPDATES && !unsupportedWebView) AkanNativeUpdateReports.flush()
227
+ }
228
+
224
229
  override fun onConfigurationChanged(newConfig: Configuration) {
225
230
  super.onConfigurationChanged(newConfig)
226
231
  if (unsupportedWebView) return
@@ -373,10 +378,14 @@ class AkanNativeActivity : Activity(), AkanNativePluginContext.Host {
373
378
  }
374
379
  }
375
380
 
381
+ //? Armed when the page starts, not when it finishes: a bundle stuck in a script never finishes loading.
382
+ override fun onPageStarted(view: WebView, url: String, favicon: Bitmap?) {
383
+ startReadyTimer()
384
+ }
385
+
376
386
  override fun onPageFinished(view: WebView, url: String) {
377
387
  root.requestApplyInsets() // push insets into the new document
378
388
  if (splashConfig.optBoolean("autoHide", true)) hideSplash(SPLASH_FADE_MS)
379
- startReadyTimer()
380
389
  }
381
390
 
382
391
  override fun doUpdateVisitedHistory(view: WebView, url: String, isReload: Boolean) {
@@ -841,7 +850,16 @@ class AkanNativeActivity : Activity(), AkanNativePluginContext.Host {
841
850
  // ---------------------------------------------------------------- web bundle updates (UP-2)
842
851
 
843
852
  private val readyTimeout = Runnable {
844
- if (AkanNativeFeatures.UPDATES && AkanNativeUpdates.onTrial) serveBundle(AkanNativeUpdates.rollback())
853
+ if (!AkanNativeFeatures.UPDATES || !AkanNativeUpdates.onTrial) return@Runnable
854
+ val (root, trial) = AkanNativeUpdates.rollback()
855
+ // A renderer stuck in a script never runs the next navigation: one that does not answer is ended, and
856
+ // onRenderProcessGone makes the activity again, on the bundle the rollback made active.
857
+ var answered = false
858
+ webView.evaluateJavascript("0") { answered = true }
859
+ webView.postDelayed({
860
+ trial?.let { AkanNativeUpdateReports.record("rolledBack", it, if (answered) "timeout" else "hang") }
861
+ if (answered || webView.webViewRenderProcess?.terminate() != true) serveBundle(root)
862
+ }, RENDERER_PROBE_MS)
845
863
  }
846
864
 
847
865
  /** A bundle on trial must call notifyReady() within updates.readyTimeout of loading. */
@@ -977,6 +995,8 @@ class AkanNativeActivity : Activity(), AkanNativePluginContext.Host {
977
995
  const val EXTRA_ENV = "akanNativeEnv"
978
996
  private const val STATE_PENDING = "akan-native.pendingResults"
979
997
  private const val SPLASH_FADE_MS = 200L
998
+ /** How long a page on trial has to answer before its renderer counts as stuck. */
999
+ private const val RENDERER_PROBE_MS = 2_000L
980
1000
  // Must match @akanjs/native/core protocol.ts INIT_PREFIX / INIT_SUFFIX.
981
1001
  private const val INIT_PREFIX = "window.__AKAN_NATIVE__=(function(o,b,e){for(var k in b)o[k]=b[k];o.env=e;return o})(window.__AKAN_NATIVE__||{},"
982
1002
  private const val INIT_SUFFIX = ");\n"
@@ -3,6 +3,7 @@ package com.akanjs.runtime
3
3
  import java.nio.ByteBuffer
4
4
  import java.nio.charset.CharacterCodingException
5
5
  import java.nio.charset.CodingErrorAction
6
+ import java.security.MessageDigest
6
7
 
7
8
  /*
8
9
  * Decisions every akan-native host makes the same way (docs/architecture.md §5): a port of
@@ -21,6 +22,13 @@ sealed class AkanNativeRoute {
21
22
  object NotFound : AkanNativeRoute()
22
23
  }
23
24
 
25
+ /** kernel.ts UpdateDecision: what a verified web bundle manifest means to this binary (updates protocol v2). */
26
+ data class AkanNativeUpdateDecision(val kind: Kind, val baseline: Double = 0.0, val revoke: List<String> = emptyList()) {
27
+ enum class Kind(val wire: String) { UNTRUSTED("untrusted"), REVOKED("revoked"), SELF_REVOKE("self-revoke"), REVOKE_LATER("revoke-later"), TAKE("take"), STALE("stale") }
28
+
29
+ val wire: String get() = if (kind == Kind.TAKE || kind == Kind.STALE) "${kind.wire}:${baseline.toLong()}:${revoke.joinToString(",")}" else kind.wire
30
+ }
31
+
24
32
  sealed class AkanNativeRangeAnswer {
25
33
  object Whole : AkanNativeRangeAnswer()
26
34
  /** First and last byte, inclusive. */
@@ -321,4 +329,33 @@ object AkanNativeKernel {
321
329
  if (!topLevel) return if (scheme in AkanNativeContract.FRAME_SCHEMES) AkanNativeNavigation.LOAD else AkanNativeNavigation.DROP
322
330
  return if (scheme in externalSchemes(extra)) AkanNativeNavigation.OPEN else AkanNativeNavigation.DROP
323
331
  }
332
+
333
+ /**
334
+ * kernel.ts updateDecision: a key revokes only keys listed before it (a leaked online key, listed first, cannot
335
+ * revoke the spares that recover from it), and resetSequence counts only when it newly revokes a key.
336
+ */
337
+ fun updateDecision(
338
+ ring: List<String>, revoked: List<String>, signer: String, revokeKeys: List<String>, resetSequence: Boolean,
339
+ sequence: Double, embedded: Double, running: Pair<Double, String>?,
340
+ ): AkanNativeUpdateDecision {
341
+ val index = ring.indexOf(signer)
342
+ if (index < 0) return AkanNativeUpdateDecision(AkanNativeUpdateDecision.Kind.UNTRUSTED)
343
+ if (signer in revoked) return AkanNativeUpdateDecision(AkanNativeUpdateDecision.Kind.REVOKED)
344
+ if (signer in revokeKeys) return AkanNativeUpdateDecision(AkanNativeUpdateDecision.Kind.SELF_REVOKE)
345
+ if (revokeKeys.any { ring.indexOf(it) > index }) return AkanNativeUpdateDecision(AkanNativeUpdateDecision.Kind.REVOKE_LATER)
346
+ val revoke = ring.filter { it in revokeKeys && it !in revoked }
347
+ val bar = running?.takeIf { it.second !in revoked && it.second !in revoke }?.first ?: 0.0
348
+ val baseline = if (resetSequence && revoke.isNotEmpty()) embedded else maxOf(embedded, bar)
349
+ val kind = if (sequence > baseline) AkanNativeUpdateDecision.Kind.TAKE else AkanNativeUpdateDecision.Kind.STALE
350
+ return AkanNativeUpdateDecision(kind, baseline, revoke)
351
+ }
352
+
353
+ /** kernel.ts updateManifestUrl: one envelope per channel and native API. */
354
+ fun updateManifestUrl(url: String, platform: String, channel: String, nativeApi: String): String = "$url/$platform/$channel/$nativeApi.json"
355
+
356
+ /** The first 8 bytes of the raw public key's SHA-256, in hex: what an envelope names its signer by. */
357
+ fun updateKeyId(publicKey: ByteArray): String =
358
+ MessageDigest.getInstance("SHA-256").digest(publicKey).take(8).joinToString("") { "%02x".format(it) }
359
+
360
+ fun isUpdateKeyId(s: String): Boolean = s.length == 16 && s.all { it in '0'..'9' || it in 'a'..'f' }
324
361
  }
@@ -0,0 +1,169 @@
1
+ // What became of each web bundle release (F-S2), posted to updates.report.url: the Android twin of
2
+ // native/ios/Sources/AkanNativeUpdateReports.swift, same file and wire format.
3
+ //
4
+ // noBackupFilesDir/akan-native-updates/report.json: { installId, nextId, events: [{ id, event, bundle, sequence, reason,
5
+ // at }], retryAt?, seen: [key] }. reset() rewrites state.json only, so the installId (random, not a device identifier)
6
+ // and the id counter live as long as the install, and an id is never used twice.
7
+ // Sent from the activity's onResume, so at launch and on each return to the foreground: oldest first, 20 to a request,
8
+ // one request at a time. A 2xx removes the batch; a 4xx other than 408 and 429 drops it, since the same body would be
9
+ // refused forever; a 408 (a body slower than the server waits for), a 429, a 5xx or no answer keeps it for the next time
10
+ // (after Retry-After). At most 100 wait, the oldest dropped first. Main thread only; the request runs on a worker thread.
11
+ package com.akanjs.runtime
12
+
13
+ import android.os.Build
14
+ import android.os.Handler
15
+ import android.os.Looper
16
+ import android.util.AtomicFile
17
+ import android.util.Log
18
+ import java.io.File
19
+ import java.io.IOException
20
+ import java.net.HttpURLConnection
21
+ import java.net.URL
22
+ import java.security.SecureRandom
23
+ import java.time.Instant
24
+ import java.time.temporal.ChronoUnit
25
+ import org.json.JSONArray
26
+ import org.json.JSONObject
27
+
28
+ object AkanNativeUpdateReports {
29
+ private const val TAG = "AkanNativeUpdates"
30
+ private const val LIMIT = 100
31
+ private const val BATCH = 20
32
+ private val main = Handler(Looper.getMainLooper())
33
+ private var sending = false
34
+
35
+ private class Report(
36
+ val installId: String,
37
+ var nextId: Long,
38
+ val events: MutableList<JSONObject>,
39
+ var retryAt: Double?,
40
+ val seen: MutableList<String>,
41
+ )
42
+
43
+ private val file get() = AtomicFile(File(AkanNativeUpdates.directory, "report.json"))
44
+
45
+ /**
46
+ * downloaded, applied, confirmed, rolledBack (timeout, relaunch, hang, verify) or failed (download, verify, disk,
47
+ * signature, manifest). Nothing is kept when the build names no report URL.
48
+ */
49
+ fun record(event: String, bundle: String?, sequence: Double?, reason: String? = null) {
50
+ if (AkanNativeUpdates.config?.reportUrl == null || bundle == null || !AkanNativeKernel.isBundleId(bundle)) return
51
+ val report = read()
52
+ // A check runs at every launch: the same failure of the same release is one event, not one per launch.
53
+ val key = "$event|$bundle|${reason ?: ""}"
54
+ if (key in report.seen) return
55
+ report.seen.add(key)
56
+ while (report.seen.size > LIMIT) report.seen.removeAt(0)
57
+ val number: Any = sequence?.let { if (it == Math.rint(it) && Math.abs(it) < 9e15) it.toLong() else it } ?: JSONObject.NULL
58
+ report.events.add(
59
+ JSONObject().put("id", report.nextId).put("event", event).put("bundle", bundle).put("sequence", number)
60
+ .put("reason", reason ?: JSONObject.NULL).put("at", Instant.now().truncatedTo(ChronoUnit.MILLIS).toString()),
61
+ )
62
+ while (report.events.size > LIMIT) report.events.removeAt(0)
63
+ report.nextId++
64
+ write(report)
65
+ }
66
+
67
+ fun record(event: String, entry: AkanNativeUpdateEntry, reason: String? = null) = record(event, entry.bundle, entry.sequence, reason)
68
+
69
+ fun flush() {
70
+ val config = AkanNativeUpdates.config ?: return
71
+ val address = config.reportUrl ?: return
72
+ if (sending) return
73
+ val report = read()
74
+ val now = System.currentTimeMillis() / 1000.0
75
+ // A Retry-After more than a day out is a clock that moved, not the server's wish.
76
+ report.retryAt?.let { if (it > now && it < now + 86_400) return }
77
+ val events = report.events.take(BATCH)
78
+ if (events.isEmpty()) return
79
+ val body = JSONObject()
80
+ .put("app", config.app).put("platform", config.platform).put("channel", config.channel).put("installId", report.installId)
81
+ .put("nativeApi", config.nativeApi).put("appVersion", config.version).put("os", "Android ${Build.VERSION.RELEASE}")
82
+ .put("events", JSONArray(events))
83
+ .toString().toByteArray()
84
+ val ids = events.map { it.optLong("id") }.toSet()
85
+ sending = true
86
+ Thread {
87
+ var status = 0
88
+ var retryAfter: String? = null
89
+ try {
90
+ val connection = URL(address).openConnection() as HttpURLConnection
91
+ try {
92
+ connection.requestMethod = "POST"
93
+ connection.doOutput = true
94
+ connection.connectTimeout = 30_000
95
+ connection.readTimeout = 30_000
96
+ connection.useCaches = false
97
+ connection.setRequestProperty("Content-Type", "application/json")
98
+ connection.setFixedLengthStreamingMode(body.size)
99
+ connection.outputStream.use { it.write(body) }
100
+ status = connection.responseCode
101
+ retryAfter = connection.getHeaderField("Retry-After")
102
+ } finally {
103
+ connection.disconnect()
104
+ }
105
+ } catch (e: Exception) {
106
+ Log.w(TAG, "cannot post update reports: $e")
107
+ }
108
+ main.post {
109
+ sending = false
110
+ settle(ids, status, retryAfter)
111
+ }
112
+ }.start()
113
+ }
114
+
115
+ private fun settle(ids: Set<Long>, status: Int, retryAfter: String?) {
116
+ val report = read()
117
+ if (status !in 200..299 && (status !in 400..499 || status == 408 || status == 429)) {
118
+ report.retryAt = retryAfter?.trim()?.toDoubleOrNull()?.let { System.currentTimeMillis() / 1000.0 + it }
119
+ write(report)
120
+ return
121
+ }
122
+ report.events.removeAll { it.optLong("id") in ids }
123
+ report.retryAt = null
124
+ write(report)
125
+ flush()
126
+ }
127
+
128
+ private fun read(): Report {
129
+ val o = try {
130
+ JSONObject(String(file.readFully()))
131
+ } catch (e: Exception) {
132
+ JSONObject()
133
+ }
134
+ val installId = o.optString("installId")
135
+ if (!Regex("[0-9a-fA-F]{32}").matches(installId)) return Report(newInstallId(), 0, mutableListOf(), null, mutableListOf())
136
+ val events = o.optJSONArray("events")
137
+ val seen = o.optJSONArray("seen")
138
+ return Report(
139
+ installId,
140
+ o.optLong("nextId"),
141
+ MutableList(events?.length() ?: 0) { events?.optJSONObject(it) }.filterNotNull().toMutableList(),
142
+ if (o.has("retryAt")) o.optDouble("retryAt") else null,
143
+ MutableList(seen?.length() ?: 0) { seen?.optString(it).orEmpty() }.filter { it.isNotEmpty() }.toMutableList(),
144
+ )
145
+ }
146
+
147
+ private fun write(report: Report) {
148
+ AkanNativeUpdates.directory.mkdirs()
149
+ val o = JSONObject().put("installId", report.installId).put("nextId", report.nextId).put("events", JSONArray(report.events))
150
+ .put("seen", JSONArray(report.seen))
151
+ report.retryAt?.let { o.put("retryAt", it) }
152
+ val target = file
153
+ val out = try {
154
+ target.startWrite()
155
+ } catch (e: IOException) {
156
+ Log.w(TAG, "cannot write report.json: $e")
157
+ return
158
+ }
159
+ try {
160
+ out.write(o.toString().toByteArray())
161
+ target.finishWrite(out)
162
+ } catch (e: IOException) {
163
+ target.failWrite(out)
164
+ Log.w(TAG, "cannot write report.json: $e")
165
+ }
166
+ }
167
+
168
+ private fun newInstallId(): String = ByteArray(16).also { SecureRandom().nextBytes(it) }.joinToString("") { "%02x".format(it) }
169
+ }