@botiverse/porthole 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (150) hide show
  1. package/LICENSE +217 -0
  2. package/dist/bridge/bridge.d.ts +39 -0
  3. package/dist/bridge/bridge.js +184 -0
  4. package/dist/bridge/types.d.ts +67 -0
  5. package/dist/bridge/types.js +1 -0
  6. package/dist/compile/compile.d.ts +35 -0
  7. package/dist/compile/compile.js +30 -0
  8. package/dist/compile/policy.d.ts +55 -0
  9. package/dist/compile/policy.js +106 -0
  10. package/dist/config/capabilities.d.ts +154 -0
  11. package/dist/config/capabilities.js +63 -0
  12. package/dist/config/schema.d.ts +131 -0
  13. package/dist/config/schema.js +1 -0
  14. package/dist/config/validate.d.ts +20 -0
  15. package/dist/config/validate.js +230 -0
  16. package/dist/conformance/behavior.d.ts +2 -0
  17. package/dist/conformance/behavior.js +376 -0
  18. package/dist/conformance/index.d.ts +95 -0
  19. package/dist/conformance/index.js +4 -0
  20. package/dist/conformance/kit.d.ts +42 -0
  21. package/dist/conformance/kit.js +24 -0
  22. package/dist/conformance/oracles.d.ts +35 -0
  23. package/dist/conformance/oracles.js +77 -0
  24. package/dist/conformance/probes.d.ts +9 -0
  25. package/dist/conformance/probes.js +865 -0
  26. package/dist/conformance/served.d.ts +68 -0
  27. package/dist/conformance/served.js +311 -0
  28. package/dist/guide/examples.d.ts +70 -0
  29. package/dist/guide/examples.js +69 -0
  30. package/dist/guide/guide.d.ts +16 -0
  31. package/dist/guide/guide.js +261 -0
  32. package/dist/host/create-host.d.ts +8 -0
  33. package/dist/host/create-host.js +248 -0
  34. package/dist/host/locate.d.ts +8 -0
  35. package/dist/host/locate.js +11 -0
  36. package/dist/host/types.d.ts +95 -0
  37. package/dist/host/types.js +1 -0
  38. package/dist/index.d.ts +23 -0
  39. package/dist/index.js +19 -0
  40. package/dist/isolation/served.d.ts +32 -0
  41. package/dist/isolation/served.js +97 -0
  42. package/dist/isolation/types.d.ts +87 -0
  43. package/dist/isolation/types.js +1 -0
  44. package/dist/mcp/adapter.d.ts +43 -0
  45. package/dist/mcp/adapter.js +327 -0
  46. package/dist/mcp/index.d.ts +27 -0
  47. package/dist/mcp/index.js +25 -0
  48. package/dist/mcp/proxy.d.ts +25 -0
  49. package/dist/mcp/proxy.js +106 -0
  50. package/dist/mcp/view.d.ts +31 -0
  51. package/dist/mcp/view.js +72 -0
  52. package/dist/platform/types.d.ts +44 -0
  53. package/dist/platform/types.js +1 -0
  54. package/dist/platform/web.d.ts +8 -0
  55. package/dist/platform/web.js +72 -0
  56. package/dist/presets/presets.d.ts +65 -0
  57. package/dist/presets/presets.js +125 -0
  58. package/dist/protocol/adapter.d.ts +73 -0
  59. package/dist/protocol/adapter.js +1 -0
  60. package/dist/protocol/jsonrpc.d.ts +43 -0
  61. package/dist/protocol/jsonrpc.js +50 -0
  62. package/dist/protocol/mcp-apps.d.ts +79 -0
  63. package/dist/protocol/mcp-apps.js +99 -0
  64. package/dist/protocol/shell-adapter.d.ts +5 -0
  65. package/dist/protocol/shell-adapter.js +228 -0
  66. package/dist/react/dialog.d.ts +20 -0
  67. package/dist/react/dialog.js +98 -0
  68. package/dist/react/frame.d.ts +75 -0
  69. package/dist/react/frame.js +221 -0
  70. package/dist/react/index.d.ts +13 -0
  71. package/dist/react/index.js +13 -0
  72. package/dist/react/live.d.ts +24 -0
  73. package/dist/react/live.js +57 -0
  74. package/dist/server/index.d.ts +27 -0
  75. package/dist/server/index.js +26 -0
  76. package/dist/server/keys.d.ts +19 -0
  77. package/dist/server/keys.js +78 -0
  78. package/dist/server/pages.d.ts +6 -0
  79. package/dist/server/pages.js +20 -0
  80. package/dist/server/proxy.d.ts +21 -0
  81. package/dist/server/proxy.js +107 -0
  82. package/dist/server/server.d.ts +7 -0
  83. package/dist/server/server.js +518 -0
  84. package/dist/server/tickets.d.ts +8 -0
  85. package/dist/server/tickets.js +28 -0
  86. package/dist/server/types.d.ts +97 -0
  87. package/dist/server/types.js +1 -0
  88. package/dist/shell/document.d.ts +28 -0
  89. package/dist/shell/document.js +84 -0
  90. package/dist/shell/plan.d.ts +35 -0
  91. package/dist/shell/plan.js +34 -0
  92. package/dist/shell/runtime.d.ts +28 -0
  93. package/dist/shell/runtime.js +832 -0
  94. package/dist/theme/variables.d.ts +73 -0
  95. package/dist/theme/variables.js +171 -0
  96. package/dist/transport/post-message.d.ts +9 -0
  97. package/dist/transport/post-message.js +38 -0
  98. package/dist/transport/types.d.ts +15 -0
  99. package/dist/transport/types.js +1 -0
  100. package/package.json +68 -3
  101. package/src/bridge/bridge.ts +256 -0
  102. package/src/bridge/types.ts +77 -0
  103. package/src/compile/compile.ts +68 -0
  104. package/src/compile/policy.ts +133 -0
  105. package/src/config/capabilities.ts +138 -0
  106. package/src/config/schema.ts +140 -0
  107. package/src/config/validate.ts +284 -0
  108. package/src/conformance/behavior.ts +430 -0
  109. package/src/conformance/index.ts +102 -0
  110. package/src/conformance/kit.ts +63 -0
  111. package/src/conformance/oracles.ts +126 -0
  112. package/src/conformance/probes.ts +1093 -0
  113. package/src/conformance/served.ts +472 -0
  114. package/src/guide/examples.ts +74 -0
  115. package/src/guide/guide.ts +424 -0
  116. package/src/host/create-host.ts +290 -0
  117. package/src/host/locate.ts +20 -0
  118. package/src/host/types.ts +95 -0
  119. package/src/index.ts +118 -0
  120. package/src/isolation/served.ts +127 -0
  121. package/src/isolation/types.ts +77 -0
  122. package/src/mcp/adapter.ts +441 -0
  123. package/src/mcp/index.ts +50 -0
  124. package/src/mcp/proxy.ts +117 -0
  125. package/src/mcp/view.ts +97 -0
  126. package/src/platform/types.ts +48 -0
  127. package/src/platform/web.ts +78 -0
  128. package/src/presets/presets.ts +193 -0
  129. package/src/protocol/adapter.ts +67 -0
  130. package/src/protocol/jsonrpc.ts +83 -0
  131. package/src/protocol/mcp-apps.ts +138 -0
  132. package/src/protocol/shell-adapter.ts +261 -0
  133. package/src/react/dialog.tsx +158 -0
  134. package/src/react/frame.tsx +422 -0
  135. package/src/react/index.ts +20 -0
  136. package/src/react/live.ts +76 -0
  137. package/src/server/index.ts +37 -0
  138. package/src/server/keys.ts +100 -0
  139. package/src/server/pages.ts +31 -0
  140. package/src/server/proxy.ts +131 -0
  141. package/src/server/server.ts +617 -0
  142. package/src/server/tickets.ts +30 -0
  143. package/src/server/types.ts +104 -0
  144. package/src/shell/document.ts +120 -0
  145. package/src/shell/plan.ts +66 -0
  146. package/src/shell/runtime.ts +832 -0
  147. package/src/theme/variables.ts +190 -0
  148. package/src/transport/post-message.ts +45 -0
  149. package/src/transport/types.ts +16 -0
  150. package/README.md +0 -3
@@ -0,0 +1,131 @@
1
+ import type { Access, Upstream } from "./types.ts";
2
+
3
+ /**
4
+ * A url-mode upstream that relays each request to the running app by HTTP: the app believes it is
5
+ * on its own origin (Host, Origin and Referer name it; dev servers check Host), learns nothing of
6
+ * the proxy (no forwarding headers), and its answer comes back on the content origin (its
7
+ * redirects to itself, and its cookies, stay there). WebSockets (a dev server's hot reload) don't
8
+ * go through a fetch-style handler: the host relays those on its own upgrade path, after
9
+ * `PortholeServer.access` lets the request in.
10
+ */
11
+
12
+ export interface ProxyOptions {
13
+ /** Where the app runs. Default: the external instance's own `url` (its origin; paths map 1:1). */
14
+ readonly target?: (access: Access) => string;
15
+ /** The fetch to relay with. Default: the global one. */
16
+ readonly fetch?: typeof fetch;
17
+ }
18
+
19
+ const HOP_BY_HOP = new Set([
20
+ "connection",
21
+ "keep-alive",
22
+ "proxy-authenticate",
23
+ "proxy-authorization",
24
+ "proxy-connection",
25
+ "te",
26
+ "trailer",
27
+ "transfer-encoding",
28
+ "upgrade",
29
+ ]);
30
+ /** What a proxy in front of the host's server added: the app is told nothing of it. */
31
+ const FORWARDING = /^(?:x-forwarded-.*|forwarded|x-real-ip)$/u;
32
+
33
+ function connectionTokens(headers: Headers): Set<string> {
34
+ return new Set(
35
+ (headers.get("connection") ?? "")
36
+ .split(",")
37
+ .map((token) => token.trim().toLowerCase())
38
+ .filter((token) => token !== ""),
39
+ );
40
+ }
41
+
42
+ /** The browser's request headers as the app at `target` gets them. */
43
+ export function upstreamRequestHeaders(headers: Headers, origin: string, target: string): Headers {
44
+ const named = connectionTokens(headers);
45
+ const out = new Headers();
46
+ for (const [name, value] of headers) {
47
+ const key = name.toLowerCase();
48
+ if (key === "host" || HOP_BY_HOP.has(key) || named.has(key) || FORWARDING.test(key)) {
49
+ continue;
50
+ }
51
+ if (key === "origin" && value === origin) {
52
+ out.set(name, target);
53
+ } else if (key === "referer" && (value === origin || value.startsWith(`${origin}/`))) {
54
+ out.set(name, `${target}${value.slice(origin.length)}`);
55
+ } else {
56
+ out.append(name, value);
57
+ }
58
+ }
59
+ // The answer comes back decoded by fetch: ask for it plain rather than pay to decode it.
60
+ out.set("accept-encoding", "identity");
61
+ return out;
62
+ }
63
+
64
+ /** The app's answer headers as the browser on `origin` gets them. */
65
+ export function upstreamResponseHeaders(headers: Headers, origin: string, target: string): Headers {
66
+ const named = connectionTokens(headers);
67
+ const out = new Headers();
68
+ for (const [name, value] of headers) {
69
+ const key = name.toLowerCase();
70
+ if (
71
+ HOP_BY_HOP.has(key) ||
72
+ named.has(key) ||
73
+ key === "set-cookie" ||
74
+ // fetch decoded the body, whatever the app said it encoded.
75
+ key === "content-encoding" ||
76
+ key === "content-length"
77
+ ) {
78
+ continue;
79
+ }
80
+ if (key === "location") {
81
+ out.set(name, onOrigin(value, origin, target));
82
+ } else {
83
+ out.append(name, value);
84
+ }
85
+ }
86
+ for (const cookie of headers.getSetCookie()) {
87
+ // Host-only on the content origin: an app's cookie never reaches the content domain's others.
88
+ const [pair = "", ...attributes] = cookie.split(";");
89
+ const kept = attributes.filter((attribute) => !/^\s*domain\s*=/iu.test(attribute));
90
+ out.append("set-cookie", [pair, ...kept].join(";"));
91
+ }
92
+ return out;
93
+ }
94
+
95
+ /** `<target>/x` → `<origin>/x`; any other URL as it is. */
96
+ function onOrigin(location: string, origin: string, target: string): string {
97
+ try {
98
+ const url = new URL(location, target);
99
+ return url.origin === target ? `${origin}${url.pathname}${url.search}${url.hash}` : location;
100
+ } catch {
101
+ return location;
102
+ }
103
+ }
104
+
105
+ /** An `Upstream` that relays to the app by HTTP. */
106
+ export function proxyTo(options: ProxyOptions = {}): Upstream {
107
+ const relay = options.fetch ?? fetch;
108
+ return async (request, access) => {
109
+ const where =
110
+ options.target?.(access) ??
111
+ (access.instance.document.kind === "external" ? access.instance.document.url : null);
112
+ if (where === null) {
113
+ return new Response("Not an app", { status: 502 });
114
+ }
115
+ const target = new URL(where).origin;
116
+ const url = new URL(request.url);
117
+ const method = request.method.toUpperCase();
118
+ const hasBody = method !== "GET" && method !== "HEAD";
119
+ const answer = await relay(`${target}${url.pathname}${url.search}`, {
120
+ method,
121
+ headers: upstreamRequestHeaders(request.headers, access.origin, target),
122
+ redirect: "manual",
123
+ ...(hasBody ? { body: request.body, duplex: "half" } : {}),
124
+ });
125
+ return new Response(method === "HEAD" ? null : answer.body, {
126
+ status: answer.status,
127
+ statusText: answer.statusText,
128
+ headers: upstreamResponseHeaders(answer.headers, access.origin, target),
129
+ });
130
+ };
131
+ }