akanjs 3.0.0-beta.26 → 3.0.0-beta.28

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 (226) hide show
  1. package/base/baseEnv.ts +19 -5
  2. package/client/csrTypes.ts +2 -0
  3. package/client/device.ts +28 -12
  4. package/client/index.ts +1 -0
  5. package/client/native/accessibility.ts +1 -0
  6. package/client/native/app-state.ts +1 -0
  7. package/client/native/app.ts +1 -0
  8. package/client/native/appearance.ts +1 -0
  9. package/client/native/auth-session.ts +1 -0
  10. package/client/native/autostart.ts +1 -0
  11. package/client/native/badge.ts +1 -0
  12. package/client/native/biometric.ts +1 -0
  13. package/client/native/browser.ts +1 -0
  14. package/client/native/camera.ts +1 -0
  15. package/client/native/clipboard.ts +1 -0
  16. package/client/native/contacts.ts +1 -0
  17. package/client/native/device.ts +1 -0
  18. package/client/native/dialog.ts +1 -0
  19. package/client/native/dock.ts +1 -0
  20. package/client/native/file-picker.ts +1 -0
  21. package/client/native/filesystem.ts +1 -0
  22. package/client/native/geolocation.ts +1 -0
  23. package/client/native/global-shortcut.ts +1 -0
  24. package/client/native/haptics.ts +1 -0
  25. package/client/native/http.ts +1 -0
  26. package/client/native/iap.ts +1 -0
  27. package/client/native/keep-awake.ts +1 -0
  28. package/client/native/keyboard.ts +1 -0
  29. package/client/native/local-notifications.ts +1 -0
  30. package/client/native/menu.ts +1 -0
  31. package/client/native/network.ts +1 -0
  32. package/client/native/opener.ts +1 -0
  33. package/client/native/preferences.ts +1 -0
  34. package/client/native/push.ts +1 -0
  35. package/client/native/screen-orientation.ts +1 -0
  36. package/client/native/screen.ts +1 -0
  37. package/client/native/secure-storage.ts +1 -0
  38. package/client/native/share.ts +1 -0
  39. package/client/native/single-instance.ts +1 -0
  40. package/client/native/splash-screen.ts +1 -0
  41. package/client/native/sqlite.ts +1 -0
  42. package/client/native/toast.ts +1 -0
  43. package/client/native/tray.ts +1 -0
  44. package/client/native/updates.ts +1 -0
  45. package/client/native/volume.ts +1 -0
  46. package/client/native/window-state.ts +1 -0
  47. package/client/native/window.ts +1 -0
  48. package/client/native.ts +31 -1
  49. package/client/resolveServerUrl.ts +11 -0
  50. package/client/router.ts +8 -0
  51. package/common/Logger.ts +26 -2
  52. package/common/RouteLayering.ts +52 -0
  53. package/common/index.ts +1 -0
  54. package/index.ts +115 -6
  55. package/native/desktop.ts +5 -0
  56. package/package.json +11 -1
  57. package/server/akanApp.ts +23 -3
  58. package/server/akanAppHeaders.ts +4 -1
  59. package/server/akanServer.ts +27 -7
  60. package/server/index.ts +1 -0
  61. package/server/lifecycle/orphanGroup.ts +21 -0
  62. package/server/nativeFile.ts +107 -0
  63. package/server/ops/opsRoute.ts +3 -2
  64. package/server/routeTreeBuilder.ts +14 -39
  65. package/server/routing/apiRouter.ts +26 -3
  66. package/server/routing/hostAllowlist.ts +24 -0
  67. package/server/selfExec.ts +25 -0
  68. package/server/types.tsx +3 -1
  69. package/server/webRouter.ts +7 -4
  70. package/service/ipcTypes.ts +4 -0
  71. package/signal/CrossSiteGuard.ts +7 -0
  72. package/test/signalTestRuntime.ts +8 -2
  73. package/types/base/baseEnv.d.ts +1 -0
  74. package/types/client/csrTypes.d.ts +2 -0
  75. package/types/client/index.d.ts +1 -0
  76. package/types/client/native/accessibility.d.ts +1 -0
  77. package/types/client/native/app-state.d.ts +1 -0
  78. package/types/client/native/app.d.ts +1 -0
  79. package/types/client/native/appearance.d.ts +1 -0
  80. package/types/client/native/auth-session.d.ts +1 -0
  81. package/types/client/native/autostart.d.ts +1 -0
  82. package/types/client/native/badge.d.ts +1 -0
  83. package/types/client/native/biometric.d.ts +1 -0
  84. package/types/client/native/browser.d.ts +1 -0
  85. package/types/client/native/camera.d.ts +1 -0
  86. package/types/client/native/clipboard.d.ts +1 -0
  87. package/types/client/native/contacts.d.ts +1 -0
  88. package/types/client/native/device.d.ts +1 -0
  89. package/types/client/native/dialog.d.ts +1 -0
  90. package/types/client/native/dock.d.ts +1 -0
  91. package/types/client/native/file-picker.d.ts +1 -0
  92. package/types/client/native/filesystem.d.ts +1 -0
  93. package/types/client/native/geolocation.d.ts +1 -0
  94. package/types/client/native/global-shortcut.d.ts +1 -0
  95. package/types/client/native/haptics.d.ts +1 -0
  96. package/types/client/native/http.d.ts +1 -0
  97. package/types/client/native/iap.d.ts +1 -0
  98. package/types/client/native/keep-awake.d.ts +1 -0
  99. package/types/client/native/keyboard.d.ts +1 -0
  100. package/types/client/native/local-notifications.d.ts +1 -0
  101. package/types/client/native/menu.d.ts +1 -0
  102. package/types/client/native/network.d.ts +1 -0
  103. package/types/client/native/opener.d.ts +1 -0
  104. package/types/client/native/preferences.d.ts +1 -0
  105. package/types/client/native/push.d.ts +1 -0
  106. package/types/client/native/screen-orientation.d.ts +1 -0
  107. package/types/client/native/screen.d.ts +1 -0
  108. package/types/client/native/secure-storage.d.ts +1 -0
  109. package/types/client/native/share.d.ts +1 -0
  110. package/types/client/native/single-instance.d.ts +1 -0
  111. package/types/client/native/splash-screen.d.ts +1 -0
  112. package/types/client/native/sqlite.d.ts +1 -0
  113. package/types/client/native/toast.d.ts +1 -0
  114. package/types/client/native/tray.d.ts +1 -0
  115. package/types/client/native/updates.d.ts +1 -0
  116. package/types/client/native/volume.d.ts +1 -0
  117. package/types/client/native/window-state.d.ts +1 -0
  118. package/types/client/native/window.d.ts +1 -0
  119. package/types/client/native.d.ts +14 -2
  120. package/types/client/resolveServerUrl.d.ts +1 -0
  121. package/types/client/router.d.ts +3 -0
  122. package/types/common/RouteLayering.d.ts +28 -0
  123. package/types/common/index.d.ts +1 -0
  124. package/types/index.d.ts +112 -6
  125. package/types/native/desktop.d.ts +3 -0
  126. package/types/server/akanAppHeaders.d.ts +2 -0
  127. package/types/server/index.d.ts +1 -0
  128. package/types/server/lifecycle/orphanGroup.d.ts +7 -0
  129. package/types/server/nativeFile.d.ts +10 -0
  130. package/types/server/routing/apiRouter.d.ts +3 -1
  131. package/types/server/routing/hostAllowlist.d.ts +8 -0
  132. package/types/server/selfExec.d.ts +8 -0
  133. package/types/server/types.d.ts +3 -1
  134. package/types/service/ipcTypes.d.ts +16 -0
  135. package/types/signal/CrossSiteGuard.d.ts +1 -0
  136. package/types/ui/recipe/buttonRecipe.d.ts +2 -2
  137. package/types/vendor/@akanjs/native/packages/cli/src/api.d.ts +57 -4
  138. package/types/vendor/@akanjs/native/packages/cli/src/config.d.ts +48 -0
  139. package/types/vendor/@akanjs/native/packages/cli/src/lib/desktop-server.d.ts +8 -0
  140. package/types/vendor/@akanjs/native/packages/cli/src/lib/prepare.d.ts +8 -2
  141. package/types/vendor/@akanjs/native/packages/cli/src/lib/project.d.ts +10 -0
  142. package/types/vendor/@akanjs/native/packages/cli/src/lib/publish.d.ts +24 -0
  143. package/types/vendor/@akanjs/native/packages/cli/src/lib/updates.d.ts +24 -0
  144. package/types/vendor/@akanjs/native/packages/cli/src/platforms/desktop-update.d.ts +4 -1
  145. package/types/vendor/@akanjs/native/packages/cli/src/platforms/macos.d.ts +5 -0
  146. package/types/vendor/@akanjs/native/packages/cli/src/platforms/windows-installer.d.ts +38 -0
  147. package/types/vendor/@akanjs/native/packages/desktop/src/dispatcher.d.ts +8 -1
  148. package/types/vendor/@akanjs/native/packages/desktop/src/grants.d.ts +11 -0
  149. package/types/vendor/@akanjs/native/packages/desktop/src/main.d.ts +11 -0
  150. package/types/vendor/@akanjs/native/packages/desktop/src/paths.d.ts +13 -2
  151. package/types/vendor/@akanjs/native/packages/desktop/src/plugin.d.ts +27 -0
  152. package/types/vendor/@akanjs/native/packages/desktop/src/relaunch.d.ts +21 -0
  153. package/types/vendor/@akanjs/native/packages/desktop/src/server.d.ts +79 -0
  154. package/types/vendor/@akanjs/native/plugins/app/src/index.d.ts +5 -0
  155. package/types/vendor/@akanjs/native/plugins/file-picker/src/index.d.ts +20 -1
  156. package/types/vendor/@akanjs/native/plugins/push/src/index.d.ts +9 -0
  157. package/types/vendor/@akanjs/native/plugins/updates/src/desktop.d.ts +28 -0
  158. package/types/vendor/@akanjs/native/plugins/volume/src/common.d.ts +10 -0
  159. package/types/vendor/@akanjs/native/plugins/volume/src/desktop.d.ts +18 -0
  160. package/types/vendor/@akanjs/native/plugins/volume/src/index.d.ts +28 -0
  161. package/types/vendor/@akanjs/native/plugins/volume/src.d.ts +1 -0
  162. package/types/webkit/csrFrameDump.d.ts +42 -0
  163. package/types/webkit/nativeNavigation.d.ts +1 -0
  164. package/types/webkit/nativePurchase.d.ts +6 -2
  165. package/types/webkit/nativeUpdates.d.ts +8 -0
  166. package/ui/Agent/Attach.tsx +6 -2
  167. package/ui/CsrImage.tsx +3 -2
  168. package/ui/System/CSR.tsx +4 -0
  169. package/vendor/@akanjs/native/native/android/src/com/akanjs/runtime/AkanNativeActivity.kt +5 -1
  170. package/vendor/@akanjs/native/native/android/src/com/akanjs/runtime/AkanNativeBridge.kt +19 -0
  171. package/vendor/@akanjs/native/native/desktop/Cargo.toml +1 -1
  172. package/vendor/@akanjs/native/native/desktop/src/lib.rs +161 -25
  173. package/vendor/@akanjs/native/native/desktop/src/volume.rs +184 -0
  174. package/vendor/@akanjs/native/native/desktop/src/win/mod.rs +3 -1
  175. package/vendor/@akanjs/native/native/desktop/src/win/volume.rs +71 -0
  176. package/vendor/@akanjs/native/native/ios/Sources/AkanNativeShell.swift +12 -2
  177. package/vendor/@akanjs/native/packages/cli/src/api.ts +142 -12
  178. package/vendor/@akanjs/native/packages/cli/src/commands/build.ts +5 -3
  179. package/vendor/@akanjs/native/packages/cli/src/commands/update.ts +10 -71
  180. package/vendor/@akanjs/native/packages/cli/src/config.ts +41 -0
  181. package/vendor/@akanjs/native/packages/cli/src/lib/configkeys.ts +9 -2
  182. package/vendor/@akanjs/native/packages/cli/src/lib/desktop-server.ts +58 -0
  183. package/vendor/@akanjs/native/packages/cli/src/lib/hmr.ts +2 -0
  184. package/vendor/@akanjs/native/packages/cli/src/lib/prepare.ts +10 -2
  185. package/vendor/@akanjs/native/packages/cli/src/lib/project.ts +38 -2
  186. package/vendor/@akanjs/native/packages/cli/src/lib/publish.ts +104 -0
  187. package/vendor/@akanjs/native/packages/cli/src/lib/updates.ts +85 -5
  188. package/vendor/@akanjs/native/packages/cli/src/platforms/android.ts +1 -0
  189. package/vendor/@akanjs/native/packages/cli/src/platforms/desktop-update.ts +6 -4
  190. package/vendor/@akanjs/native/packages/cli/src/platforms/desktop.ts +16 -0
  191. package/vendor/@akanjs/native/packages/cli/src/platforms/macos.ts +44 -1
  192. package/vendor/@akanjs/native/packages/cli/src/platforms/windows-installer.ts +436 -0
  193. package/vendor/@akanjs/native/packages/cli/src/platforms/windows.ts +2 -0
  194. package/vendor/@akanjs/native/packages/core/src/runtime.ts +26 -5
  195. package/vendor/@akanjs/native/packages/desktop/src/dispatcher.ts +20 -0
  196. package/vendor/@akanjs/native/packages/desktop/src/grants.ts +47 -0
  197. package/vendor/@akanjs/native/packages/desktop/src/host.ts +102 -14
  198. package/vendor/@akanjs/native/packages/desktop/src/main.ts +59 -11
  199. package/vendor/@akanjs/native/packages/desktop/src/paths.ts +38 -4
  200. package/vendor/@akanjs/native/packages/desktop/src/plugin.ts +28 -0
  201. package/vendor/@akanjs/native/packages/desktop/src/relaunch.ts +159 -0
  202. package/vendor/@akanjs/native/packages/desktop/src/server.ts +493 -0
  203. package/vendor/@akanjs/native/plugins/app/android/AppPlugin.kt +14 -0
  204. package/vendor/@akanjs/native/plugins/app/native-plugin.json +2 -2
  205. package/vendor/@akanjs/native/plugins/app/src/desktop.ts +5 -0
  206. package/vendor/@akanjs/native/plugins/app/src/index.ts +6 -1
  207. package/vendor/@akanjs/native/plugins/app/src/web.ts +3 -0
  208. package/vendor/@akanjs/native/plugins/file-picker/src/desktop.ts +39 -2
  209. package/vendor/@akanjs/native/plugins/file-picker/src/index.ts +36 -1
  210. package/vendor/@akanjs/native/plugins/push/android/PushPlugin.kt +9 -1
  211. package/vendor/@akanjs/native/plugins/push/ios/PushPlugin.swift +4 -0
  212. package/vendor/@akanjs/native/plugins/push/src/index.ts +10 -0
  213. package/vendor/@akanjs/native/plugins/updates/src/desktop.ts +464 -452
  214. package/vendor/@akanjs/native/plugins/volume/android/VolumePlugin.kt +107 -0
  215. package/vendor/@akanjs/native/plugins/volume/native-plugin.json +14 -0
  216. package/vendor/@akanjs/native/plugins/volume/src/common.ts +28 -0
  217. package/vendor/@akanjs/native/plugins/volume/src/desktop.ts +185 -0
  218. package/vendor/@akanjs/native/plugins/volume/src/index.ts +32 -0
  219. package/webkit/CsrRouteTable.ts +15 -35
  220. package/webkit/RenderLayer.tsx +71 -6
  221. package/webkit/bootCsr.tsx +11 -8
  222. package/webkit/csrFrameDump.ts +67 -0
  223. package/webkit/nativeNavigation.ts +17 -0
  224. package/webkit/nativePurchase.ts +12 -2
  225. package/webkit/nativeUpdates.ts +55 -0
  226. package/webkit/useCsrValues.ts +27 -0
@@ -15,8 +15,9 @@ import {
15
15
  getRouteExports,
16
16
  Logger,
17
17
  matchRoutePattern,
18
- parseBasePaths,
19
18
  parseRouteModuleKey,
19
+ type RouteLayer,
20
+ RouteLayering,
20
21
  routeSegmentToTreePath,
21
22
  } from "akanjs/common";
22
23
  import { createElement } from "react";
@@ -54,15 +55,12 @@ export class RouteTreeBuilder {
54
55
  };
55
56
 
56
57
  readonly #context: PagesContext;
57
- readonly #baseLayoutPaths: string[];
58
58
  readonly #routeMap = new Map<string, Route>();
59
59
  readonly #pagePatterns: { key: string; pattern: string }[] = [];
60
60
  readonly #fallbackRoutes: LayoutFallbackRoute[] = [];
61
61
 
62
62
  constructor(context: PagesContext) {
63
63
  this.#context = context;
64
- const basePaths = process.env.AKAN_PUBLIC_BASE_PATHS ? parseBasePaths(process.env.AKAN_PUBLIC_BASE_PATHS) : null;
65
- this.#baseLayoutPaths = ["/", "/:lang", ...(basePaths?.map((bp) => `/:lang/${bp}`) ?? [])];
66
64
  this.#routeMap.set("/", { path: "/", children: new Map() });
67
65
  }
68
66
 
@@ -169,7 +167,7 @@ export class RouteTreeBuilder {
169
167
  targetRouteMap.set(targetPath, {
170
168
  ...(targetRouteMap.get(targetPath) ?? { path: targetPath, children: new Map<string, Route>() }),
171
169
  ...(parsed.kind === "layout"
172
- ? { renderLayout: routeRender }
170
+ ? { renderLayout: routeRender, isRootLayout: parsed.isInternalRootLayout }
173
171
  : {
174
172
  renderPage: routeRender,
175
173
  pageIncludesOwnLayout: parsed.leaf === "_index",
@@ -178,48 +176,27 @@ export class RouteTreeBuilder {
178
176
  } as Route);
179
177
  }
180
178
 
181
- #getPathRoutes(
182
- route: Route,
183
- parentRootLayouts: RouteRender[] = [],
184
- parentLayouts: RouteRender[] = [],
185
- parentPaths: string[] = [],
186
- parentHead?: ResolveHead,
187
- parentRootRenders: RouteRender[] = [],
188
- ): PathRoute[] {
189
- const parentPath = parentPaths.filter((p) => p !== "/").join("");
190
- const currentPathSegment = /^\/\(.*\)$/.test(route.path) ? "" : route.path;
191
- const isRoot = this.#baseLayoutPaths.includes(parentPath + currentPathSegment) && parentRootLayouts.length < 2;
192
- const routePath = parentPath + currentPathSegment;
193
- const pathSegments = [...parentPaths, ...(currentPathSegment ? [currentPathSegment] : [])];
194
- const currentRootLayout = isRoot && route.renderLayout ? route.renderLayout : null;
195
-
196
- const currentOverrideRenders = route.renderOverrides ? [route.renderOverrides] : [];
197
- const nodeRenders = [...currentOverrideRenders, ...(route.renderLayout ? [route.renderLayout] : [])];
198
- const rootLayoutStack = [...parentRootLayouts, ...(currentRootLayout ? [currentRootLayout] : [])];
199
- const renderRootLayouts = isRoot ? [...parentRootRenders, ...nodeRenders] : parentRootRenders;
200
- const renderLayouts = isRoot ? parentLayouts : [...parentLayouts, ...nodeRenders];
179
+ #getPathRoutes(route: Route, parent: RouteLayer<RouteRender> | null = null, parentHead?: ResolveHead): PathRoute[] {
180
+ const layer = RouteLayering.of(route, parent);
201
181
  if (route.renderLayout) {
202
182
  this.#fallbackRoutes.push({
203
- path: routePath,
204
- pathSegments,
205
- renderRootLayouts,
206
- renderLayouts,
183
+ path: layer.path,
184
+ pathSegments: layer.pathSegments,
185
+ renderRootLayouts: layer.renderRootLayouts,
186
+ renderLayouts: layer.renderLayouts,
207
187
  });
208
188
  }
209
189
  const routeHead = RouteTreeBuilder.#composeHeadResolvers(route.renderLayout?.resolveHead, parentHead);
210
- const pageNodeRenders = route.pageIncludesOwnLayout === false ? currentOverrideRenders : nodeRenders;
211
- const pageRenderRootLayouts = isRoot ? [...parentRootRenders, ...pageNodeRenders] : parentRootRenders;
212
- const pageRenderLayouts = isRoot ? parentLayouts : [...parentLayouts, ...pageNodeRenders];
213
190
  const pageHead = route.pageIncludesOwnLayout === false ? parentHead : routeHead;
214
191
  return [
215
192
  ...(route.renderPage
216
193
  ? [
217
194
  {
218
- path: routePath,
219
- pathSegments,
195
+ path: layer.path,
196
+ pathSegments: layer.pathSegments,
220
197
  renderPage: route.renderPage,
221
- renderRootLayouts: pageRenderRootLayouts,
222
- renderLayouts: pageRenderLayouts,
198
+ renderRootLayouts: layer.pageRenderRootLayouts,
199
+ renderLayouts: layer.pageRenderLayouts,
223
200
  resolveHead: RouteTreeBuilder.#composeHeadResolvers(route.renderPage.resolveHead, pageHead),
224
201
  isSpecialRoute: route.isSpecialRoute,
225
202
  pageState: route.pageState ?? defaultPageState,
@@ -227,9 +204,7 @@ export class RouteTreeBuilder {
227
204
  ]
228
205
  : []),
229
206
  ...(route.children.size
230
- ? [...route.children.values()].flatMap((child) =>
231
- this.#getPathRoutes(child, rootLayoutStack, renderLayouts, pathSegments, routeHead, renderRootLayouts),
232
- )
207
+ ? [...route.children.values()].flatMap((child) => this.#getPathRoutes(child, layer, routeHead))
233
208
  : []),
234
209
  ];
235
210
  }
@@ -8,6 +8,7 @@ import { copyBunRequestFields, type WebProxyRunner } from "../proxy";
8
8
  import { SignalResolver } from "../resolver";
9
9
  import type { HttpRoutes, SignalRouteOptions, WebsocketRoutes } from "../types";
10
10
  import { AppWsData } from "./appWsData";
11
+ import type { HostAllowlist } from "./hostAllowlist";
11
12
 
12
13
  export interface HmrStateSource {
13
14
  readonly state: {
@@ -34,6 +35,7 @@ export interface ApiRouteInputs {
34
35
  renderEnvRoutes: HttpRoutes;
35
36
  upgradeAppWs: (req: Request, data: AppWsData) => boolean;
36
37
  webProxyRunner?: WebProxyRunner | null;
38
+ hostAllowlist?: HostAllowlist | null;
37
39
  }
38
40
 
39
41
  type RouteValue = NonNullHttpRoutes[keyof NonNullHttpRoutes];
@@ -62,6 +64,7 @@ export class ApiRouter {
62
64
  renderEnvRoutes,
63
65
  upgradeAppWs,
64
66
  webProxyRunner,
67
+ hostAllowlist,
65
68
  }: ApiRouteInputs): NonNullHttpRoutes {
66
69
  const endpointEntries = Object.entries(routes ?? {}).map(
67
70
  ([p, handler]) =>
@@ -76,6 +79,12 @@ export class ApiRouter {
76
79
  const endpointPaths = new Set([...endpointEntries.map(([path]) => path), ...builtinEntries.map(([path]) => path)]);
77
80
  const routeTable = {
78
81
  [`${prefix}${websocketPrefix}` as "/api/ws"]: (req) => {
82
+
83
+ try {
84
+ CrossSiteGuard.assertOrigin(req, new URL(req.url), "websocket");
85
+ } catch {
86
+ return new Response("Forbidden", { status: 403 });
87
+ }
79
88
  const upgraded = upgradeAppWs(req, AppWsData.fromRequest(req));
80
89
  if (upgraded) return;
81
90
  return new Response("Failed to upgrade to WebSocket", { status: 500 });
@@ -84,9 +93,10 @@ export class ApiRouter {
84
93
  ...Object.fromEntries(builtinEntries),
85
94
  ...(renderEnvRoutes ?? {}),
86
95
  } as NonNullHttpRoutes;
87
- return webProxyRunner
96
+ const served = webProxyRunner
88
97
  ? ApiRouter.#wrapRoutesWithWebProxy(routeTable, webProxyRunner, prefix, endpointPaths)
89
98
  : routeTable;
99
+ return hostAllowlist ? ApiRouter.#guardHosts(served, hostAllowlist) : served;
90
100
  }
91
101
 
92
102
  static buildWebsocketHandlers({
@@ -214,6 +224,15 @@ export class ApiRouter {
214
224
  ) as NonNullHttpRoutes;
215
225
  }
216
226
 
227
+ static #guardHosts(routes: NonNullHttpRoutes, allowlist: HostAllowlist): NonNullHttpRoutes {
228
+ return Object.fromEntries(
229
+ Object.entries(routes).map(([path, route]) => [
230
+ path,
231
+ ApiRouter.#mapRoute(route, (handler) => (req) => (allowlist.allows(req) ? handler(req) : allowlist.refuse())),
232
+ ]),
233
+ ) as NonNullHttpRoutes;
234
+ }
235
+
217
236
  static #isApiRoute(path: string, apiPrefix: string): boolean {
218
237
  const normalized = apiPrefix.replace(/\/$/, "");
219
238
  return path === normalized || path.startsWith(`${normalized}/`);
@@ -242,12 +261,16 @@ export class ApiRouter {
242
261
 
243
262
  static #mapRoute(route: RouteValue, wrap: (handler: RouteHandler) => RouteHandler): RouteValue {
244
263
  if (typeof route === "function") return wrap(route as RouteHandler) as RouteValue;
245
- if (route instanceof Response) return wrap(() => route) as RouteValue;
264
+ if (route instanceof Response) return wrap(() => route.clone()) as RouteValue;
246
265
  if (!route || typeof route !== "object") return route;
247
266
  return Object.fromEntries(
248
267
  Object.entries(route).map(([method, handler]) => [
249
268
  method,
250
- typeof handler === "function" ? wrap(handler as RouteHandler) : handler,
269
+ typeof handler === "function"
270
+ ? wrap(handler as RouteHandler)
271
+ : handler instanceof Response
272
+ ? wrap(() => handler.clone())
273
+ : handler,
251
274
  ]),
252
275
  ) as RouteValue;
253
276
  }
@@ -0,0 +1,24 @@
1
+
2
+
3
+ export class HostAllowlist {
4
+ readonly #hosts: Set<string>;
5
+
6
+ constructor(hosts: Iterable<string>) {
7
+ this.#hosts = new Set([...hosts].map((host) => host.trim().toLowerCase()).filter(Boolean));
8
+ }
9
+
10
+ /** `AKAN_ALLOWED_HOSTS`: comma-separated `host:port` entries; unset or blank serves every Host. */
11
+ static fromEnv(env: Record<string, string | undefined> = process.env): HostAllowlist | null {
12
+ const allowlist = new HostAllowlist((env.AKAN_ALLOWED_HOSTS ?? "").split(","));
13
+ return allowlist.#hosts.size ? allowlist : null;
14
+ }
15
+
16
+ allows(req: Request): boolean {
17
+ const host = req.headers.get("host")?.trim().toLowerCase();
18
+ return !!host && this.#hosts.has(host);
19
+ }
20
+
21
+ refuse(): Response {
22
+ return new Response("Forbidden", { status: 403 });
23
+ }
24
+ }
@@ -0,0 +1,25 @@
1
+
2
+
3
+ export class SelfExec {
4
+ static #beBun: string | undefined;
5
+
6
+ static adopt() {
7
+ const value = process.env.BUN_BE_BUN;
8
+ if (value === undefined) return;
9
+ SelfExec.#beBun = value;
10
+ delete process.env.BUN_BE_BUN;
11
+ }
12
+
13
+ /** A desktop app's carried server: only its shell starts the app executable as Bun. */
14
+ static get carried(): boolean {
15
+ return SelfExec.#beBun !== undefined;
16
+ }
17
+
18
+ static env(env: NodeJS.ProcessEnv = process.env): NodeJS.ProcessEnv {
19
+ return SelfExec.#beBun === undefined ? env : { ...env, BUN_BE_BUN: SelfExec.#beBun };
20
+ }
21
+
22
+ static command(script: string, ...args: string[]): string[] {
23
+ return [process.execPath, ...process.execArgv, script, ...args];
24
+ }
25
+ }
package/server/types.tsx CHANGED
@@ -125,7 +125,9 @@ export const getWebConfigFromEnv = (): AkanWebConfig => {
125
125
 
126
126
  export interface MobileDeepLinkAssociation {
127
127
  targetName: string;
128
- appId: string;
128
+ /** Null when the target names no id for the platform; that platform then gets no association. */
129
+ iosAppId: string | null;
130
+ androidAppId: string | null;
129
131
  domains: string[];
130
132
  iosTeamId?: string;
131
133
  androidSha256CertFingerprints?: string[];
@@ -1168,9 +1168,9 @@ export class WebRouter {
1168
1168
  const associations = artifact.deepLinkAssociations ?? [];
1169
1169
  if (pathname === APPLE_APP_SITE_ASSOCIATION_PATH) {
1170
1170
  const details = associations
1171
- .filter((association) => association.domains.length > 0 && association.iosTeamId)
1171
+ .filter((association) => association.domains.length > 0 && association.iosTeamId && association.iosAppId)
1172
1172
  .map((association) => ({
1173
- appIDs: [`${association.iosTeamId}.${association.appId}`],
1173
+ appIDs: [`${association.iosTeamId}.${association.iosAppId}`],
1174
1174
  components: [{ "/": "/*" }],
1175
1175
  }));
1176
1176
  if (details.length === 0) return null;
@@ -1180,14 +1180,17 @@ export class WebRouter {
1180
1180
  const packageSuffixes = process.env.AKAN_PUBLIC_ENV === "main" ? [""] : ["", ".debug"];
1181
1181
  const assetLinks = associations
1182
1182
  .filter(
1183
- (association) => association.domains.length > 0 && (association.androidSha256CertFingerprints?.length ?? 0) > 0,
1183
+ (association) =>
1184
+ association.domains.length > 0 &&
1185
+ !!association.androidAppId &&
1186
+ (association.androidSha256CertFingerprints?.length ?? 0) > 0,
1184
1187
  )
1185
1188
  .flatMap((association) =>
1186
1189
  packageSuffixes.map((suffix) => ({
1187
1190
  relation: ["delegate_permission/common.handle_all_urls"],
1188
1191
  target: {
1189
1192
  namespace: "android_app",
1190
- package_name: `${association.appId}${suffix}`,
1193
+ package_name: `${association.androidAppId}${suffix}`,
1191
1194
  sha256_cert_fingerprints: association.androidSha256CertFingerprints,
1192
1195
  },
1193
1196
  })),
@@ -143,6 +143,7 @@ export type AkanIpcMessage =
143
143
  /** Actual websocket upstream the child bound; may differ from the preferred port when it was in use. */
144
144
  wsUpstream?: Extract<AkanUpstream, { type: "tcp" }>;
145
145
  healthPath?: string;
146
+ crossSite?: { allowedOrigins: string[]; enabled: boolean };
146
147
  }
147
148
  | { type: "backend-ready"; pid: number }
148
149
  | { type: "pubsub.publish"; roomId: string; data: object | object[]; origin?: string }
@@ -163,6 +164,9 @@ export type AkanIpcMessage =
163
164
  /** Hub → child: the lowest severity any subscriber wants; `null` tells the child to stop forwarding. */
164
165
  | { type: "log.level"; minSev: number | null }
165
166
  | { type: "shutdown"; signal?: string }
167
+ /** Desktop server → the shell that started it: the path behind a file picker grant (forServer). */
168
+ | { type: "file.resolve"; id: string; grant: string }
169
+ | { type: "file.resolved"; id: string; path?: string; mode?: "read" | "write" | "folder"; error?: string }
166
170
  | { type: "error"; message: string; stack?: string; pid?: number };
167
171
 
168
172
  export const sendAkanIpc = (message: AkanIpcMessage) => {
@@ -14,16 +14,23 @@ export class CrossSiteGuard {
14
14
  * Android and Windows; both WebViews send exactly that as `Origin`. */
15
15
  static readonly nativeOrigins = ["app://localhost", "https://app.localhost"] as const;
16
16
  static #enabled = true;
17
+ static #allowedOrigins: string[] = [];
17
18
  static #allowed = new Set<string>(CrossSiteGuard.nativeOrigins);
18
19
 
19
20
  /** Applied at boot from the mounting app's `option.ts`; the native shells stay allowed unless disabled. */
20
21
  static configure({ allowedOrigins = [], enabled = true }: CrossSiteOption) {
21
22
  CrossSiteGuard.#enabled = enabled;
23
+ CrossSiteGuard.#allowedOrigins = [...allowedOrigins];
22
24
  CrossSiteGuard.#allowed = new Set([...CrossSiteGuard.nativeOrigins, ...allowedOrigins]);
23
25
  }
24
26
 
27
+ static option(): Required<CrossSiteOption> {
28
+ return { allowedOrigins: [...CrossSiteGuard.#allowedOrigins], enabled: CrossSiteGuard.#enabled };
29
+ }
30
+
25
31
  static reset() {
26
32
  CrossSiteGuard.#enabled = true;
33
+ CrossSiteGuard.#allowedOrigins = [];
27
34
  CrossSiteGuard.#allowed = new Set(CrossSiteGuard.nativeOrigins);
28
35
  }
29
36
 
@@ -20,7 +20,7 @@ export interface SignalTestContext<Fetch = FetchProxy> {
20
20
  export type SignalTestOptions = Pick<TestServerOptions, "storage" | "workerId" | "port" | "serverMode">;
21
21
 
22
22
  type SignalServerModule = {
23
- env: BackendEnv;
23
+ env?: BackendEnv;
24
24
  fetch: FetchProxy;
25
25
  lib: AkanLib;
26
26
  };
@@ -68,6 +68,11 @@ const importServerModule = async (type: "app" | "lib", name: string): Promise<Si
68
68
 
69
69
  const importLibModule = async (name: string): Promise<SignalServerModule> => await import(`@libs/${name}/server`);
70
70
 
71
+ const importTestingEnv = async (type: "app" | "lib", name: string, targetModule: SignalServerModule) =>
72
+ type === "app"
73
+ ? ((await import(`@apps/${name}/env/env.server.testing`)) as { env?: BackendEnv }).env
74
+ : targetModule.env;
75
+
71
76
  export const setupSignalTestTarget = async <Fetch = FetchProxy>(
72
77
  {
73
78
  type = process.env.AKAN_TEST_TARGET_TYPE as "app" | "lib" | undefined,
@@ -96,10 +101,11 @@ export const setupSignalTestTarget = async <Fetch = FetchProxy>(
96
101
  Promise.all((libNames ?? []).filter((libName) => libName !== name).map(importLibModule)),
97
102
  importServerModule(type, name),
98
103
  ]);
104
+ const testingEnv = await importTestingEnv(type, name, targetModule);
99
105
  const target: SignalTestTarget = {
100
106
  type,
101
107
  name,
102
- env: { ...env, ...targetModule.env },
108
+ env: { ...env, ...testingEnv },
103
109
  fetch: targetModule.fetch,
104
110
  libs: [...dependencyModules.map((mod) => mod.lib), targetModule.lib],
105
111
  };
@@ -77,3 +77,4 @@ export declare const getWsPrefix: () => string;
77
77
  export declare const resetEnvCache: () => void;
78
78
  /** Cached after the first call. */
79
79
  export declare const getEnv: () => ClientEnv;
80
+ export declare const getServerOrigin: () => string;
@@ -171,6 +171,8 @@ export interface Route {
171
171
  path: string;
172
172
  renderPage?: RouteRender;
173
173
  renderLayout?: RouteRender;
174
+ /** `renderLayout` is a generated `__root_layout`: a root boundary the page generator found. */
175
+ isRootLayout?: boolean;
174
176
  /** Synthetic layout render from a `_overrides.tsx` at this node; wraps the subtree in a UI-override provider. */
175
177
  renderOverrides?: RouteRender;
176
178
  pageIncludesOwnLayout?: boolean;
@@ -8,6 +8,7 @@ export * from "./device.d.ts";
8
8
  export * from "./frameConfig.d.ts";
9
9
  export * from "./locale.d.ts";
10
10
  export * from "./makePageProto.d.ts";
11
+ export * from "./resolveServerUrl.d.ts";
11
12
  export * from "./route.d.ts";
12
13
  export * from "./router.d.ts";
13
14
  export * from "./rscNavigation.d.ts";
@@ -0,0 +1 @@
1
+ export * from "../../vendor/@akanjs/native/plugins/accessibility/src/index.d.ts";
@@ -0,0 +1 @@
1
+ export * from "../../vendor/@akanjs/native/plugins/app-state/src/index.d.ts";
@@ -0,0 +1 @@
1
+ export * from "../../vendor/@akanjs/native/plugins/app/src/index.d.ts";
@@ -0,0 +1 @@
1
+ export * from "../../vendor/@akanjs/native/plugins/appearance/src/index.d.ts";
@@ -0,0 +1 @@
1
+ export * from "../../vendor/@akanjs/native/plugins/auth-session/src/index.d.ts";
@@ -0,0 +1 @@
1
+ export * from "../../vendor/@akanjs/native/plugins/autostart/src/index.d.ts";
@@ -0,0 +1 @@
1
+ export * from "../../vendor/@akanjs/native/plugins/badge/src/index.d.ts";
@@ -0,0 +1 @@
1
+ export * from "../../vendor/@akanjs/native/plugins/biometric/src/index.d.ts";
@@ -0,0 +1 @@
1
+ export * from "../../vendor/@akanjs/native/plugins/browser/src/index.d.ts";
@@ -0,0 +1 @@
1
+ export * from "../../vendor/@akanjs/native/plugins/camera/src/index.d.ts";
@@ -0,0 +1 @@
1
+ export * from "../../vendor/@akanjs/native/plugins/clipboard/src/index.d.ts";
@@ -0,0 +1 @@
1
+ export * from "../../vendor/@akanjs/native/plugins/contacts/src/index.d.ts";
@@ -0,0 +1 @@
1
+ export * from "../../vendor/@akanjs/native/plugins/device/src/index.d.ts";
@@ -0,0 +1 @@
1
+ export * from "../../vendor/@akanjs/native/plugins/dialog/src/index.d.ts";
@@ -0,0 +1 @@
1
+ export * from "../../vendor/@akanjs/native/plugins/dock/src/index.d.ts";
@@ -0,0 +1 @@
1
+ export * from "../../vendor/@akanjs/native/plugins/file-picker/src/index.d.ts";
@@ -0,0 +1 @@
1
+ export * from "../../vendor/@akanjs/native/plugins/filesystem/src/index.d.ts";
@@ -0,0 +1 @@
1
+ export * from "../../vendor/@akanjs/native/plugins/geolocation/src/index.d.ts";
@@ -0,0 +1 @@
1
+ export * from "../../vendor/@akanjs/native/plugins/global-shortcut/src/index.d.ts";
@@ -0,0 +1 @@
1
+ export * from "../../vendor/@akanjs/native/plugins/haptics/src/index.d.ts";
@@ -0,0 +1 @@
1
+ export * from "../../vendor/@akanjs/native/plugins/http/src/index.d.ts";
@@ -0,0 +1 @@
1
+ export * from "../../vendor/@akanjs/native/plugins/iap/src/index.d.ts";
@@ -0,0 +1 @@
1
+ export * from "../../vendor/@akanjs/native/plugins/keep-awake/src/index.d.ts";
@@ -0,0 +1 @@
1
+ export * from "../../vendor/@akanjs/native/plugins/keyboard/src/index.d.ts";
@@ -0,0 +1 @@
1
+ export * from "../../vendor/@akanjs/native/plugins/local-notifications/src/index.d.ts";
@@ -0,0 +1 @@
1
+ export * from "../../vendor/@akanjs/native/plugins/menu/src/index.d.ts";
@@ -0,0 +1 @@
1
+ export * from "../../vendor/@akanjs/native/plugins/network/src/index.d.ts";
@@ -0,0 +1 @@
1
+ export * from "../../vendor/@akanjs/native/plugins/opener/src/index.d.ts";
@@ -0,0 +1 @@
1
+ export * from "../../vendor/@akanjs/native/plugins/preferences/src/index.d.ts";
@@ -0,0 +1 @@
1
+ export * from "../../vendor/@akanjs/native/plugins/push/src/index.d.ts";
@@ -0,0 +1 @@
1
+ export * from "../../vendor/@akanjs/native/plugins/screen-orientation/src/index.d.ts";
@@ -0,0 +1 @@
1
+ export * from "../../vendor/@akanjs/native/plugins/screen/src/index.d.ts";
@@ -0,0 +1 @@
1
+ export * from "../../vendor/@akanjs/native/plugins/secure-storage/src/index.d.ts";
@@ -0,0 +1 @@
1
+ export * from "../../vendor/@akanjs/native/plugins/share/src/index.d.ts";
@@ -0,0 +1 @@
1
+ export * from "../../vendor/@akanjs/native/plugins/single-instance/src/index.d.ts";
@@ -0,0 +1 @@
1
+ export * from "../../vendor/@akanjs/native/plugins/splash-screen/src/index.d.ts";
@@ -0,0 +1 @@
1
+ export * from "../../vendor/@akanjs/native/plugins/sqlite/src/index.d.ts";
@@ -0,0 +1 @@
1
+ export * from "../../vendor/@akanjs/native/plugins/toast/src/index.d.ts";
@@ -0,0 +1 @@
1
+ export * from "../../vendor/@akanjs/native/plugins/tray/src/index.d.ts";
@@ -0,0 +1 @@
1
+ export * from "../../vendor/@akanjs/native/plugins/updates/src/index.d.ts";
@@ -0,0 +1 @@
1
+ export * from "../../vendor/@akanjs/native/plugins/volume/src/index.d.ts";
@@ -0,0 +1 @@
1
+ export * from "../../vendor/@akanjs/native/plugins/window-state/src/index.d.ts";
@@ -0,0 +1 @@
1
+ export * from "../../vendor/@akanjs/native/plugins/window/src/index.d.ts";
@@ -1,13 +1,18 @@
1
1
  import { browser } from "../vendor/@akanjs/native/plugins/browser/src/index.d.ts";
2
2
  import { opener } from "../vendor/@akanjs/native/plugins/opener/src/index.d.ts";
3
- export { AkanNativeError, fileBlob, releaseFile } from "../vendor/@akanjs/native/packages/core/src/index.d.ts";
3
+ export type { LiveValue, Plugin } from "../vendor/@akanjs/native/packages/core/src/index.d.ts";
4
+ export { AkanNativeError, createLiveValue, definePlugin, defineWebPlugin, fileBlob, isAkanNativeError, releaseFile, shallowEqual, } from "../vendor/@akanjs/native/packages/core/src/index.d.ts";
4
5
  export { app } from "../vendor/@akanjs/native/plugins/app/src/index.d.ts";
5
6
  export { appState } from "../vendor/@akanjs/native/plugins/app-state/src/index.d.ts";
7
+ export type { AuthSessionApi } from "../vendor/@akanjs/native/plugins/auth-session/src/index.d.ts";
8
+ export { authSession } from "../vendor/@akanjs/native/plugins/auth-session/src/index.d.ts";
6
9
  export type { Photo } from "../vendor/@akanjs/native/plugins/camera/src/index.d.ts";
7
10
  export type { Contact } from "../vendor/@akanjs/native/plugins/contacts/src/index.d.ts";
8
11
  export type { DeviceInfo } from "../vendor/@akanjs/native/plugins/device/src/index.d.ts";
9
12
  export { device } from "../vendor/@akanjs/native/plugins/device/src/index.d.ts";
10
13
  export { dialog } from "../vendor/@akanjs/native/plugins/dialog/src/index.d.ts";
14
+ export type { DirectoryFile, PickDirectoryOptions, PickDirectoryResult, PickedFile, PickFilesOptions, SaveFileOptions, SaveFileResult, } from "../vendor/@akanjs/native/plugins/file-picker/src/index.d.ts";
15
+ export { filePicker } from "../vendor/@akanjs/native/plugins/file-picker/src/index.d.ts";
11
16
  export type { Position } from "../vendor/@akanjs/native/plugins/geolocation/src/index.d.ts";
12
17
  export { haptics } from "../vendor/@akanjs/native/plugins/haptics/src/index.d.ts";
13
18
  export type { IapProduct, IapTransaction } from "../vendor/@akanjs/native/plugins/iap/src/index.d.ts";
@@ -16,6 +21,11 @@ export { preferences } from "../vendor/@akanjs/native/plugins/preferences/src/in
16
21
  export type { PushAction, PushMessage, PushPermissionState, PushToken as NativePushToken, } from "../vendor/@akanjs/native/plugins/push/src/index.d.ts";
17
22
  export { push } from "../vendor/@akanjs/native/plugins/push/src/index.d.ts";
18
23
  export { secureStorage } from "../vendor/@akanjs/native/plugins/secure-storage/src/index.d.ts";
24
+ export type { UpdateCheck, UpdateProgress, UpdateState } from "../vendor/@akanjs/native/plugins/updates/src/index.d.ts";
25
+ export { markReady, updates, useUpdateState } from "../vendor/@akanjs/native/plugins/updates/src/index.d.ts";
26
+ export type { VolumeState } from "../vendor/@akanjs/native/plugins/volume/src/index.d.ts";
27
+ export { volume } from "../vendor/@akanjs/native/plugins/volume/src/index.d.ts";
28
+ export { useLiveValue, usePluginEvent } from "../vendor/@akanjs/native/packages/react/src/index.d.ts";
19
29
  export { browser, opener };
20
30
  export declare const loadCamera: () => Promise<typeof import("../vendor/@akanjs/native/plugins/camera/src/index.d.ts")>;
21
31
  export declare const loadContacts: () => Promise<typeof import("../vendor/@akanjs/native/plugins/contacts/src/index.d.ts")>;
@@ -23,7 +33,9 @@ export declare const loadGeolocation: () => Promise<typeof import("../vendor/@ak
23
33
  export declare const loadIap: () => Promise<typeof import("../vendor/@akanjs/native/plugins/iap/src/index.d.ts")>;
24
34
  /** Inside an iOS or Android shell of the native runtime; a mobile target opened in a browser is not. */
25
35
  export declare const isNativeApp: () => boolean;
36
+ /** Inside any shell of the native runtime, desktop included. */
37
+ export declare const isNativeShell: () => boolean;
26
38
  export declare const nativePlatform: () => "ios" | "android" | null;
27
- export declare const desktopPlatform: () => "windows" | "macos" | "linux" | null;
39
+ export declare const desktopPlatform: () => "macos" | "windows" | "linux" | null;
28
40
  /** http(s) in the in-app browser, anything else (mailto:, tel:) in the app the system picks. */
29
41
  export declare const openExternalUrl: (url: string) => Promise<void>;
@@ -0,0 +1 @@
1
+ export declare const resolveServerUrl: (url: string) => string;
@@ -76,6 +76,9 @@ declare class Router {
76
76
  backOrFallback(fallbackHref?: string, routeOptions?: RouteOptions): never;
77
77
  back(routeOptions?: RouteOptions): never;
78
78
  refresh(): never;
79
+ /** Client-side redirects so far: a render that saw this change while it ran redirected instead of drawing. A method
80
+ * for the reason `navigation()` is one. */
81
+ redirectCount(): number;
79
82
  redirect(href: string, options?: RedirectOptions): never;
80
83
  notFound(): never;
81
84
  setLang(lang: string): never;
@@ -0,0 +1,28 @@
1
+ interface LayeredRoute<Render> {
2
+ /** The tree segment: `/`, `/:lang`, `/(group)`, `/foo`. */
3
+ path: string;
4
+ renderLayout?: Render;
5
+ /** A generated `__root_layout`: one of the root boundaries the page generator found. */
6
+ isRootLayout?: boolean;
7
+ renderOverrides?: Render;
8
+ /** `false` for a named page file, which sits beside its directory's layout rather than under it. */
9
+ pageIncludesOwnLayout?: boolean;
10
+ }
11
+ export interface RouteLayer<Render> {
12
+ /** The URL pattern down to this node; a route group adds nothing to it. */
13
+ path: string;
14
+ pathSegments: string[];
15
+ isRoot: boolean;
16
+ /** The root layout above or at this node, overrides left out: once there is one, nothing below is a root. */
17
+ rootLayouts: Render[];
18
+ renderRootLayouts: Render[];
19
+ renderLayouts: Render[];
20
+ /** What a page file at this node renders inside: without this node's layout for a named page file. */
21
+ pageRenderRootLayouts: Render[];
22
+ pageRenderLayouts: Render[];
23
+ }
24
+ export declare class RouteLayering {
25
+ #private;
26
+ static of<Render>(route: LayeredRoute<Render>, parent: RouteLayer<Render> | null): RouteLayer<Render>;
27
+ }
28
+ export {};