@greensecurity/javascript-sdk 0.46.0-beta.9 → 0.47.0-beta.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 (223) hide show
  1. package/dist/commonjs/__tests__/apps/contract.test.js +288 -4
  2. package/dist/commonjs/__tests__/apps/contract.test.js.map +1 -1
  3. package/dist/commonjs/__tests__/apps/devpreview-routes.test.d.ts +2 -0
  4. package/dist/commonjs/__tests__/apps/devpreview-routes.test.d.ts.map +1 -0
  5. package/dist/commonjs/__tests__/apps/devpreview-routes.test.js +86 -0
  6. package/dist/commonjs/__tests__/apps/devpreview-routes.test.js.map +1 -0
  7. package/dist/commonjs/__tests__/apps/kit/example.test.js +84 -4
  8. package/dist/commonjs/__tests__/apps/kit/example.test.js.map +1 -1
  9. package/dist/commonjs/__tests__/apps/kit/pages.test.d.ts +2 -0
  10. package/dist/commonjs/__tests__/apps/kit/pages.test.d.ts.map +1 -0
  11. package/dist/commonjs/__tests__/apps/kit/pages.test.js +119 -0
  12. package/dist/commonjs/__tests__/apps/kit/pages.test.js.map +1 -0
  13. package/dist/commonjs/__tests__/apps/kit/render.test.js +24 -0
  14. package/dist/commonjs/__tests__/apps/kit/render.test.js.map +1 -1
  15. package/dist/commonjs/__tests__/apps/kit/store.test.d.ts +2 -0
  16. package/dist/commonjs/__tests__/apps/kit/store.test.d.ts.map +1 -0
  17. package/dist/commonjs/__tests__/apps/kit/store.test.js +123 -0
  18. package/dist/commonjs/__tests__/apps/kit/store.test.js.map +1 -0
  19. package/dist/commonjs/__tests__/apps/post-install-page.test.d.ts +2 -0
  20. package/dist/commonjs/__tests__/apps/post-install-page.test.d.ts.map +1 -0
  21. package/dist/commonjs/__tests__/apps/post-install-page.test.js +110 -0
  22. package/dist/commonjs/__tests__/apps/post-install-page.test.js.map +1 -0
  23. package/dist/commonjs/__tests__/apps/routes.test.d.ts +2 -0
  24. package/dist/commonjs/__tests__/apps/routes.test.d.ts.map +1 -0
  25. package/dist/commonjs/__tests__/apps/routes.test.js +238 -0
  26. package/dist/commonjs/__tests__/apps/routes.test.js.map +1 -0
  27. package/dist/commonjs/__tests__/vendors.test.js +1 -1
  28. package/dist/commonjs/__tests__/zoneDevicesCreate.test.d.ts +2 -0
  29. package/dist/commonjs/__tests__/zoneDevicesCreate.test.d.ts.map +1 -0
  30. package/dist/commonjs/__tests__/zoneDevicesCreate.test.js +47 -0
  31. package/dist/commonjs/__tests__/zoneDevicesCreate.test.js.map +1 -0
  32. package/dist/commonjs/__tests__/zones.test.js +4 -28
  33. package/dist/commonjs/__tests__/zones.test.js.map +1 -1
  34. package/dist/commonjs/admin-funcs/zoneDevicesCreate.d.ts +11 -0
  35. package/dist/commonjs/admin-funcs/zoneDevicesCreate.d.ts.map +1 -0
  36. package/dist/commonjs/admin-funcs/zoneDevicesCreate.js +14 -0
  37. package/dist/commonjs/admin-funcs/zoneDevicesCreate.js.map +1 -0
  38. package/dist/commonjs/admin-funcs/zoneDevicesMacAvailability.d.ts +3 -0
  39. package/dist/commonjs/admin-funcs/zoneDevicesMacAvailability.d.ts.map +1 -0
  40. package/dist/commonjs/admin-funcs/zoneDevicesMacAvailability.js +11 -0
  41. package/dist/commonjs/admin-funcs/zoneDevicesMacAvailability.js.map +1 -0
  42. package/dist/commonjs/apps/components.d.ts +321 -0
  43. package/dist/commonjs/apps/components.d.ts.map +1 -1
  44. package/dist/commonjs/apps/components.js +168 -2
  45. package/dist/commonjs/apps/components.js.map +1 -1
  46. package/dist/commonjs/apps/context.d.ts +24 -0
  47. package/dist/commonjs/apps/context.d.ts.map +1 -1
  48. package/dist/commonjs/apps/context.js.map +1 -1
  49. package/dist/commonjs/apps/devpreview.d.ts +20 -3
  50. package/dist/commonjs/apps/devpreview.d.ts.map +1 -1
  51. package/dist/commonjs/apps/devpreview.js +18 -7
  52. package/dist/commonjs/apps/devpreview.js.map +1 -1
  53. package/dist/commonjs/apps/hooks/index.d.ts +33 -2
  54. package/dist/commonjs/apps/hooks/index.d.ts.map +1 -1
  55. package/dist/commonjs/apps/hooks/index.js +41 -1
  56. package/dist/commonjs/apps/hooks/index.js.map +1 -1
  57. package/dist/commonjs/apps/hooks/store.d.ts +48 -0
  58. package/dist/commonjs/apps/hooks/store.d.ts.map +1 -0
  59. package/dist/commonjs/apps/hooks/store.js +87 -0
  60. package/dist/commonjs/apps/hooks/store.js.map +1 -0
  61. package/dist/commonjs/apps/manifest.d.ts +64 -3
  62. package/dist/commonjs/apps/manifest.d.ts.map +1 -1
  63. package/dist/commonjs/apps/manifest.js +128 -27
  64. package/dist/commonjs/apps/manifest.js.map +1 -1
  65. package/dist/commonjs/apps/protocol.d.ts +1 -1
  66. package/dist/commonjs/apps/protocol.d.ts.map +1 -1
  67. package/dist/commonjs/apps/route.d.ts +11 -1
  68. package/dist/commonjs/apps/route.d.ts.map +1 -1
  69. package/dist/commonjs/apps/route.js +13 -7
  70. package/dist/commonjs/apps/route.js.map +1 -1
  71. package/dist/commonjs/apps/routes.d.ts +65 -0
  72. package/dist/commonjs/apps/routes.d.ts.map +1 -0
  73. package/dist/commonjs/apps/routes.js +202 -0
  74. package/dist/commonjs/apps/routes.js.map +1 -0
  75. package/dist/commonjs/apps/surfaces.d.ts +20 -2
  76. package/dist/commonjs/apps/surfaces.d.ts.map +1 -1
  77. package/dist/commonjs/apps/surfaces.js +45 -7
  78. package/dist/commonjs/apps/surfaces.js.map +1 -1
  79. package/dist/commonjs/apps/testing/index.d.ts +17 -1
  80. package/dist/commonjs/apps/testing/index.d.ts.map +1 -1
  81. package/dist/commonjs/apps/testing/index.js +50 -1
  82. package/dist/commonjs/apps/testing/index.js.map +1 -1
  83. package/dist/commonjs/apps/ui/index.d.ts +13 -0
  84. package/dist/commonjs/apps/ui/index.d.ts.map +1 -1
  85. package/dist/commonjs/apps/ui/index.js +19 -2
  86. package/dist/commonjs/apps/ui/index.js.map +1 -1
  87. package/dist/commonjs/index.extras.d.ts +4 -0
  88. package/dist/commonjs/index.extras.d.ts.map +1 -1
  89. package/dist/commonjs/index.extras.js +5 -1
  90. package/dist/commonjs/index.extras.js.map +1 -1
  91. package/dist/commonjs/lib/config.d.ts +3 -3
  92. package/dist/commonjs/lib/config.js +3 -3
  93. package/dist/commonjs/models/components/vendorcredentialsummary.d.ts +7 -0
  94. package/dist/commonjs/models/components/vendorcredentialsummary.d.ts.map +1 -1
  95. package/dist/commonjs/models/components/vendorcredentialsummary.js +2 -0
  96. package/dist/commonjs/models/components/vendorcredentialsummary.js.map +1 -1
  97. package/dist/esm/__tests__/apps/contract.test.js +289 -5
  98. package/dist/esm/__tests__/apps/contract.test.js.map +1 -1
  99. package/dist/esm/__tests__/apps/devpreview-routes.test.d.ts +2 -0
  100. package/dist/esm/__tests__/apps/devpreview-routes.test.d.ts.map +1 -0
  101. package/dist/esm/__tests__/apps/devpreview-routes.test.js +84 -0
  102. package/dist/esm/__tests__/apps/devpreview-routes.test.js.map +1 -0
  103. package/dist/esm/__tests__/apps/kit/example.test.js +85 -5
  104. package/dist/esm/__tests__/apps/kit/example.test.js.map +1 -1
  105. package/dist/esm/__tests__/apps/kit/pages.test.d.ts +2 -0
  106. package/dist/esm/__tests__/apps/kit/pages.test.d.ts.map +1 -0
  107. package/dist/esm/__tests__/apps/kit/pages.test.js +117 -0
  108. package/dist/esm/__tests__/apps/kit/pages.test.js.map +1 -0
  109. package/dist/esm/__tests__/apps/kit/render.test.js +25 -1
  110. package/dist/esm/__tests__/apps/kit/render.test.js.map +1 -1
  111. package/dist/esm/__tests__/apps/kit/store.test.d.ts +2 -0
  112. package/dist/esm/__tests__/apps/kit/store.test.d.ts.map +1 -0
  113. package/dist/esm/__tests__/apps/kit/store.test.js +121 -0
  114. package/dist/esm/__tests__/apps/kit/store.test.js.map +1 -0
  115. package/dist/esm/__tests__/apps/post-install-page.test.d.ts +2 -0
  116. package/dist/esm/__tests__/apps/post-install-page.test.d.ts.map +1 -0
  117. package/dist/esm/__tests__/apps/post-install-page.test.js +108 -0
  118. package/dist/esm/__tests__/apps/post-install-page.test.js.map +1 -0
  119. package/dist/esm/__tests__/apps/routes.test.d.ts +2 -0
  120. package/dist/esm/__tests__/apps/routes.test.d.ts.map +1 -0
  121. package/dist/esm/__tests__/apps/routes.test.js +236 -0
  122. package/dist/esm/__tests__/apps/routes.test.js.map +1 -0
  123. package/dist/esm/__tests__/vendors.test.js +1 -1
  124. package/dist/esm/__tests__/zoneDevicesCreate.test.d.ts +2 -0
  125. package/dist/esm/__tests__/zoneDevicesCreate.test.d.ts.map +1 -0
  126. package/dist/esm/__tests__/zoneDevicesCreate.test.js +45 -0
  127. package/dist/esm/__tests__/zoneDevicesCreate.test.js.map +1 -0
  128. package/dist/esm/__tests__/zones.test.js +4 -28
  129. package/dist/esm/__tests__/zones.test.js.map +1 -1
  130. package/dist/esm/admin-funcs/zoneDevicesCreate.d.ts +11 -0
  131. package/dist/esm/admin-funcs/zoneDevicesCreate.d.ts.map +1 -0
  132. package/dist/esm/admin-funcs/zoneDevicesCreate.js +11 -0
  133. package/dist/esm/admin-funcs/zoneDevicesCreate.js.map +1 -0
  134. package/dist/esm/admin-funcs/zoneDevicesMacAvailability.d.ts +3 -0
  135. package/dist/esm/admin-funcs/zoneDevicesMacAvailability.d.ts.map +1 -0
  136. package/dist/esm/admin-funcs/zoneDevicesMacAvailability.js +8 -0
  137. package/dist/esm/admin-funcs/zoneDevicesMacAvailability.js.map +1 -0
  138. package/dist/esm/apps/components.d.ts +321 -0
  139. package/dist/esm/apps/components.d.ts.map +1 -1
  140. package/dist/esm/apps/components.js +163 -1
  141. package/dist/esm/apps/components.js.map +1 -1
  142. package/dist/esm/apps/context.d.ts +24 -0
  143. package/dist/esm/apps/context.d.ts.map +1 -1
  144. package/dist/esm/apps/context.js.map +1 -1
  145. package/dist/esm/apps/devpreview.d.ts +20 -3
  146. package/dist/esm/apps/devpreview.d.ts.map +1 -1
  147. package/dist/esm/apps/devpreview.js +19 -8
  148. package/dist/esm/apps/devpreview.js.map +1 -1
  149. package/dist/esm/apps/hooks/index.d.ts +33 -2
  150. package/dist/esm/apps/hooks/index.d.ts.map +1 -1
  151. package/dist/esm/apps/hooks/index.js +37 -1
  152. package/dist/esm/apps/hooks/index.js.map +1 -1
  153. package/dist/esm/apps/hooks/store.d.ts +48 -0
  154. package/dist/esm/apps/hooks/store.d.ts.map +1 -0
  155. package/dist/esm/apps/hooks/store.js +84 -0
  156. package/dist/esm/apps/hooks/store.js.map +1 -0
  157. package/dist/esm/apps/manifest.d.ts +64 -3
  158. package/dist/esm/apps/manifest.d.ts.map +1 -1
  159. package/dist/esm/apps/manifest.js +116 -26
  160. package/dist/esm/apps/manifest.js.map +1 -1
  161. package/dist/esm/apps/protocol.d.ts +1 -1
  162. package/dist/esm/apps/protocol.d.ts.map +1 -1
  163. package/dist/esm/apps/route.d.ts +11 -1
  164. package/dist/esm/apps/route.d.ts.map +1 -1
  165. package/dist/esm/apps/route.js +14 -8
  166. package/dist/esm/apps/route.js.map +1 -1
  167. package/dist/esm/apps/routes.d.ts +65 -0
  168. package/dist/esm/apps/routes.d.ts.map +1 -0
  169. package/dist/esm/apps/routes.js +194 -0
  170. package/dist/esm/apps/routes.js.map +1 -0
  171. package/dist/esm/apps/surfaces.d.ts +20 -2
  172. package/dist/esm/apps/surfaces.d.ts.map +1 -1
  173. package/dist/esm/apps/surfaces.js +39 -2
  174. package/dist/esm/apps/surfaces.js.map +1 -1
  175. package/dist/esm/apps/testing/index.d.ts +17 -1
  176. package/dist/esm/apps/testing/index.d.ts.map +1 -1
  177. package/dist/esm/apps/testing/index.js +49 -1
  178. package/dist/esm/apps/testing/index.js.map +1 -1
  179. package/dist/esm/apps/ui/index.d.ts +13 -0
  180. package/dist/esm/apps/ui/index.d.ts.map +1 -1
  181. package/dist/esm/apps/ui/index.js +17 -1
  182. package/dist/esm/apps/ui/index.js.map +1 -1
  183. package/dist/esm/index.extras.d.ts +4 -0
  184. package/dist/esm/index.extras.d.ts.map +1 -1
  185. package/dist/esm/index.extras.js +5 -1
  186. package/dist/esm/index.extras.js.map +1 -1
  187. package/dist/esm/lib/config.d.ts +3 -3
  188. package/dist/esm/lib/config.js +3 -3
  189. package/dist/esm/models/components/vendorcredentialsummary.d.ts +7 -0
  190. package/dist/esm/models/components/vendorcredentialsummary.d.ts.map +1 -1
  191. package/dist/esm/models/components/vendorcredentialsummary.js +2 -0
  192. package/dist/esm/models/components/vendorcredentialsummary.js.map +1 -1
  193. package/examples/apps/hello-green/src/store.ts +15 -0
  194. package/jsr.json +1 -1
  195. package/package.json +1 -1
  196. package/src/__tests__/apps/contract.test.ts +305 -4
  197. package/src/__tests__/apps/devpreview-routes.test.ts +103 -0
  198. package/src/__tests__/apps/kit/example.test.ts +94 -5
  199. package/src/__tests__/apps/kit/pages.test.ts +137 -0
  200. package/src/__tests__/apps/kit/render.test.ts +35 -1
  201. package/src/__tests__/apps/kit/store.test.ts +149 -0
  202. package/src/__tests__/apps/post-install-page.test.ts +126 -0
  203. package/src/__tests__/apps/routes.test.ts +318 -0
  204. package/src/__tests__/vendors.test.ts +1 -1
  205. package/src/__tests__/zoneDevicesCreate.test.ts +56 -0
  206. package/src/__tests__/zones.test.ts +4 -28
  207. package/src/admin-funcs/zoneDevicesCreate.ts +22 -0
  208. package/src/admin-funcs/zoneDevicesMacAvailability.ts +8 -0
  209. package/src/apps/components.ts +183 -1
  210. package/src/apps/context.ts +25 -0
  211. package/src/apps/devpreview.ts +20 -7
  212. package/src/apps/hooks/index.ts +42 -2
  213. package/src/apps/hooks/store.ts +115 -0
  214. package/src/apps/manifest.ts +151 -27
  215. package/src/apps/protocol.ts +1 -1
  216. package/src/apps/route.ts +22 -10
  217. package/src/apps/routes.ts +230 -0
  218. package/src/apps/surfaces.ts +45 -12
  219. package/src/apps/testing/index.ts +48 -2
  220. package/src/apps/ui/index.ts +18 -1
  221. package/src/index.extras.ts +5 -1
  222. package/src/lib/config.ts +3 -3
  223. package/src/models/components/vendorcredentialsummary.ts +9 -0
@@ -6,7 +6,10 @@
6
6
  * Import as `@greensecurity/javascript-sdk/apps/hooks`.
7
7
  */
8
8
  Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.createAppStore = void 0;
9
10
  exports.useExtensionContext = useExtensionContext;
11
+ exports.usePageRoute = usePageRoute;
12
+ exports.useNavParams = useNavParams;
10
13
  exports.useHostApi = useHostApi;
11
14
  exports.showToast = showToast;
12
15
  exports.navigate = navigate;
@@ -14,6 +17,8 @@ const react_1 = require("react");
14
17
  const protocol_js_1 = require("../protocol.js");
15
18
  const route_js_1 = require("../route.js");
16
19
  const session_js_1 = require("../runtime/session.js");
20
+ var store_js_1 = require("./store.js");
21
+ Object.defineProperty(exports, "createAppStore", { enumerable: true, get: function () { return store_js_1.createAppStore; } });
17
22
  const TOAST_TYPES = ["success", "caution", "pending"];
18
23
  function useSessionValue(hook) {
19
24
  const value = (0, react_1.useContext)(session_js_1.AppSessionContext);
@@ -26,6 +31,37 @@ function useSessionValue(hook) {
26
31
  function useExtensionContext() {
27
32
  return useSessionValue("useExtensionContext").context;
28
33
  }
34
+ /**
35
+ * The app page being shown, on views rendered as one of the app's `ui_extensions.routes`
36
+ * (`environment.route`): the declared `path` (`/reports/:id`), the concrete `pathname`
37
+ * (`/reports/7`) and the decoded `params` (`{ id: "7" }`, always strings). `undefined` on every
38
+ * other view. Re-renders with the new route when the host moves to another path of the same page
39
+ * (an `updateContext`), so read params here rather than once on mount.
40
+ *
41
+ * ```tsx
42
+ * const route = usePageRoute();
43
+ * const id = Number(route?.params["id"]);
44
+ * ```
45
+ */
46
+ function usePageRoute() {
47
+ return useSessionValue("usePageRoute").context.environment.route;
48
+ }
49
+ const NO_PARAMS = Object.freeze({});
50
+ /**
51
+ * The `envParams` this app passed to the `navigate` call that opened this view
52
+ * (`environment.navParams`). Only this app can set them, and they never appear in an address, so
53
+ * they are the place for navigation data the view relies on. The page's URL query
54
+ * (`environment.queryParams`) is different: anyone can put anything in a link, so treat it as
55
+ * untrusted input. Empty (`{}`) when the view did not open from a navigation.
56
+ *
57
+ * ```tsx
58
+ * // VendorPanel: navigate({ type: "appPage", path: "/reports", envParams: { vendorId: "42" } })
59
+ * const { vendorId } = useNavParams();
60
+ * ```
61
+ */
62
+ function useNavParams() {
63
+ return useSessionValue("useNavParams").context.environment.navParams ?? NO_PARAMS;
64
+ }
29
65
  /** The host API proxy for the view being rendered. Every method is async. */
30
66
  function useHostApi() {
31
67
  return useSessionValue("useHostApi").host;
@@ -58,7 +94,11 @@ function showToast(message, opts = {}) {
58
94
  return Promise.reject(error);
59
95
  }
60
96
  }
61
- /** Asks the host to navigate. Apps name a destination; the host owns its routes. */
97
+ /**
98
+ * Asks the host to navigate. Apps name a destination; the host owns its routes. To open one of
99
+ * the app's own pages, pass a concrete path in the app's space:
100
+ * `navigate({ type: "appPage", path: "/reports/7" })` (not `/reports/:id`).
101
+ */
62
102
  function navigate(to, opts = {}) {
63
103
  if (!(0, route_js_1.isRouteDescriptor)(to))
64
104
  return Promise.reject(new TypeError("navigate() needs a valid RouteDescriptor"));
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/apps/hooks/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/apps/hooks/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,48 @@
1
+ export type AppStoreListener<T> = (state: T, previous: T) => void;
2
+ /** Fields to change, or a function of the current state that returns them. */
3
+ export type AppStoreUpdate<T> = Partial<T> | ((state: T) => Partial<T>);
4
+ export interface AppStore<T extends object> {
5
+ /** The current state. The same object until a `setState` changes a field. */
6
+ getState(): T;
7
+ /**
8
+ * Shallow-merges `update` into the state, like React's class `setState`: fields left out keep
9
+ * their values. A new state object is made, and listeners run, only when a field changes
10
+ * (`Object.is`), so setting a field to its current value re-renders nothing.
11
+ */
12
+ setState(update: AppStoreUpdate<T>): void;
13
+ /** Calls `listener` after each change, with the new and the previous state. Returns the unsubscribe. */
14
+ subscribe(listener: AppStoreListener<T>): () => void;
15
+ /**
16
+ * Reads the store in a component and re-renders it when the value changes. Without a selector
17
+ * the component re-renders on every change; with one, only when the selected value changes
18
+ * (`Object.is`), so select a field (or derive a primitive) rather than build a new object.
19
+ * The subscription ends when the component unmounts.
20
+ */
21
+ useStore(): T;
22
+ useStore<S>(selector: (state: T) => S): S;
23
+ }
24
+ /**
25
+ * Creates a store that every view of this app can read and update. Create it once, at module
26
+ * level, in a file the views import:
27
+ *
28
+ * ```ts
29
+ * // src/store.ts
30
+ * export const notes = createAppStore({ saved: 0 });
31
+ * // in a view
32
+ * const saved = notes.useStore((s) => s.saved);
33
+ * notes.setState((s) => ({ saved: s.saved + 1 }));
34
+ * ```
35
+ *
36
+ * What it guarantees, and what it does not:
37
+ * - Shared by every view of this app that is open in the same browser tab (web) or the same app
38
+ * session (iOS, Android): a ContextView, its FocusView, the settings page and the app's pages
39
+ * all see one state, and an update in one re-renders the others.
40
+ * - In memory only. Nothing is written to storage or sent anywhere.
41
+ * - Lost whenever the sandbox restarts: after a crash, a rebuild in a dev preview, sign-out, a
42
+ * reload of the web page, or a restart of the mobile app. Each restart begins again from
43
+ * `initial`. Keep anything that must last in your own backend or the Secret Store.
44
+ * - Never shared with other apps: each app has its own sandbox.
45
+ * - Not shared across devices, browser tabs or users.
46
+ */
47
+ export declare function createAppStore<T extends object>(initial: T): AppStore<T>;
48
+ //# sourceMappingURL=store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../../../src/apps/hooks/store.ts"],"names":[],"mappings":"AAYA,MAAM,MAAM,gBAAgB,CAAC,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,KAAK,IAAI,CAAC;AAElE,8EAA8E;AAC9E,MAAM,MAAM,cAAc,CAAC,CAAC,IAAI,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC;AAExE,MAAM,WAAW,QAAQ,CAAC,CAAC,SAAS,MAAM;IACxC,6EAA6E;IAC7E,QAAQ,IAAI,CAAC,CAAC;IACd;;;;OAIG;IACH,QAAQ,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC;IAC1C,wGAAwG;IACxG,SAAS,CAAC,QAAQ,EAAE,gBAAgB,CAAC,CAAC,CAAC,GAAG,MAAM,IAAI,CAAC;IACrD;;;;;OAKG;IACH,QAAQ,IAAI,CAAC,CAAC;IACd,QAAQ,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;CAC3C;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,cAAc,CAAC,CAAC,SAAS,MAAM,EAAE,OAAO,EAAE,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,CA4BxE"}
@@ -0,0 +1,87 @@
1
+ "use strict";
2
+ /*
3
+ * Hand-written (not generated by Speakeasy). Third-party app developer kit (C10); see
4
+ * docs/third-party-apps/plan.md.
5
+ *
6
+ * State shared by the views of one app. The host runs one sandbox (one worker) per app and mounts
7
+ * every view of that app into it, each as its own React root. React context therefore stops at a
8
+ * view's edge, but module state does not: a store created at module level is the same object in
9
+ * every view.
10
+ */
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.createAppStore = createAppStore;
13
+ const react_1 = require("react");
14
+ /**
15
+ * Creates a store that every view of this app can read and update. Create it once, at module
16
+ * level, in a file the views import:
17
+ *
18
+ * ```ts
19
+ * // src/store.ts
20
+ * export const notes = createAppStore({ saved: 0 });
21
+ * // in a view
22
+ * const saved = notes.useStore((s) => s.saved);
23
+ * notes.setState((s) => ({ saved: s.saved + 1 }));
24
+ * ```
25
+ *
26
+ * What it guarantees, and what it does not:
27
+ * - Shared by every view of this app that is open in the same browser tab (web) or the same app
28
+ * session (iOS, Android): a ContextView, its FocusView, the settings page and the app's pages
29
+ * all see one state, and an update in one re-renders the others.
30
+ * - In memory only. Nothing is written to storage or sent anywhere.
31
+ * - Lost whenever the sandbox restarts: after a crash, a rebuild in a dev preview, sign-out, a
32
+ * reload of the web page, or a restart of the mobile app. Each restart begins again from
33
+ * `initial`. Keep anything that must last in your own backend or the Secret Store.
34
+ * - Never shared with other apps: each app has its own sandbox.
35
+ * - Not shared across devices, browser tabs or users.
36
+ */
37
+ function createAppStore(initial) {
38
+ let state = initial;
39
+ const listeners = new Set();
40
+ const store = {
41
+ getState: () => state,
42
+ setState(update) {
43
+ const patch = typeof update === "function" ? update(state) : update;
44
+ const keys = Object.keys(patch);
45
+ if (!keys.some((key) => !Object.is(state[key], patch[key])))
46
+ return;
47
+ const previous = state;
48
+ state = { ...state, ...patch };
49
+ for (const listener of [...listeners])
50
+ listener(state, previous);
51
+ },
52
+ subscribe(listener) {
53
+ listeners.add(listener);
54
+ return () => {
55
+ listeners.delete(listener);
56
+ };
57
+ },
58
+ useStore: function useStore(selector) {
59
+ return useStoreValue(store, selector);
60
+ },
61
+ };
62
+ return store;
63
+ }
64
+ /**
65
+ * `useSyncExternalStore` over `store`. `store.subscribe` is read at render time, so it is the
66
+ * store's own function (or whatever replaced it), and the subscription follows it.
67
+ */
68
+ function useStoreValue(store, selector) {
69
+ // The last selection, so a selector that derives a new object from an unchanged state still
70
+ // gives React the same snapshot (React requires a stable snapshot between changes).
71
+ const cache = (0, react_1.useRef)(null);
72
+ const subscribe = store.subscribe;
73
+ const onStoreChange = (0, react_1.useCallback)((notify) => subscribe(() => notify()), [subscribe]);
74
+ const getSnapshot = () => {
75
+ const current = store.getState();
76
+ const cached = cache.current;
77
+ if (cached && cached.state === current && cached.selector === selector)
78
+ return cached.value;
79
+ const value = selector ? selector(current) : current;
80
+ // A changed state whose selection is equal keeps the previous value, so the view skips a render.
81
+ const stable = cached && Object.is(cached.value, value) ? cached.value : value;
82
+ cache.current = { state: current, selector, value: stable };
83
+ return stable;
84
+ };
85
+ return (0, react_1.useSyncExternalStore)(onStoreChange, getSnapshot, getSnapshot);
86
+ }
87
+ //# sourceMappingURL=store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store.js","sourceRoot":"","sources":["../../../../src/apps/hooks/store.ts"],"names":[],"mappings":";AAAA;;;;;;;;GAQG;;AAqDH,wCA4BC;AA/ED,iCAAkE;AA4BlE;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,SAAgB,cAAc,CAAmB,OAAU;IACzD,IAAI,KAAK,GAAG,OAAO,CAAC;IACpB,MAAM,SAAS,GAAG,IAAI,GAAG,EAAuB,CAAC;IAEjD,MAAM,KAAK,GAAgB;QACzB,QAAQ,EAAE,GAAG,EAAE,CAAC,KAAK;QAErB,QAAQ,CAAC,MAAM;YACb,MAAM,KAAK,GAAG,OAAO,MAAM,KAAK,UAAU,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;YACpE,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,CAAmB,CAAC;YAClD,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC;gBAAE,OAAO;YACpE,MAAM,QAAQ,GAAG,KAAK,CAAC;YACvB,KAAK,GAAG,EAAE,GAAG,KAAK,EAAE,GAAG,KAAK,EAAE,CAAC;YAC/B,KAAK,MAAM,QAAQ,IAAI,CAAC,GAAG,SAAS,CAAC;gBAAE,QAAQ,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC;QACnE,CAAC;QAED,SAAS,CAAC,QAAQ;YAChB,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;YACxB,OAAO,GAAG,EAAE;gBACV,SAAS,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;YAC7B,CAAC,CAAC;QACJ,CAAC;QAED,QAAQ,EAAE,SAAS,QAAQ,CAAI,QAA0B;YACvD,OAAO,aAAa,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC;QACxC,CAA4B;KAC7B,CAAC;IACF,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;;GAGG;AACH,SAAS,aAAa,CAAsB,KAAkB,EAAE,QAA0B;IACxF,4FAA4F;IAC5F,oFAAoF;IACpF,MAAM,KAAK,GAAG,IAAA,cAAM,EAA6E,IAAI,CAAC,CAAC;IACvG,MAAM,SAAS,GAAG,KAAK,CAAC,SAAS,CAAC;IAClC,MAAM,aAAa,GAAG,IAAA,mBAAW,EAAC,CAAC,MAAkB,EAAE,EAAE,CAAC,SAAS,CAAC,GAAG,EAAE,CAAC,MAAM,EAAE,CAAC,EAAE,CAAC,SAAS,CAAC,CAAC,CAAC;IAElG,MAAM,WAAW,GAAG,GAAU,EAAE;QAC9B,MAAM,OAAO,GAAG,KAAK,CAAC,QAAQ,EAAE,CAAC;QACjC,MAAM,MAAM,GAAG,KAAK,CAAC,OAAO,CAAC;QAC7B,IAAI,MAAM,IAAI,MAAM,CAAC,KAAK,KAAK,OAAO,IAAI,MAAM,CAAC,QAAQ,KAAK,QAAQ;YAAE,OAAO,MAAM,CAAC,KAAK,CAAC;QAC5F,MAAM,KAAK,GAAG,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC;QACrD,iGAAiG;QACjG,MAAM,MAAM,GAAG,MAAM,IAAI,MAAM,CAAC,EAAE,CAAC,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC;QAC/E,KAAK,CAAC,OAAO,GAAG,EAAE,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC;QAC5D,OAAO,MAAM,CAAC;IAChB,CAAC,CAAC;IAEF,OAAO,IAAA,4BAAoB,EAAC,aAAa,EAAE,WAAW,EAAE,WAAW,CAAC,CAAC;AACvE,CAAC"}
@@ -1,5 +1,6 @@
1
1
  import * as z from "zod/v4";
2
2
  export { IPV4_OCTET_PATTERN, PORT_PATTERN, CSP_PATH_CHAR, CSP_SOURCE_MAX_CHARS, CSP_SOURCE_PATTERN, CSP_SOURCES_MAX_PER_DIRECTIVE, CSP_SOURCES_MAX_TOTAL_CHARS, cspSourcesTotalChars, HTTPS_URL_PATTERN, isHttpsUrl, isParsableHttpsUrl, } from "./url.js";
3
+ export { APP_ROUTES_MAX, CONCRETE_ROUTE_PATH_PATTERN, isConcreteRoutePath, isRoutePath, matchAppRoute, normalizeRoutePath, ROUTE_PATH_MAX_CHARS, ROUTE_PATH_MAX_SEGMENTS, ROUTE_PATH_PATTERN, routeSetIssues, type AppRouteMatch, type RouteSetIssue, } from "./routes.js";
3
4
  /** A single SRI hash with the digest length its algorithm produces (32, 48 or 64 bytes, base64). */
4
5
  export declare const SRI_PATTERN = "^(sha256-[A-Za-z0-9+/]{43}=|sha384-[A-Za-z0-9+/]{64}|sha512-[A-Za-z0-9+/]{86}==)$";
5
6
  /** SemVer 2.0.0 (semver.org's reference pattern): no empty or leading-zero numeric identifiers. */
@@ -45,6 +46,45 @@ export declare const appViewSchema: z.ZodObject<{
45
46
  }>;
46
47
  component: z.ZodString;
47
48
  }, z.core.$strict>;
49
+ /** Reports {@link routeSetIssues} (segment cap, repeated params, duplicate paths, repeated portals). */
50
+ export declare function addRouteIssues(routes: ReadonlyArray<{
51
+ path: string;
52
+ portals?: readonly string[] | undefined;
53
+ navigation?: unknown;
54
+ }> | undefined, ctx: z.RefinementCtx, path: ReadonlyArray<string | number>): void;
55
+ /**
56
+ * One app page. The host renders `component` at `/{portal}/apps/{appId}/pages{path}`.
57
+ *
58
+ * `title` is required here, for new manifests. The server keeps accepting title-less entries in a
59
+ * bundle-less v1 manifest, which stored `routes[{ path, component }]` before pages were rendered;
60
+ * such a manifest has nothing to render them with, and the host never shows them.
61
+ */
62
+ /**
63
+ * Text a person reads (a page title, a sidebar label): at least one character that isn't
64
+ * whitespace. The host never shows a blank one, so the manifest can't publish one.
65
+ *
66
+ * This is JavaScript's `\S` spelled out (ASCII whitespace, the Unicode space separators,
67
+ * U+2028/U+2029 and U+FEFF), because the generated schema's patterns also run as Ruby regexps, and
68
+ * Ruby's `\S` is ASCII-only: there, a title of no-break spaces would pass.
69
+ */
70
+ export declare const NON_BLANK_PATTERN = "[^\\t\\n\\v\\f\\r \\u00a0\\u1680\\u2000-\\u200a\\u2028\\u2029\\u202f\\u205f\\u3000\\ufeff]";
71
+ export declare const appRouteSchema: z.ZodObject<{
72
+ path: z.ZodString;
73
+ component: z.ZodString;
74
+ /** Page header title. */
75
+ title: z.ZodString;
76
+ /** Portals the page is offered in. Absent = every portal the host renders. */
77
+ portals: z.ZodOptional<z.ZodArray<z.ZodEnum<{
78
+ facility: "facility";
79
+ system: "system";
80
+ company: "company";
81
+ vendor: "vendor";
82
+ }>>>;
83
+ /** Offer the page in the sidebar / mobile drawer menu under this label (default: `title`). */
84
+ navigation: z.ZodOptional<z.ZodObject<{
85
+ label: z.ZodOptional<z.ZodString>;
86
+ }, z.core.$strict>>;
87
+ }, z.core.$strict>;
48
88
  /** Constants are delivered to every view as `environment.constants`. */
49
89
  export declare const appConstantsSchema: z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>;
50
90
  export declare const appCspSchema: z.ZodObject<{
@@ -84,10 +124,23 @@ export declare const appManifestSchema: z.ZodObject<{
84
124
  }>;
85
125
  component: z.ZodString;
86
126
  }, z.core.$strict>>>;
87
- /** Reserved for a future FullPageView. Stored and validated, not rendered. */
127
+ /** App pages, each a full screen under `/{portal}/apps/{appId}/pages`. */
88
128
  routes: z.ZodOptional<z.ZodArray<z.ZodObject<{
89
129
  path: z.ZodString;
90
130
  component: z.ZodString;
131
+ /** Page header title. */
132
+ title: z.ZodString;
133
+ /** Portals the page is offered in. Absent = every portal the host renders. */
134
+ portals: z.ZodOptional<z.ZodArray<z.ZodEnum<{
135
+ facility: "facility";
136
+ system: "system";
137
+ company: "company";
138
+ vendor: "vendor";
139
+ }>>>;
140
+ /** Offer the page in the sidebar / mobile drawer menu under this label (default: `title`). */
141
+ navigation: z.ZodOptional<z.ZodObject<{
142
+ label: z.ZodOptional<z.ZodString>;
143
+ }, z.core.$strict>>;
91
144
  }, z.core.$strict>>>;
92
145
  csp: z.ZodOptional<z.ZodObject<{
93
146
  connect_src: z.ZodOptional<z.ZodArray<z.ZodString>>;
@@ -102,8 +155,14 @@ export declare const appManifestSchema: z.ZodObject<{
102
155
  url: "url";
103
156
  app_settings_view: "app_settings_view";
104
157
  app_main_view: "app_main_view";
158
+ app_page: "app_page";
105
159
  }>;
106
160
  url: z.ZodOptional<z.ZodString>;
161
+ /**
162
+ * For `app_page` only: the page to open, a concrete path (no `:params`) that one of
163
+ * `ui_extensions.routes` matches, such as `/welcome` or `/reports/7`.
164
+ */
165
+ path: z.ZodOptional<z.ZodString>;
107
166
  }, z.core.$strict>>;
108
167
  webhooks: z.ZodOptional<z.ZodObject<{
109
168
  url: z.ZodString;
@@ -115,12 +174,14 @@ export declare const appManifestSchema: z.ZodObject<{
115
174
  }, z.core.$strict>>;
116
175
  }, z.core.$strict>;
117
176
  export type AppManifestV2 = z.infer<typeof appManifestSchema>;
177
+ export type AppRoute = z.infer<typeof appRouteSchema>;
118
178
  export type PostInstallAction = NonNullable<AppManifestV2["post_install_action"]>;
119
179
  /**
120
180
  * What installing the app does when `post_install_action` is left out, so the server and the host
121
181
  * never land an installer on a view the app does not have: nothing for a back-end-only app, the
122
- * settings page for a settings-only app, and the app's main view otherwise. `null` means
123
- * "no post-install step". An explicit action is returned unchanged.
182
+ * settings page for a settings-only app, and the app's main view otherwise. A root page (route
183
+ * `/`) counts as a main view. `null` means "no post-install step". An explicit action, including
184
+ * `app_page`, is returned unchanged.
124
185
  */
125
186
  export declare function effectivePostInstallAction(manifest: Pick<AppManifestV2, "post_install_action" | "ui_extensions">): PostInstallAction | null;
126
187
  export type AppView = z.infer<typeof appViewSchema>;
@@ -1 +1 @@
1
- {"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../../../src/apps/manifest.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../../../src/apps/manifest.ts"],"names":[],"mappings":"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"}
@@ -43,14 +43,16 @@ var __importStar = (this && this.__importStar) || (function () {
43
43
  };
44
44
  })();
45
45
  Object.defineProperty(exports, "__esModule", { value: true });
46
- exports.appManifestSchema = exports.appCspSchema = exports.appConstantsSchema = exports.appViewSchema = exports.cspSourceSchema = exports.httpsUrlSchema = exports.APP_WEBHOOK_EVENTS = exports.PLATFORM_PERMISSIONS = exports.PERMISSION_PATTERN = exports.SAFE_INTEGER_STRING_PATTERN = exports.COMPONENT_NAME_PATTERN = exports.SEMVER_PATTERN = exports.SRI_PATTERN = exports.isParsableHttpsUrl = exports.isHttpsUrl = exports.HTTPS_URL_PATTERN = exports.cspSourcesTotalChars = exports.CSP_SOURCES_MAX_TOTAL_CHARS = exports.CSP_SOURCES_MAX_PER_DIRECTIVE = exports.CSP_SOURCE_PATTERN = exports.CSP_SOURCE_MAX_CHARS = exports.CSP_PATH_CHAR = exports.PORT_PATTERN = exports.IPV4_OCTET_PATTERN = void 0;
46
+ exports.appManifestSchema = exports.appCspSchema = exports.appConstantsSchema = exports.appRouteSchema = exports.NON_BLANK_PATTERN = exports.appViewSchema = exports.cspSourceSchema = exports.httpsUrlSchema = exports.APP_WEBHOOK_EVENTS = exports.PLATFORM_PERMISSIONS = exports.PERMISSION_PATTERN = exports.SAFE_INTEGER_STRING_PATTERN = exports.COMPONENT_NAME_PATTERN = exports.SEMVER_PATTERN = exports.SRI_PATTERN = exports.routeSetIssues = exports.ROUTE_PATH_PATTERN = exports.ROUTE_PATH_MAX_SEGMENTS = exports.ROUTE_PATH_MAX_CHARS = exports.normalizeRoutePath = exports.matchAppRoute = exports.isRoutePath = exports.isConcreteRoutePath = exports.CONCRETE_ROUTE_PATH_PATTERN = exports.APP_ROUTES_MAX = exports.isParsableHttpsUrl = exports.isHttpsUrl = exports.HTTPS_URL_PATTERN = exports.cspSourcesTotalChars = exports.CSP_SOURCES_MAX_TOTAL_CHARS = exports.CSP_SOURCES_MAX_PER_DIRECTIVE = exports.CSP_SOURCE_PATTERN = exports.CSP_SOURCE_MAX_CHARS = exports.CSP_PATH_CHAR = exports.PORT_PATTERN = exports.IPV4_OCTET_PATTERN = void 0;
47
47
  exports.isPlatformPermission = isPlatformPermission;
48
48
  exports.addCspBudgetIssue = addCspBudgetIssue;
49
49
  exports.addDuplicateViewportIssues = addDuplicateViewportIssues;
50
+ exports.addRouteIssues = addRouteIssues;
50
51
  exports.effectivePostInstallAction = effectivePostInstallAction;
51
52
  exports.manifestJsonSchema = manifestJsonSchema;
52
53
  exports.validateManifest = validateManifest;
53
54
  const z = __importStar(require("zod/v4"));
55
+ const routes_js_1 = require("./routes.js");
54
56
  const surfaces_js_1 = require("./surfaces.js");
55
57
  const url_js_1 = require("./url.js");
56
58
  // The URL and CSP-source rules live in the zod-free `url.ts` so the host renderer can use them
@@ -67,6 +69,18 @@ Object.defineProperty(exports, "cspSourcesTotalChars", { enumerable: true, get:
67
69
  Object.defineProperty(exports, "HTTPS_URL_PATTERN", { enumerable: true, get: function () { return url_js_2.HTTPS_URL_PATTERN; } });
68
70
  Object.defineProperty(exports, "isHttpsUrl", { enumerable: true, get: function () { return url_js_2.isHttpsUrl; } });
69
71
  Object.defineProperty(exports, "isParsableHttpsUrl", { enumerable: true, get: function () { return url_js_2.isParsableHttpsUrl; } });
72
+ // The route grammar and matcher live in the zod-free `routes.ts` for the same reason.
73
+ var routes_js_2 = require("./routes.js");
74
+ Object.defineProperty(exports, "APP_ROUTES_MAX", { enumerable: true, get: function () { return routes_js_2.APP_ROUTES_MAX; } });
75
+ Object.defineProperty(exports, "CONCRETE_ROUTE_PATH_PATTERN", { enumerable: true, get: function () { return routes_js_2.CONCRETE_ROUTE_PATH_PATTERN; } });
76
+ Object.defineProperty(exports, "isConcreteRoutePath", { enumerable: true, get: function () { return routes_js_2.isConcreteRoutePath; } });
77
+ Object.defineProperty(exports, "isRoutePath", { enumerable: true, get: function () { return routes_js_2.isRoutePath; } });
78
+ Object.defineProperty(exports, "matchAppRoute", { enumerable: true, get: function () { return routes_js_2.matchAppRoute; } });
79
+ Object.defineProperty(exports, "normalizeRoutePath", { enumerable: true, get: function () { return routes_js_2.normalizeRoutePath; } });
80
+ Object.defineProperty(exports, "ROUTE_PATH_MAX_CHARS", { enumerable: true, get: function () { return routes_js_2.ROUTE_PATH_MAX_CHARS; } });
81
+ Object.defineProperty(exports, "ROUTE_PATH_MAX_SEGMENTS", { enumerable: true, get: function () { return routes_js_2.ROUTE_PATH_MAX_SEGMENTS; } });
82
+ Object.defineProperty(exports, "ROUTE_PATH_PATTERN", { enumerable: true, get: function () { return routes_js_2.ROUTE_PATH_PATTERN; } });
83
+ Object.defineProperty(exports, "routeSetIssues", { enumerable: true, get: function () { return routes_js_2.routeSetIssues; } });
70
84
  /** A single SRI hash with the digest length its algorithm produces (32, 48 or 64 bytes, base64). */
71
85
  exports.SRI_PATTERN = "^(sha256-[A-Za-z0-9+/]{43}=|sha384-[A-Za-z0-9+/]{64}|sha512-[A-Za-z0-9+/]{86}==)$";
72
86
  /** SemVer 2.0.0 (semver.org's reference pattern): no empty or leading-zero numeric identifiers. */
@@ -100,7 +114,10 @@ const PLATFORM_PERMISSION_NAMES = new Set(exports.PLATFORM_PERMISSIONS.map((p) =
100
114
  function isPlatformPermission(value) {
101
115
  return typeof value === "string" && PLATFORM_PERMISSION_NAMES.has(value);
102
116
  }
103
- const SURFACE_IDS = surfaces_js_1.SURFACES.map((s) => s.id);
117
+ // A view's viewport. `page` is not one: an app declares its pages in `routes`, not in `views`.
118
+ const SURFACE_IDS = surfaces_js_1.SURFACES.filter((s) => s.kind !== "page").map((s) => s.id);
119
+ /** Portals an app page may be offered in (`routes[].portals`). */
120
+ const ROUTE_PORTALS = ["company", "vendor", "facility", "system"];
104
121
  /** Lifecycle events an app's own webhook endpoint can receive (see C19). */
105
122
  exports.APP_WEBHOOK_EVENTS = [
106
123
  "MarketplaceAppActivation.created",
@@ -153,6 +170,42 @@ exports.appViewSchema = z.strictObject({
153
170
  viewport: z.enum(SURFACE_IDS),
154
171
  component: z.string().max(100).regex(new RegExp(exports.COMPONENT_NAME_PATTERN)),
155
172
  });
173
+ /** Reports {@link routeSetIssues} (segment cap, repeated params, duplicate paths, repeated portals). */
174
+ function addRouteIssues(routes, ctx, path) {
175
+ for (const issue of (0, routes_js_1.routeSetIssues)(routes ?? [])) {
176
+ ctx.addIssue({ code: "custom", path: [...path, issue.index, issue.field], message: issue.message });
177
+ }
178
+ }
179
+ /**
180
+ * One app page. The host renders `component` at `/{portal}/apps/{appId}/pages{path}`.
181
+ *
182
+ * `title` is required here, for new manifests. The server keeps accepting title-less entries in a
183
+ * bundle-less v1 manifest, which stored `routes[{ path, component }]` before pages were rendered;
184
+ * such a manifest has nothing to render them with, and the host never shows them.
185
+ */
186
+ /**
187
+ * Text a person reads (a page title, a sidebar label): at least one character that isn't
188
+ * whitespace. The host never shows a blank one, so the manifest can't publish one.
189
+ *
190
+ * This is JavaScript's `\S` spelled out (ASCII whitespace, the Unicode space separators,
191
+ * U+2028/U+2029 and U+FEFF), because the generated schema's patterns also run as Ruby regexps, and
192
+ * Ruby's `\S` is ASCII-only: there, a title of no-break spaces would pass.
193
+ */
194
+ exports.NON_BLANK_PATTERN = "[^\\t\\n\\v\\f\\r \\u00a0\\u1680\\u2000-\\u200a\\u2028\\u2029\\u202f\\u205f\\u3000\\ufeff]";
195
+ exports.appRouteSchema = z.strictObject({
196
+ path: z.string().max(routes_js_1.ROUTE_PATH_MAX_CHARS).regex(new RegExp(routes_js_1.ROUTE_PATH_PATTERN)),
197
+ component: z.string().max(100).regex(new RegExp(exports.COMPONENT_NAME_PATTERN)),
198
+ /** Page header title. */
199
+ title: z.string().min(1).max(100).regex(new RegExp(exports.NON_BLANK_PATTERN), { message: "must not be blank" }),
200
+ /** Portals the page is offered in. Absent = every portal the host renders. */
201
+ portals: z.array(z.enum(ROUTE_PORTALS)).min(1).max(ROUTE_PORTALS.length).optional(),
202
+ /** Offer the page in the sidebar / mobile drawer menu under this label (default: `title`). */
203
+ navigation: z
204
+ .strictObject({
205
+ label: z.string().min(1).max(40).regex(new RegExp(exports.NON_BLANK_PATTERN), { message: "must not be blank" }).optional(),
206
+ })
207
+ .optional(),
208
+ });
156
209
  /** Constants are delivered to every view as `environment.constants`. */
157
210
  exports.appConstantsSchema = z.record(z.string().max(100).regex(/^[A-Za-z_][A-Za-z0-9_]*$/), z.union([z.string().max(2048), z.number(), z.boolean()]));
158
211
  exports.appCspSchema = z.strictObject({
@@ -192,21 +245,20 @@ exports.appManifestSchema = z
192
245
  integrity: z.string().max(200).regex(new RegExp(exports.SRI_PATTERN)),
193
246
  }).optional(),
194
247
  views: z.array(exports.appViewSchema).max(200).optional(),
195
- /** Reserved for a future FullPageView. Stored and validated, not rendered. */
196
- routes: z
197
- .array(z.strictObject({
198
- path: z.string().max(200).regex(/^\/[a-z0-9\-/:]*$/),
199
- component: z.string().max(100).regex(new RegExp(exports.COMPONENT_NAME_PATTERN)),
200
- }))
201
- .max(50)
202
- .optional(),
248
+ /** App pages, each a full screen under `/{portal}/apps/{appId}/pages`. */
249
+ routes: z.array(exports.appRouteSchema).max(routes_js_1.APP_ROUTES_MAX).optional(),
203
250
  csp: exports.appCspSchema.optional(),
204
251
  })
205
252
  .optional(),
206
253
  post_install_action: z
207
254
  .strictObject({
208
- type: z.enum(["url", "app_settings_view", "app_main_view"]),
255
+ type: z.enum(["url", "app_settings_view", "app_main_view", "app_page"]),
209
256
  url: exports.httpsUrlSchema.optional(),
257
+ /**
258
+ * For `app_page` only: the page to open, a concrete path (no `:params`) that one of
259
+ * `ui_extensions.routes` matches, such as `/welcome` or `/reports/7`.
260
+ */
261
+ path: z.string().max(routes_js_1.ROUTE_PATH_MAX_CHARS).regex(new RegExp(routes_js_1.CONCRETE_ROUTE_PATH_PATTERN)).optional(),
210
262
  })
211
263
  .optional(),
212
264
  // The app's own webhook endpoint: registered by the catalog when the manifest is published, not
@@ -220,31 +272,50 @@ exports.appManifestSchema = z
220
272
  .optional(),
221
273
  })
222
274
  .superRefine((manifest, ctx) => {
223
- addDuplicateViewportIssues(manifest.ui_extensions?.views, ctx, ["ui_extensions", "views"]);
224
- addCspBudgetIssue(manifest.ui_extensions?.csp, ctx, ["ui_extensions", "csp"]);
225
275
  const events = manifest.webhooks?.events ?? [];
226
276
  if (new Set(events).size !== events.length) {
227
277
  ctx.addIssue({ code: "custom", path: ["webhooks", "events"], message: "events must not repeat" });
228
278
  }
229
- if (manifest.ui_extensions?.views?.length && !manifest.ui_extensions.bundle) {
279
+ if (manifest.post_install_action?.type === "url" && !manifest.post_install_action.url) {
280
+ ctx.addIssue({ code: "custom", path: ["post_install_action", "url"], message: "url is required for type=url" });
281
+ }
282
+ if (manifest.post_install_action?.type !== "app_page" && manifest.post_install_action?.path !== undefined) {
283
+ ctx.addIssue({ code: "custom", path: ["post_install_action", "path"], message: "path is only allowed for type=app_page" });
284
+ }
285
+ // When a field under `ui_extensions` fails its own schema, zod hands this refinement the
286
+ // manifest without `ui_extensions`, so every rule below would report a problem that isn't
287
+ // there (a missing settings view, an unmatched post-install page). Report the real one only.
288
+ if (ctx.issues.some((issue) => issue.path?.[0] === "ui_extensions"))
289
+ return;
290
+ addDuplicateViewportIssues(manifest.ui_extensions?.views, ctx, ["ui_extensions", "views"]);
291
+ addRouteIssues(manifest.ui_extensions?.routes, ctx, ["ui_extensions", "routes"]);
292
+ addCspBudgetIssue(manifest.ui_extensions?.csp, ctx, ["ui_extensions", "csp"]);
293
+ const hasViews = Boolean(manifest.ui_extensions?.views?.length);
294
+ const hasRoutes = Boolean(manifest.ui_extensions?.routes?.length);
295
+ if (hasViews && !manifest.ui_extensions?.bundle) {
230
296
  ctx.addIssue({
231
297
  code: "custom",
232
298
  path: ["ui_extensions", "bundle"],
233
299
  message: "bundle is required when views are declared",
234
300
  });
235
301
  }
302
+ if (hasRoutes && !manifest.ui_extensions?.bundle) {
303
+ ctx.addIssue({
304
+ code: "custom",
305
+ path: ["ui_extensions", "bundle"],
306
+ message: "bundle is required when routes are declared",
307
+ });
308
+ }
236
309
  // `version` stays optional only for legacy (v1) manifests. Anything the host renders needs one:
237
310
  // it names the runtime URL and reaches views as `ExtensionContext.app.version`.
238
- if ((manifest.ui_extensions?.bundle || manifest.ui_extensions?.views?.length) && !manifest.version) {
311
+ if ((manifest.ui_extensions?.bundle || hasViews || hasRoutes) && !manifest.version) {
239
312
  ctx.addIssue({
240
313
  code: "custom",
241
314
  path: ["version"],
242
- message: "version is required when ui_extensions declares a bundle or views",
315
+ message: "version is required when ui_extensions declares a bundle, views or routes",
243
316
  });
244
317
  }
245
- if (manifest.post_install_action?.type === "url" && !manifest.post_install_action.url) {
246
- ctx.addIssue({ code: "custom", path: ["post_install_action", "url"], message: "url is required for type=url" });
247
- }
318
+ addAppPageActionIssues(manifest, ctx);
248
319
  // A back-end-only app has no views, so it cannot land installers on one. Checked only for
249
320
  // versioned (v2) manifests: v1 apps may carry a post-install view type from before views existed.
250
321
  const viewports = new Set((manifest.ui_extensions?.views ?? []).map((v) => v.viewport));
@@ -257,28 +328,58 @@ exports.appManifestSchema = z
257
328
  });
258
329
  }
259
330
  // The settings page is not a main view: a settings-only app should land on app_settings_view.
260
- const mainViewports = [...viewports].filter((v) => v !== "settings");
261
- if (versioned && manifest.post_install_action?.type === "app_main_view" && mainViewports.length === 0) {
331
+ // A root page (route `/`) is one: installing opens `/{portal}/apps/{id}/pages`.
332
+ if (versioned && manifest.post_install_action?.type === "app_main_view" && !hasMainView(manifest)) {
262
333
  ctx.addIssue({
263
334
  code: "custom",
264
335
  path: ["post_install_action", "type"],
265
- message: "app_main_view requires a view other than settings",
336
+ message: "app_main_view requires a view other than settings, or a root route",
266
337
  });
267
338
  }
268
339
  });
340
+ /**
341
+ * The rules for `post_install_action.path`: required for `app_page` and allowed nowhere else, a
342
+ * concrete path within the route limits, and one that a declared route matches (`/reports/7` for a
343
+ * `/reports/:id` route), so installing never opens a page the app does not have.
344
+ */
345
+ function addAppPageActionIssues(manifest, ctx) {
346
+ const action = manifest.post_install_action;
347
+ if (action === undefined)
348
+ return;
349
+ const path = ["post_install_action", "path"];
350
+ // `path` on another type is reported by the manifest refinement, before the rules that need
351
+ // `ui_extensions`.
352
+ if (action.type !== "app_page")
353
+ return;
354
+ if (action.path === undefined) {
355
+ ctx.addIssue({ code: "custom", path, message: "path is required for type=app_page" });
356
+ }
357
+ else if (!(0, routes_js_1.isConcreteRoutePath)(action.path)) {
358
+ ctx.addIssue({ code: "custom", path, message: "path must be a page path without :params, such as /reports/7" });
359
+ }
360
+ else if ((0, routes_js_1.matchAppRoute)(manifest.ui_extensions?.routes ?? [], action.path) === null) {
361
+ ctx.addIssue({ code: "custom", path, message: "path must match one of ui_extensions.routes" });
362
+ }
363
+ }
364
+ /** Whether a manifest has a main view to land on: a view other than settings, or a root page `/`. */
365
+ function hasMainView(manifest) {
366
+ const views = manifest.ui_extensions?.views ?? [];
367
+ const routes = manifest.ui_extensions?.routes ?? [];
368
+ return views.some((v) => v.viewport !== "settings") || routes.some((r) => r.path === "/");
369
+ }
269
370
  /**
270
371
  * What installing the app does when `post_install_action` is left out, so the server and the host
271
372
  * never land an installer on a view the app does not have: nothing for a back-end-only app, the
272
- * settings page for a settings-only app, and the app's main view otherwise. `null` means
273
- * "no post-install step". An explicit action is returned unchanged.
373
+ * settings page for a settings-only app, and the app's main view otherwise. A root page (route
374
+ * `/`) counts as a main view. `null` means "no post-install step". An explicit action, including
375
+ * `app_page`, is returned unchanged.
274
376
  */
275
377
  function effectivePostInstallAction(manifest) {
276
378
  if (manifest.post_install_action)
277
379
  return manifest.post_install_action;
278
- const viewports = (manifest.ui_extensions?.views ?? []).map((v) => v.viewport);
279
- if (viewports.some((v) => v !== "settings"))
380
+ if (hasMainView(manifest))
280
381
  return { type: "app_main_view" };
281
- if (viewports.includes("settings"))
382
+ if ((manifest.ui_extensions?.views ?? []).some((v) => v.viewport === "settings"))
282
383
  return { type: "app_settings_view" };
283
384
  return null;
284
385
  }