@databricks/appkit 0.0.2
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.
- package/CLAUDE.md +3 -0
- package/DCO +25 -0
- package/LICENSE +203 -0
- package/NOTICE.md +73 -0
- package/README.md +35 -0
- package/bin/setup-claude.js +190 -0
- package/dist/_virtual/rolldown_runtime.js +39 -0
- package/dist/analytics/analytics.d.ts +31 -0
- package/dist/analytics/analytics.d.ts.map +1 -0
- package/dist/analytics/analytics.js +149 -0
- package/dist/analytics/analytics.js.map +1 -0
- package/dist/analytics/defaults.js +17 -0
- package/dist/analytics/defaults.js.map +1 -0
- package/dist/analytics/index.js +3 -0
- package/dist/analytics/query.js +50 -0
- package/dist/analytics/query.js.map +1 -0
- package/dist/analytics/types.d.ts +9 -0
- package/dist/analytics/types.d.ts.map +1 -0
- package/dist/app/index.d.ts +23 -0
- package/dist/app/index.d.ts.map +1 -0
- package/dist/app/index.js +49 -0
- package/dist/app/index.js.map +1 -0
- package/dist/appkit/package.js +7 -0
- package/dist/appkit/package.js.map +1 -0
- package/dist/cache/defaults.js +14 -0
- package/dist/cache/defaults.js.map +1 -0
- package/dist/cache/index.d.ts +119 -0
- package/dist/cache/index.d.ts.map +1 -0
- package/dist/cache/index.js +307 -0
- package/dist/cache/index.js.map +1 -0
- package/dist/cache/storage/defaults.js +16 -0
- package/dist/cache/storage/defaults.js.map +1 -0
- package/dist/cache/storage/index.js +4 -0
- package/dist/cache/storage/memory.js +87 -0
- package/dist/cache/storage/memory.js.map +1 -0
- package/dist/cache/storage/persistent.js +211 -0
- package/dist/cache/storage/persistent.js.map +1 -0
- package/dist/connectors/index.js +6 -0
- package/dist/connectors/lakebase/client.js +348 -0
- package/dist/connectors/lakebase/client.js.map +1 -0
- package/dist/connectors/lakebase/defaults.js +13 -0
- package/dist/connectors/lakebase/defaults.js.map +1 -0
- package/dist/connectors/lakebase/index.js +3 -0
- package/dist/connectors/sql-warehouse/client.js +284 -0
- package/dist/connectors/sql-warehouse/client.js.map +1 -0
- package/dist/connectors/sql-warehouse/defaults.js +12 -0
- package/dist/connectors/sql-warehouse/defaults.js.map +1 -0
- package/dist/connectors/sql-warehouse/index.js +3 -0
- package/dist/core/appkit.d.ts +14 -0
- package/dist/core/appkit.d.ts.map +1 -0
- package/dist/core/appkit.js +66 -0
- package/dist/core/appkit.js.map +1 -0
- package/dist/core/index.js +3 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.js +21 -0
- package/dist/index.js.map +1 -0
- package/dist/plugin/dev-reader.d.ts +20 -0
- package/dist/plugin/dev-reader.d.ts.map +1 -0
- package/dist/plugin/dev-reader.js +63 -0
- package/dist/plugin/dev-reader.js.map +1 -0
- package/dist/plugin/index.js +4 -0
- package/dist/plugin/interceptors/cache.js +15 -0
- package/dist/plugin/interceptors/cache.js.map +1 -0
- package/dist/plugin/interceptors/retry.js +32 -0
- package/dist/plugin/interceptors/retry.js.map +1 -0
- package/dist/plugin/interceptors/telemetry.js +33 -0
- package/dist/plugin/interceptors/telemetry.js.map +1 -0
- package/dist/plugin/interceptors/timeout.js +35 -0
- package/dist/plugin/interceptors/timeout.js.map +1 -0
- package/dist/plugin/plugin.d.ts +43 -0
- package/dist/plugin/plugin.d.ts.map +1 -0
- package/dist/plugin/plugin.js +119 -0
- package/dist/plugin/plugin.js.map +1 -0
- package/dist/plugin/to-plugin.d.ts +7 -0
- package/dist/plugin/to-plugin.d.ts.map +1 -0
- package/dist/plugin/to-plugin.js +12 -0
- package/dist/plugin/to-plugin.js.map +1 -0
- package/dist/server/base-server.js +24 -0
- package/dist/server/base-server.js.map +1 -0
- package/dist/server/index.d.ts +100 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +224 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/remote-tunnel/denied.html +68 -0
- package/dist/server/remote-tunnel/gate.js +51 -0
- package/dist/server/remote-tunnel/gate.js.map +1 -0
- package/dist/server/remote-tunnel/index.html +165 -0
- package/dist/server/remote-tunnel/remote-tunnel-controller.js +100 -0
- package/dist/server/remote-tunnel/remote-tunnel-controller.js.map +1 -0
- package/dist/server/remote-tunnel/remote-tunnel-manager.js +320 -0
- package/dist/server/remote-tunnel/remote-tunnel-manager.js.map +1 -0
- package/dist/server/remote-tunnel/wait.html +158 -0
- package/dist/server/static-server.js +47 -0
- package/dist/server/static-server.js.map +1 -0
- package/dist/server/types.d.ts +14 -0
- package/dist/server/types.d.ts.map +1 -0
- package/dist/server/utils.js +70 -0
- package/dist/server/utils.js.map +1 -0
- package/dist/server/vite-dev-server.js +103 -0
- package/dist/server/vite-dev-server.js.map +1 -0
- package/dist/shared/src/cache.d.ts +62 -0
- package/dist/shared/src/cache.d.ts.map +1 -0
- package/dist/shared/src/execute.d.ts +46 -0
- package/dist/shared/src/execute.d.ts.map +1 -0
- package/dist/shared/src/plugin.d.ts +50 -0
- package/dist/shared/src/plugin.d.ts.map +1 -0
- package/dist/shared/src/sql/helpers.d.ts +160 -0
- package/dist/shared/src/sql/helpers.d.ts.map +1 -0
- package/dist/shared/src/sql/helpers.js +103 -0
- package/dist/shared/src/sql/helpers.js.map +1 -0
- package/dist/shared/src/sql/types.d.ts +34 -0
- package/dist/shared/src/sql/types.d.ts.map +1 -0
- package/dist/shared/src/tunnel.d.ts +30 -0
- package/dist/shared/src/tunnel.d.ts.map +1 -0
- package/dist/stream/arrow-stream-processor.js +154 -0
- package/dist/stream/arrow-stream-processor.js.map +1 -0
- package/dist/stream/buffers.js +88 -0
- package/dist/stream/buffers.js.map +1 -0
- package/dist/stream/defaults.js +14 -0
- package/dist/stream/defaults.js.map +1 -0
- package/dist/stream/index.js +6 -0
- package/dist/stream/sse-writer.js +61 -0
- package/dist/stream/sse-writer.js.map +1 -0
- package/dist/stream/stream-manager.d.ts +27 -0
- package/dist/stream/stream-manager.d.ts.map +1 -0
- package/dist/stream/stream-manager.js +191 -0
- package/dist/stream/stream-manager.js.map +1 -0
- package/dist/stream/stream-registry.js +54 -0
- package/dist/stream/stream-registry.js.map +1 -0
- package/dist/stream/types.js +14 -0
- package/dist/stream/types.js.map +1 -0
- package/dist/stream/validator.js +25 -0
- package/dist/stream/validator.js.map +1 -0
- package/dist/telemetry/config.js +20 -0
- package/dist/telemetry/config.js.map +1 -0
- package/dist/telemetry/index.d.ts +4 -0
- package/dist/telemetry/index.js +8 -0
- package/dist/telemetry/instrumentations.js +38 -0
- package/dist/telemetry/instrumentations.js.map +1 -0
- package/dist/telemetry/noop.js +54 -0
- package/dist/telemetry/noop.js.map +1 -0
- package/dist/telemetry/telemetry-manager.js +113 -0
- package/dist/telemetry/telemetry-manager.js.map +1 -0
- package/dist/telemetry/telemetry-provider.js +82 -0
- package/dist/telemetry/telemetry-provider.js.map +1 -0
- package/dist/telemetry/types.d.ts +74 -0
- package/dist/telemetry/types.d.ts.map +1 -0
- package/dist/type-generator/vite-plugin.d.ts +22 -0
- package/dist/type-generator/vite-plugin.d.ts.map +1 -0
- package/dist/type-generator/vite-plugin.js +49 -0
- package/dist/type-generator/vite-plugin.js.map +1 -0
- package/dist/utils/databricks-client-middleware.d.ts +17 -0
- package/dist/utils/databricks-client-middleware.d.ts.map +1 -0
- package/dist/utils/databricks-client-middleware.js +117 -0
- package/dist/utils/databricks-client-middleware.js.map +1 -0
- package/dist/utils/env-validator.js +14 -0
- package/dist/utils/env-validator.js.map +1 -0
- package/dist/utils/index.js +26 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/merge.js +25 -0
- package/dist/utils/merge.js.map +1 -0
- package/dist/utils/vite-config-merge.js +22 -0
- package/dist/utils/vite-config-merge.js.map +1 -0
- package/llms.txt +193 -0
- package/package.json +70 -0
- package/scripts/postinstall.js +6 -0
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
import { REMOTE_TUNNEL_ASSET_PREFIXES } from "./gate.js";
|
|
2
|
+
import { generateTunnelIdFromEmail, getConfigScript, parseCookies } from "../utils.js";
|
|
3
|
+
import { randomUUID } from "node:crypto";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import fs from "node:fs";
|
|
6
|
+
import { fileURLToPath } from "node:url";
|
|
7
|
+
import { WebSocketServer } from "ws";
|
|
8
|
+
|
|
9
|
+
//#region src/server/remote-tunnel/remote-tunnel-manager.ts
|
|
10
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
11
|
+
const __dirname = path.dirname(__filename);
|
|
12
|
+
const MAX_ASSET_FETCH_TIMEOUT = 6e4;
|
|
13
|
+
/**
|
|
14
|
+
* Remote tunnel manager for the AppKit.
|
|
15
|
+
*
|
|
16
|
+
* This class is responsible for managing the remote tunnels for the development server.
|
|
17
|
+
* It also handles the asset fetching and the HMR for the development server.
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* ```ts
|
|
21
|
+
* const remoteTunnelManager = new RemoteTunnelManager(devFileReader);
|
|
22
|
+
* remoteTunnelManager.setup(app);
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
var RemoteTunnelManager = class {
|
|
26
|
+
constructor(devFileReader) {
|
|
27
|
+
this.tunnels = /* @__PURE__ */ new Map();
|
|
28
|
+
this.devFileReader = devFileReader;
|
|
29
|
+
this.wss = new WebSocketServer({
|
|
30
|
+
noServer: true,
|
|
31
|
+
path: "/dev-tunnel"
|
|
32
|
+
});
|
|
33
|
+
this.hmrWss = new WebSocketServer({
|
|
34
|
+
noServer: true,
|
|
35
|
+
path: "/dev-hmr"
|
|
36
|
+
});
|
|
37
|
+
this.registerTunnelGetter();
|
|
38
|
+
}
|
|
39
|
+
setServer(server) {
|
|
40
|
+
this.server = server;
|
|
41
|
+
}
|
|
42
|
+
/** Asset middleware for the development server. */
|
|
43
|
+
assetMiddleware() {
|
|
44
|
+
return async (req, res) => {
|
|
45
|
+
const email = req.headers["x-forwarded-email"];
|
|
46
|
+
let tunnelId;
|
|
47
|
+
const cookieHeader = req.headers.cookie;
|
|
48
|
+
if (cookieHeader) {
|
|
49
|
+
const match = cookieHeader.match(/dev-tunnel-id=([^;]+)/);
|
|
50
|
+
if (match) tunnelId = match[1];
|
|
51
|
+
}
|
|
52
|
+
if (!tunnelId) tunnelId = generateTunnelIdFromEmail(email);
|
|
53
|
+
if (!tunnelId) return res.status(404).send("Tunnel not ready");
|
|
54
|
+
const tunnel = this.tunnels.get(tunnelId);
|
|
55
|
+
if (!tunnel) return res.status(404).send("Tunnel not found");
|
|
56
|
+
const { ws, approvedViewers, pendingFetches } = tunnel;
|
|
57
|
+
if (!approvedViewers.has(email)) return res.status(403).send("Not approved for this tunnel");
|
|
58
|
+
const path$1 = req.originalUrl;
|
|
59
|
+
const requestId = randomUUID();
|
|
60
|
+
const request = {
|
|
61
|
+
type: "fetch",
|
|
62
|
+
path: path$1,
|
|
63
|
+
method: req.method,
|
|
64
|
+
requestId
|
|
65
|
+
};
|
|
66
|
+
const r = await new Promise((resolve, reject) => {
|
|
67
|
+
const timeout = setTimeout(() => {
|
|
68
|
+
pendingFetches.delete(requestId);
|
|
69
|
+
reject(/* @__PURE__ */ new Error("Asset fetch timeout"));
|
|
70
|
+
}, MAX_ASSET_FETCH_TIMEOUT);
|
|
71
|
+
pendingFetches.set(requestId, {
|
|
72
|
+
resolve,
|
|
73
|
+
reject,
|
|
74
|
+
timeout
|
|
75
|
+
});
|
|
76
|
+
ws.send(JSON.stringify(request));
|
|
77
|
+
}).catch((err) => {
|
|
78
|
+
console.error(`Failed to fetch ${path$1}:`, err.message);
|
|
79
|
+
return {
|
|
80
|
+
status: 504,
|
|
81
|
+
body: Buffer.from(""),
|
|
82
|
+
headers: {}
|
|
83
|
+
};
|
|
84
|
+
});
|
|
85
|
+
res.status(r.status).set(r.headers).send(r.body || Buffer.from(""));
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
/** Dev mode middleware for the development server. */
|
|
89
|
+
devModeMiddleware() {
|
|
90
|
+
return async (req, res, next) => {
|
|
91
|
+
const dev = req.query?.dev;
|
|
92
|
+
if (dev === void 0) return next();
|
|
93
|
+
if (req.path.startsWith("/api") || req.path.startsWith("/query") || req.path.match(/\.(js|css|png|jpg|jpeg|svg|ico|json|woff|woff2|ttf)$/)) return next();
|
|
94
|
+
const viewerEmail = req.headers["x-forwarded-email"];
|
|
95
|
+
const isOwnerMode = dev === "" || dev === "true";
|
|
96
|
+
const tunnelId = isOwnerMode ? generateTunnelIdFromEmail(viewerEmail) : dev.toString();
|
|
97
|
+
if (!tunnelId) return res.status(400).send("Invalid tunnel ID");
|
|
98
|
+
if (!isOwnerMode) {
|
|
99
|
+
const approvalResponse = this.handleViewerApproval(tunnelId, viewerEmail, req.query.retry === "true", res);
|
|
100
|
+
if (approvalResponse) return approvalResponse;
|
|
101
|
+
}
|
|
102
|
+
res.cookie("dev-tunnel-id", tunnelId, {
|
|
103
|
+
httpOnly: false,
|
|
104
|
+
sameSite: "lax"
|
|
105
|
+
});
|
|
106
|
+
const indexPath = path.join(__dirname, "index.html");
|
|
107
|
+
let html = fs.readFileSync(indexPath, "utf-8");
|
|
108
|
+
html = html.replace("<body>", `<body>${getConfigScript()}`);
|
|
109
|
+
res.send(html);
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
/** Setup the dev mode middleware. */
|
|
113
|
+
setup(app) {
|
|
114
|
+
app.use(this.devModeMiddleware());
|
|
115
|
+
app.use(REMOTE_TUNNEL_ASSET_PREFIXES, this.assetMiddleware());
|
|
116
|
+
}
|
|
117
|
+
static isRemoteServerEnabled() {
|
|
118
|
+
return process.env.NODE_ENV !== "production" && process.env.DISABLE_REMOTE_SERVING !== "true" && Boolean(process.env.DATABRICKS_CLIENT_SECRET);
|
|
119
|
+
}
|
|
120
|
+
loadHtmlTemplate(filename, replacements) {
|
|
121
|
+
const filePath = path.join(__dirname, filename);
|
|
122
|
+
let content = fs.readFileSync(filePath, "utf-8");
|
|
123
|
+
for (const [key, value] of Object.entries(replacements)) content = content.replaceAll(`{{${key}}}`, value);
|
|
124
|
+
return content;
|
|
125
|
+
}
|
|
126
|
+
handleViewerApproval(tunnelId, viewerEmail, retry, res) {
|
|
127
|
+
const tunnel = this.tunnels.get(tunnelId);
|
|
128
|
+
if (!tunnel) return res.status(404).send("Tunnel not found");
|
|
129
|
+
if (viewerEmail === tunnel.owner) return null;
|
|
130
|
+
if (retry) tunnel.rejectedViewers.delete(viewerEmail);
|
|
131
|
+
if (tunnel.rejectedViewers.has(viewerEmail)) {
|
|
132
|
+
const html$1 = this.loadHtmlTemplate("denied.html", { tunnelId });
|
|
133
|
+
return res.status(403).send(html$1);
|
|
134
|
+
}
|
|
135
|
+
if (tunnel.approvedViewers.has(viewerEmail)) return null;
|
|
136
|
+
if (!tunnel.pendingRequests.has(viewerEmail)) {
|
|
137
|
+
const requestId = randomUUID();
|
|
138
|
+
tunnel.pendingRequests.add(viewerEmail);
|
|
139
|
+
tunnel.ws.send(JSON.stringify({
|
|
140
|
+
type: "connection:request",
|
|
141
|
+
requestId,
|
|
142
|
+
viewer: viewerEmail
|
|
143
|
+
}));
|
|
144
|
+
}
|
|
145
|
+
const html = this.loadHtmlTemplate("wait.html", { tunnelId });
|
|
146
|
+
return res.status(200).send(html);
|
|
147
|
+
}
|
|
148
|
+
setupWebSocket() {
|
|
149
|
+
this.wss.on("connection", (ws, req) => {
|
|
150
|
+
const email = req.headers["x-forwarded-email"];
|
|
151
|
+
const tunnelId = generateTunnelIdFromEmail(email);
|
|
152
|
+
if (!tunnelId) return ws.close();
|
|
153
|
+
this.tunnels.set(tunnelId, {
|
|
154
|
+
ws,
|
|
155
|
+
owner: email,
|
|
156
|
+
approvedViewers: new Set([email]),
|
|
157
|
+
pendingRequests: /* @__PURE__ */ new Set(),
|
|
158
|
+
rejectedViewers: /* @__PURE__ */ new Set(),
|
|
159
|
+
pendingFetches: /* @__PURE__ */ new Map(),
|
|
160
|
+
pendingFileReads: /* @__PURE__ */ new Map(),
|
|
161
|
+
waitingForBinaryBody: null
|
|
162
|
+
});
|
|
163
|
+
ws.on("message", (msg, isBinary) => {
|
|
164
|
+
const tunnel = this.tunnels.get(tunnelId);
|
|
165
|
+
if (!tunnel) return;
|
|
166
|
+
if (isBinary) {
|
|
167
|
+
if (!tunnel.waitingForBinaryBody) {
|
|
168
|
+
console.warn("Received binary message but no requestId is waiting for body");
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
const requestId = tunnel.waitingForBinaryBody;
|
|
172
|
+
const pending = tunnel.pendingFetches.get(requestId);
|
|
173
|
+
if (!pending || !pending.metadata) {
|
|
174
|
+
console.warn("Received binary message but pending fetch not found");
|
|
175
|
+
tunnel.waitingForBinaryBody = null;
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
tunnel.waitingForBinaryBody = null;
|
|
179
|
+
clearTimeout(pending.timeout);
|
|
180
|
+
tunnel.pendingFetches.delete(requestId);
|
|
181
|
+
pending.resolve({
|
|
182
|
+
status: pending.metadata.status,
|
|
183
|
+
headers: pending.metadata.headers,
|
|
184
|
+
body: msg
|
|
185
|
+
});
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
try {
|
|
189
|
+
const data = JSON.parse(msg.toString());
|
|
190
|
+
if (data.type === "connection:response") {
|
|
191
|
+
if (tunnel && data.viewer) {
|
|
192
|
+
tunnel.pendingRequests.delete(data.viewer);
|
|
193
|
+
if (data.approved) {
|
|
194
|
+
tunnel.approvedViewers.add(data.viewer);
|
|
195
|
+
console.log(`✅ Approved ${data.viewer} for tunnel ${tunnelId}`);
|
|
196
|
+
} else {
|
|
197
|
+
tunnel.rejectedViewers.add(data.viewer);
|
|
198
|
+
console.log(`❌ Denied ${data.viewer} for tunnel ${tunnelId}`);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
} else if (data.type === "fetch:response:meta") {
|
|
202
|
+
const pending = tunnel.pendingFetches.get(data.requestId);
|
|
203
|
+
if (pending) {
|
|
204
|
+
pending.metadata = {
|
|
205
|
+
status: data.status,
|
|
206
|
+
headers: data.headers
|
|
207
|
+
};
|
|
208
|
+
if (data.status === 304 || data.status === 204 || data.status >= 300 && data.status < 400) {
|
|
209
|
+
clearTimeout(pending.timeout);
|
|
210
|
+
tunnel.pendingFetches.delete(data.requestId);
|
|
211
|
+
pending.resolve({
|
|
212
|
+
status: data.status,
|
|
213
|
+
headers: data.headers,
|
|
214
|
+
body: Buffer.from("")
|
|
215
|
+
});
|
|
216
|
+
} else tunnel.waitingForBinaryBody = data.requestId;
|
|
217
|
+
}
|
|
218
|
+
} else if (data.type === "file:read:response") {
|
|
219
|
+
const pending = tunnel.pendingFileReads.get(data.requestId);
|
|
220
|
+
if (pending) {
|
|
221
|
+
clearTimeout(pending.timeout);
|
|
222
|
+
tunnel.pendingFileReads.delete(data.requestId);
|
|
223
|
+
if (data.error) pending.reject(new Error(data.error));
|
|
224
|
+
else pending.resolve(data.content);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
} catch (e) {
|
|
228
|
+
console.error("Failed to parse WebSocket message:", e);
|
|
229
|
+
}
|
|
230
|
+
});
|
|
231
|
+
ws.send(JSON.stringify({
|
|
232
|
+
type: "tunnel:ready",
|
|
233
|
+
tunnelId
|
|
234
|
+
}));
|
|
235
|
+
ws.on("close", () => {
|
|
236
|
+
const tunnel = this.tunnels.get(tunnelId);
|
|
237
|
+
if (tunnel) {
|
|
238
|
+
for (const [_, pending] of tunnel.pendingFetches) {
|
|
239
|
+
clearTimeout(pending.timeout);
|
|
240
|
+
pending.reject(/* @__PURE__ */ new Error("Tunnel closed"));
|
|
241
|
+
}
|
|
242
|
+
tunnel.pendingFetches.clear();
|
|
243
|
+
}
|
|
244
|
+
this.tunnels.delete(tunnelId);
|
|
245
|
+
});
|
|
246
|
+
});
|
|
247
|
+
this.hmrWss.on("connection", (browserWs, req) => {
|
|
248
|
+
const cookies = parseCookies(req);
|
|
249
|
+
const email = req.headers["x-forwarded-email"];
|
|
250
|
+
const tunnelId = cookies["dev-tunnel-id"] || generateTunnelIdFromEmail(email);
|
|
251
|
+
if (!tunnelId) return browserWs.close();
|
|
252
|
+
const cliTunnel = this.tunnels.get(tunnelId);
|
|
253
|
+
if (!cliTunnel) return browserWs.close();
|
|
254
|
+
const { ws: cliWs, approvedViewers } = cliTunnel;
|
|
255
|
+
if (!approvedViewers.has(email)) return browserWs.close(1008, "Not approved");
|
|
256
|
+
browserWs.on("message", (msg) => {
|
|
257
|
+
const hmrStart = Date.now();
|
|
258
|
+
console.log("browser -> cli browserWS message", msg.toString());
|
|
259
|
+
cliWs.send(JSON.stringify({
|
|
260
|
+
type: "hmr:message",
|
|
261
|
+
body: msg.toString(),
|
|
262
|
+
timestamp: hmrStart
|
|
263
|
+
}));
|
|
264
|
+
});
|
|
265
|
+
const cliHandler = (msg, isBinary) => {
|
|
266
|
+
if (isBinary) return;
|
|
267
|
+
try {
|
|
268
|
+
const data = JSON.parse(msg.toString());
|
|
269
|
+
if (data.type === "hmr:message") browserWs.send(data.body);
|
|
270
|
+
} catch {
|
|
271
|
+
console.error("Failed to parse CLI message for HMR:", msg.toString().substring(0, 100));
|
|
272
|
+
}
|
|
273
|
+
};
|
|
274
|
+
cliWs.on("message", cliHandler);
|
|
275
|
+
browserWs.on("close", () => {
|
|
276
|
+
cliWs.off("message", cliHandler);
|
|
277
|
+
});
|
|
278
|
+
});
|
|
279
|
+
this.server?.on("upgrade", (req, socket, head) => {
|
|
280
|
+
const url = req.url ?? "";
|
|
281
|
+
if (url.startsWith("/dev-tunnel")) this.wss.handleUpgrade(req, socket, head, (ws) => {
|
|
282
|
+
this.wss.emit("connection", ws, req);
|
|
283
|
+
});
|
|
284
|
+
else if (url.startsWith("/dev-hmr")) this.hmrWss.handleUpgrade(req, socket, head, (browserWs) => {
|
|
285
|
+
this.hmrWss.emit("connection", browserWs, req);
|
|
286
|
+
});
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
registerTunnelGetter() {
|
|
290
|
+
this.devFileReader.registerTunnelGetter(this.getTunnelForRequest.bind(this));
|
|
291
|
+
}
|
|
292
|
+
getTunnelForRequest(req) {
|
|
293
|
+
const email = req.headers["x-forwarded-email"];
|
|
294
|
+
const cookieHeader = req.headers.cookie;
|
|
295
|
+
let tunnelId;
|
|
296
|
+
if (cookieHeader) {
|
|
297
|
+
const match = cookieHeader.match(/dev-tunnel-id=([^;]+)/);
|
|
298
|
+
if (match) tunnelId = match[1];
|
|
299
|
+
}
|
|
300
|
+
if (!tunnelId) tunnelId = generateTunnelIdFromEmail(email);
|
|
301
|
+
return tunnelId ? this.tunnels.get(tunnelId) || null : null;
|
|
302
|
+
}
|
|
303
|
+
cleanup() {
|
|
304
|
+
for (const [, tunnel] of this.tunnels) {
|
|
305
|
+
for (const [_, pending] of tunnel.pendingFetches) {
|
|
306
|
+
clearTimeout(pending.timeout);
|
|
307
|
+
pending.reject(/* @__PURE__ */ new Error("Server shutting down"));
|
|
308
|
+
}
|
|
309
|
+
tunnel.pendingFetches.clear();
|
|
310
|
+
tunnel.ws.close();
|
|
311
|
+
}
|
|
312
|
+
this.tunnels.clear();
|
|
313
|
+
if (this.wss) this.wss.close();
|
|
314
|
+
if (this.hmrWss) this.hmrWss.close();
|
|
315
|
+
}
|
|
316
|
+
};
|
|
317
|
+
|
|
318
|
+
//#endregion
|
|
319
|
+
export { RemoteTunnelManager };
|
|
320
|
+
//# sourceMappingURL=remote-tunnel-manager.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"remote-tunnel-manager.js","names":["tunnelId: string | undefined","path","html"],"sources":["../../../src/server/remote-tunnel/remote-tunnel-manager.ts"],"sourcesContent":["import { randomUUID } from \"node:crypto\";\nimport fs from \"node:fs\";\nimport type { Server as HTTPServer } from \"node:http\";\nimport path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport type express from \"express\";\nimport type { TunnelConnection } from \"shared\";\nimport { WebSocketServer } from \"ws\";\nimport {\n generateTunnelIdFromEmail,\n getConfigScript,\n parseCookies,\n} from \"../utils\";\nimport { REMOTE_TUNNEL_ASSET_PREFIXES } from \"./gate\";\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = path.dirname(__filename);\nconst MAX_ASSET_FETCH_TIMEOUT = 60_000;\n\ninterface DevFileReader {\n registerTunnelGetter(\n getter: (req: express.Request) => TunnelConnection | null,\n ): void;\n}\n\n/**\n * Remote tunnel manager for the AppKit.\n *\n * This class is responsible for managing the remote tunnels for the development server.\n * It also handles the asset fetching and the HMR for the development server.\n *\n * @example\n * ```ts\n * const remoteTunnelManager = new RemoteTunnelManager(devFileReader);\n * remoteTunnelManager.setup(app);\n * ```\n */\nexport class RemoteTunnelManager {\n private tunnels = new Map<string, TunnelConnection>();\n private wss: WebSocketServer;\n private hmrWss: WebSocketServer;\n private server?: HTTPServer;\n private devFileReader: DevFileReader;\n\n constructor(devFileReader: DevFileReader) {\n this.devFileReader = devFileReader;\n this.wss = new WebSocketServer({ noServer: true, path: \"/dev-tunnel\" });\n this.hmrWss = new WebSocketServer({ noServer: true, path: \"/dev-hmr\" });\n\n this.registerTunnelGetter();\n }\n\n setServer(server: HTTPServer) {\n this.server = server;\n }\n\n /** Asset middleware for the development server. */\n assetMiddleware() {\n return async (req: express.Request, res: express.Response) => {\n const email = req.headers[\"x-forwarded-email\"] as string;\n\n // Try cookie first, then generate from email\n let tunnelId: string | undefined;\n const cookieHeader = req.headers.cookie;\n\n if (cookieHeader) {\n // Fast path: extract dev-tunnel-id from cookie without full parse\n const match = cookieHeader.match(/dev-tunnel-id=([^;]+)/);\n if (match) {\n tunnelId = match[1];\n }\n }\n\n if (!tunnelId) {\n tunnelId = generateTunnelIdFromEmail(email);\n }\n\n if (!tunnelId) return res.status(404).send(\"Tunnel not ready\");\n\n const tunnel = this.tunnels.get(tunnelId);\n\n if (!tunnel) return res.status(404).send(\"Tunnel not found\");\n\n const { ws, approvedViewers, pendingFetches } = tunnel;\n\n if (!approvedViewers.has(email)) {\n return res.status(403).send(\"Not approved for this tunnel\");\n }\n\n const path = req.originalUrl;\n const requestId = randomUUID();\n\n const request = { type: \"fetch\", path, method: req.method, requestId };\n\n const response = await new Promise((resolve, reject) => {\n const timeout = setTimeout(() => {\n pendingFetches.delete(requestId);\n reject(new Error(\"Asset fetch timeout\"));\n }, MAX_ASSET_FETCH_TIMEOUT);\n\n pendingFetches.set(requestId, { resolve, reject, timeout });\n\n ws.send(JSON.stringify(request));\n }).catch((err) => {\n console.error(`Failed to fetch ${path}:`, err.message);\n return { status: 504, body: Buffer.from(\"\"), headers: {} };\n });\n\n const r = response as any;\n\n res\n .status(r.status)\n .set(r.headers)\n .send(r.body || Buffer.from(\"\"));\n };\n }\n\n /** Dev mode middleware for the development server. */\n devModeMiddleware() {\n return async (\n req: express.Request,\n res: express.Response,\n next: express.NextFunction,\n ) => {\n const dev = req.query?.dev;\n\n if (dev === undefined) {\n return next();\n }\n\n if (\n req.path.startsWith(\"/api\") ||\n req.path.startsWith(\"/query\") ||\n req.path.match(/\\.(js|css|png|jpg|jpeg|svg|ico|json|woff|woff2|ttf)$/)\n ) {\n return next();\n }\n\n const viewerEmail = req.headers[\"x-forwarded-email\"] as string;\n const isOwnerMode = dev === \"\" || dev === \"true\";\n\n const tunnelId = isOwnerMode\n ? generateTunnelIdFromEmail(viewerEmail)\n : dev.toString();\n\n if (!tunnelId) {\n return res.status(400).send(\"Invalid tunnel ID\");\n }\n\n if (!isOwnerMode) {\n const approvalResponse = this.handleViewerApproval(\n tunnelId,\n viewerEmail,\n req.query.retry === \"true\",\n res,\n );\n\n if (approvalResponse) {\n return approvalResponse;\n }\n }\n\n res.cookie(\"dev-tunnel-id\", tunnelId, {\n httpOnly: false,\n sameSite: \"lax\",\n });\n\n const indexPath = path.join(__dirname, \"index.html\");\n let html = fs.readFileSync(indexPath, \"utf-8\");\n html = html.replace(\"<body>\", `<body>${getConfigScript()}`);\n\n res.send(html);\n };\n }\n\n /** Setup the dev mode middleware. */\n setup(app: express.Application) {\n app.use(this.devModeMiddleware());\n app.use(REMOTE_TUNNEL_ASSET_PREFIXES, this.assetMiddleware());\n }\n\n static isRemoteServerEnabled() {\n return (\n process.env.NODE_ENV !== \"production\" &&\n process.env.DISABLE_REMOTE_SERVING !== \"true\" &&\n // DATABRICKS_CLIENT_SECRET is set in the .env file for deployed environments\n Boolean(process.env.DATABRICKS_CLIENT_SECRET)\n );\n }\n\n private loadHtmlTemplate(\n filename: string,\n replacements: Record<string, string>,\n ): string {\n const filePath = path.join(__dirname, filename);\n let content = fs.readFileSync(filePath, \"utf-8\");\n\n for (const [key, value] of Object.entries(replacements)) {\n content = content.replaceAll(`{{${key}}}`, value);\n }\n\n return content;\n }\n\n private handleViewerApproval(\n tunnelId: string,\n viewerEmail: string,\n retry: boolean,\n res: express.Response,\n ): express.Response | null {\n const tunnel = this.tunnels.get(tunnelId);\n\n if (!tunnel) {\n return res.status(404).send(\"Tunnel not found\");\n }\n\n if (viewerEmail === tunnel.owner) {\n return null;\n }\n\n if (retry) {\n tunnel.rejectedViewers.delete(viewerEmail);\n }\n\n if (tunnel.rejectedViewers.has(viewerEmail)) {\n const html = this.loadHtmlTemplate(\"denied.html\", { tunnelId });\n return res.status(403).send(html);\n }\n\n if (tunnel.approvedViewers.has(viewerEmail)) {\n return null;\n }\n\n if (!tunnel.pendingRequests.has(viewerEmail)) {\n const requestId = randomUUID();\n tunnel.pendingRequests.add(viewerEmail);\n tunnel.ws.send(\n JSON.stringify({\n type: \"connection:request\",\n requestId,\n viewer: viewerEmail,\n }),\n );\n }\n\n const html = this.loadHtmlTemplate(\"wait.html\", { tunnelId });\n return res.status(200).send(html);\n }\n\n setupWebSocket() {\n this.wss.on(\"connection\", (ws, req) => {\n const email = req.headers[\"x-forwarded-email\"] as string;\n const tunnelId = generateTunnelIdFromEmail(email);\n\n if (!tunnelId) return ws.close();\n\n this.tunnels.set(tunnelId, {\n ws,\n owner: email,\n approvedViewers: new Set([email]),\n pendingRequests: new Set(),\n rejectedViewers: new Set(),\n pendingFetches: new Map(),\n pendingFileReads: new Map(),\n waitingForBinaryBody: null,\n });\n\n ws.on(\"message\", (msg, isBinary) => {\n const tunnel = this.tunnels.get(tunnelId);\n if (!tunnel) return;\n\n if (isBinary) {\n if (!tunnel.waitingForBinaryBody) {\n console.warn(\n \"Received binary message but no requestId is waiting for body\",\n );\n return;\n }\n\n const requestId = tunnel.waitingForBinaryBody;\n const pending = tunnel.pendingFetches.get(requestId);\n\n if (!pending || !pending.metadata) {\n console.warn(\"Received binary message but pending fetch not found\");\n tunnel.waitingForBinaryBody = null;\n return;\n }\n\n tunnel.waitingForBinaryBody = null;\n clearTimeout(pending.timeout);\n tunnel.pendingFetches.delete(requestId);\n\n pending.resolve({\n status: pending.metadata.status,\n headers: pending.metadata.headers,\n body: msg as Buffer,\n });\n return;\n }\n\n try {\n const data = JSON.parse(msg.toString());\n\n if (data.type === \"connection:response\") {\n if (tunnel && data.viewer) {\n tunnel.pendingRequests.delete(data.viewer);\n\n if (data.approved) {\n tunnel.approvedViewers.add(data.viewer);\n console.log(\n `✅ Approved ${data.viewer} for tunnel ${tunnelId}`,\n );\n } else {\n tunnel.rejectedViewers.add(data.viewer);\n console.log(`❌ Denied ${data.viewer} for tunnel ${tunnelId}`);\n }\n }\n } else if (data.type === \"fetch:response:meta\") {\n const pending = tunnel.pendingFetches.get(data.requestId);\n if (pending) {\n pending.metadata = {\n status: data.status,\n headers: data.headers,\n };\n if (\n data.status === 304 ||\n data.status === 204 ||\n (data.status >= 300 && data.status < 400)\n ) {\n clearTimeout(pending.timeout);\n tunnel.pendingFetches.delete(data.requestId);\n pending.resolve({\n status: data.status,\n headers: data.headers,\n body: Buffer.from(\"\"),\n });\n } else {\n tunnel.waitingForBinaryBody = data.requestId;\n }\n }\n } else if (data.type === \"file:read:response\") {\n const pending = tunnel.pendingFileReads.get(data.requestId);\n if (pending) {\n clearTimeout(pending.timeout);\n tunnel.pendingFileReads.delete(data.requestId);\n\n if (data.error) {\n pending.reject(new Error(data.error));\n } else {\n pending.resolve(data.content);\n }\n }\n }\n } catch (e) {\n console.error(\"Failed to parse WebSocket message:\", e);\n }\n });\n\n ws.send(JSON.stringify({ type: \"tunnel:ready\", tunnelId }));\n\n ws.on(\"close\", () => {\n const tunnel = this.tunnels.get(tunnelId);\n\n if (tunnel) {\n for (const [_, pending] of tunnel.pendingFetches) {\n clearTimeout(pending.timeout);\n pending.reject(new Error(\"Tunnel closed\"));\n }\n tunnel.pendingFetches.clear();\n }\n\n this.tunnels.delete(tunnelId);\n });\n });\n\n this.hmrWss.on(\"connection\", (browserWs, req) => {\n const cookies = parseCookies(req);\n const email = req.headers[\"x-forwarded-email\"] as string;\n const tunnelId =\n cookies[\"dev-tunnel-id\"] || generateTunnelIdFromEmail(email);\n\n if (!tunnelId) return browserWs.close();\n\n const cliTunnel = this.tunnels.get(tunnelId);\n\n if (!cliTunnel) return browserWs.close();\n\n const { ws: cliWs, approvedViewers } = cliTunnel;\n\n if (!approvedViewers.has(email)) {\n return browserWs.close(1008, \"Not approved\");\n }\n // Browser → CLI\n browserWs.on(\"message\", (msg) => {\n const hmrStart = Date.now();\n console.log(\"browser -> cli browserWS message\", msg.toString());\n cliWs.send(\n JSON.stringify({\n type: \"hmr:message\",\n body: msg.toString(),\n timestamp: hmrStart,\n }),\n );\n });\n\n // // CLI → Browser\n const cliHandler = (msg: Buffer | string, isBinary: boolean) => {\n // Ignore binary messages (they're for fetch responses, not HMR)\n if (isBinary) return;\n\n try {\n const data = JSON.parse(msg.toString());\n\n if (data.type === \"hmr:message\") {\n browserWs.send(data.body);\n }\n } catch {\n console.error(\n \"Failed to parse CLI message for HMR:\",\n msg.toString().substring(0, 100),\n );\n }\n };\n cliWs.on(\"message\", cliHandler);\n\n browserWs.on(\"close\", () => {\n cliWs.off(\"message\", cliHandler);\n });\n });\n\n // // Browser HMR connection\n this.server?.on(\"upgrade\", (req, socket, head) => {\n const url = req.url ?? \"\";\n\n if (url.startsWith(\"/dev-tunnel\")) {\n this.wss.handleUpgrade(req, socket, head, (ws) => {\n this.wss.emit(\"connection\", ws, req);\n });\n } else if (url.startsWith(\"/dev-hmr\")) {\n this.hmrWss.handleUpgrade(req, socket, head, (browserWs) => {\n this.hmrWss.emit(\"connection\", browserWs, req);\n });\n }\n });\n }\n\n registerTunnelGetter() {\n this.devFileReader.registerTunnelGetter(\n this.getTunnelForRequest.bind(this),\n );\n }\n\n getTunnelForRequest(req: express.Request) {\n const email = req.headers[\"x-forwarded-email\"] as string;\n const cookieHeader = req.headers.cookie;\n\n let tunnelId: string | undefined;\n\n if (cookieHeader) {\n const match = cookieHeader.match(/dev-tunnel-id=([^;]+)/);\n if (match) {\n tunnelId = match[1];\n }\n }\n\n if (!tunnelId) {\n tunnelId = generateTunnelIdFromEmail(email);\n }\n\n return tunnelId ? this.tunnels.get(tunnelId) || null : null;\n }\n\n cleanup() {\n for (const [, tunnel] of this.tunnels) {\n for (const [_, pending] of tunnel.pendingFetches) {\n clearTimeout(pending.timeout);\n pending.reject(new Error(\"Server shutting down\"));\n }\n tunnel.pendingFetches.clear();\n tunnel.ws.close();\n }\n this.tunnels.clear();\n\n if (this.wss) {\n this.wss.close();\n }\n if (this.hmrWss) {\n this.hmrWss.close();\n }\n }\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<title>Waiting for Approval</title>
|
|
6
|
+
<meta http-equiv="refresh" content="2">
|
|
7
|
+
</head>
|
|
8
|
+
<body>
|
|
9
|
+
<div id="root">
|
|
10
|
+
<style>
|
|
11
|
+
body {
|
|
12
|
+
background: #1b1b1d;
|
|
13
|
+
height: 100vh;
|
|
14
|
+
margin: 0;
|
|
15
|
+
display: flex;
|
|
16
|
+
align-items: center;
|
|
17
|
+
justify-content: center;
|
|
18
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto",
|
|
19
|
+
"Helvetica", "Arial", sans-serif;
|
|
20
|
+
}
|
|
21
|
+
.loader-container {
|
|
22
|
+
display: flex;
|
|
23
|
+
flex-direction: column;
|
|
24
|
+
align-items: center;
|
|
25
|
+
padding: 50px 70px;
|
|
26
|
+
}
|
|
27
|
+
.logo {
|
|
28
|
+
width: 64px;
|
|
29
|
+
height: 64px;
|
|
30
|
+
margin-bottom: 32px;
|
|
31
|
+
position: relative;
|
|
32
|
+
}
|
|
33
|
+
.logo svg {
|
|
34
|
+
width: 100%;
|
|
35
|
+
height: 100%;
|
|
36
|
+
}
|
|
37
|
+
.spinner {
|
|
38
|
+
width: 32px;
|
|
39
|
+
height: 32px;
|
|
40
|
+
border: 3px solid rgba(255, 255, 255, 0.1);
|
|
41
|
+
border-top: 3px solid #ff3621;
|
|
42
|
+
border-radius: 50%;
|
|
43
|
+
animation: spin 0.8s linear infinite;
|
|
44
|
+
margin-bottom: 32px;
|
|
45
|
+
}
|
|
46
|
+
@keyframes spin {
|
|
47
|
+
0% {
|
|
48
|
+
transform: rotate(0deg);
|
|
49
|
+
}
|
|
50
|
+
100% {
|
|
51
|
+
transform: rotate(360deg);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
.loading-text {
|
|
55
|
+
font-size: 1.125rem;
|
|
56
|
+
color: #ffffff;
|
|
57
|
+
letter-spacing: 0.02em;
|
|
58
|
+
font-weight: 400;
|
|
59
|
+
text-align: center;
|
|
60
|
+
margin-top: 0;
|
|
61
|
+
opacity: 0.9;
|
|
62
|
+
}
|
|
63
|
+
.sub-text {
|
|
64
|
+
font-size: 0.875rem;
|
|
65
|
+
color: rgba(255, 255, 255, 0.6);
|
|
66
|
+
margin-top: 12px;
|
|
67
|
+
text-align: center;
|
|
68
|
+
}
|
|
69
|
+
.tunnel-id {
|
|
70
|
+
font-size: 0.75rem;
|
|
71
|
+
color: rgba(255, 255, 255, 0.4);
|
|
72
|
+
margin-top: 8px;
|
|
73
|
+
letter-spacing: 0.05em;
|
|
74
|
+
font-family: monospace;
|
|
75
|
+
}
|
|
76
|
+
.databricks-brand {
|
|
77
|
+
font-size: 0.875rem;
|
|
78
|
+
color: rgba(255, 255, 255, 0.5);
|
|
79
|
+
margin-top: 24px;
|
|
80
|
+
letter-spacing: 0.05em;
|
|
81
|
+
text-transform: uppercase;
|
|
82
|
+
font-weight: 500;
|
|
83
|
+
}
|
|
84
|
+
.dots {
|
|
85
|
+
display: inline-block;
|
|
86
|
+
animation: dots 1.5s steps(4, end) infinite;
|
|
87
|
+
}
|
|
88
|
+
@keyframes dots {
|
|
89
|
+
0%,
|
|
90
|
+
20% {
|
|
91
|
+
content: ".";
|
|
92
|
+
}
|
|
93
|
+
40% {
|
|
94
|
+
content: "..";
|
|
95
|
+
}
|
|
96
|
+
60%,
|
|
97
|
+
100% {
|
|
98
|
+
content: "...";
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
</style>
|
|
102
|
+
<div class="loader-container">
|
|
103
|
+
<div class="logo">
|
|
104
|
+
<svg
|
|
105
|
+
version="1.1"
|
|
106
|
+
id="Layer_1"
|
|
107
|
+
xmlns:x="ns_extend;"
|
|
108
|
+
xmlns:i="ns_ai;"
|
|
109
|
+
xmlns:graph="ns_graphs;"
|
|
110
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
111
|
+
xmlns:xlink="http://www.w3.org/1999/xlink"
|
|
112
|
+
x="0px"
|
|
113
|
+
y="0px"
|
|
114
|
+
viewBox="0 0 40.1 42"
|
|
115
|
+
style="enable-background: new 0 0 40.1 42"
|
|
116
|
+
xml:space="preserve"
|
|
117
|
+
>
|
|
118
|
+
<style type="text/css">
|
|
119
|
+
.st0 {
|
|
120
|
+
fill: #ff3621;
|
|
121
|
+
}
|
|
122
|
+
</style>
|
|
123
|
+
<metadata>
|
|
124
|
+
<sfw xmlns="ns_sfw;">
|
|
125
|
+
<slices></slices>
|
|
126
|
+
<sliceSourceBounds
|
|
127
|
+
bottomLeftOrigin="true"
|
|
128
|
+
height="42"
|
|
129
|
+
width="40.1"
|
|
130
|
+
x="-69.1"
|
|
131
|
+
y="-10.5"
|
|
132
|
+
></sliceSourceBounds>
|
|
133
|
+
</sfw>
|
|
134
|
+
</metadata>
|
|
135
|
+
<g>
|
|
136
|
+
<path
|
|
137
|
+
class="st0"
|
|
138
|
+
d="M40.1,31.1v-7.4l-0.8-0.5L20.1,33.7l-18.2-10l0-4.3l18.2,9.9l20.1-10.9v-7.3l-0.8-0.5L20.1,21.2L2.6,11.6
|
|
139
|
+
L20.1,2l14.1,7.7l1.1-0.6V8.3L20.1,0L0,10.9V12L20.1,23l18.2-10v4.4l-18.2,10L0.8,16.8L0,17.3v7.4l20.1,10.9l18.2-9.9v4.3l-18.2,10
|
|
140
|
+
L0.8,29.5L0,30v1.1L20.1,42L40.1,31.1z"
|
|
141
|
+
></path>
|
|
142
|
+
</g>
|
|
143
|
+
</svg>
|
|
144
|
+
</div>
|
|
145
|
+
|
|
146
|
+
<div class="spinner"></div>
|
|
147
|
+
<div class="loading-text">
|
|
148
|
+
Waiting for approval<span class="dots">...</span>
|
|
149
|
+
</div>
|
|
150
|
+
<div class="sub-text">
|
|
151
|
+
Requesting access to tunnel from owner
|
|
152
|
+
</div>
|
|
153
|
+
<div class="tunnel-id">Tunnel ID: {{tunnelId}}</div>
|
|
154
|
+
<div class="databricks-brand">Databricks</div>
|
|
155
|
+
</div>
|
|
156
|
+
</div>
|
|
157
|
+
</body>
|
|
158
|
+
</html>
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { BaseServer } from "./base-server.js";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
import express from "express";
|
|
5
|
+
|
|
6
|
+
//#region src/server/static-server.ts
|
|
7
|
+
/**
|
|
8
|
+
* Static server for the AppKit.
|
|
9
|
+
*
|
|
10
|
+
* Serves pre-built static files in production mode. Handles SPA routing
|
|
11
|
+
* by serving index.html for non-API routes and injects runtime configuration.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```ts
|
|
15
|
+
* const staticServer = new StaticServer(app, staticPath, endpoints);
|
|
16
|
+
* staticServer.setup();
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
var StaticServer = class extends BaseServer {
|
|
20
|
+
constructor(app, staticPath, endpoints = {}) {
|
|
21
|
+
super(app, endpoints);
|
|
22
|
+
this.staticPath = staticPath;
|
|
23
|
+
}
|
|
24
|
+
/** Setup the static server. */
|
|
25
|
+
setup() {
|
|
26
|
+
this.app.use(express.static(this.staticPath, { index: false }));
|
|
27
|
+
this.app.get("*", (req, res, next) => {
|
|
28
|
+
if (req.path.startsWith("/api") || req.path.startsWith("/query")) return next();
|
|
29
|
+
this.serveIndex(res);
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
/** Serve the index.html file. */
|
|
33
|
+
serveIndex(res) {
|
|
34
|
+
const indexPath = path.join(this.staticPath, "index.html");
|
|
35
|
+
if (!fs.existsSync(indexPath)) {
|
|
36
|
+
res.status(404).send("index.html not found");
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
let html = fs.readFileSync(indexPath, "utf-8");
|
|
40
|
+
html = html.replace("<body>", `<body>${this.getConfigScript()}`);
|
|
41
|
+
res.send(html);
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
//#endregion
|
|
46
|
+
export { StaticServer };
|
|
47
|
+
//# sourceMappingURL=static-server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"static-server.js","names":["expressStatic"],"sources":["../../src/server/static-server.ts"],"sourcesContent":["import fs from \"node:fs\";\nimport path from \"node:path\";\nimport type express from \"express\";\nimport expressStatic from \"express\";\nimport { BaseServer } from \"./base-server\";\nimport type { PluginEndpoints } from \"./utils\";\n\n/**\n * Static server for the AppKit.\n *\n * Serves pre-built static files in production mode. Handles SPA routing\n * by serving index.html for non-API routes and injects runtime configuration.\n *\n * @example\n * ```ts\n * const staticServer = new StaticServer(app, staticPath, endpoints);\n * staticServer.setup();\n * ```\n */\nexport class StaticServer extends BaseServer {\n private staticPath: string;\n\n constructor(\n app: express.Application,\n staticPath: string,\n endpoints: PluginEndpoints = {},\n ) {\n super(app, endpoints);\n this.staticPath = staticPath;\n }\n\n /** Setup the static server. */\n setup() {\n this.app.use(\n expressStatic.static(this.staticPath, {\n index: false,\n }),\n );\n\n this.app.get(\"*\", (req, res, next) => {\n if (req.path.startsWith(\"/api\") || req.path.startsWith(\"/query\")) {\n return next();\n }\n this.serveIndex(res);\n });\n }\n\n /** Serve the index.html file. */\n private serveIndex(res: express.Response) {\n const indexPath = path.join(this.staticPath, \"index.html\");\n\n if (!fs.existsSync(indexPath)) {\n res.status(404).send(\"index.html not found\");\n return;\n }\n\n let html = fs.readFileSync(indexPath, \"utf-8\");\n html = html.replace(\"<body>\", `<body>${this.getConfigScript()}`);\n res.send(html);\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAmBA,IAAa,eAAb,cAAkC,WAAW;CAG3C,YACE,KACA,YACA,YAA6B,EAAE,EAC/B;AACA,QAAM,KAAK,UAAU;AACrB,OAAK,aAAa;;;CAIpB,QAAQ;AACN,OAAK,IAAI,IACPA,QAAc,OAAO,KAAK,YAAY,EACpC,OAAO,OACR,CAAC,CACH;AAED,OAAK,IAAI,IAAI,MAAM,KAAK,KAAK,SAAS;AACpC,OAAI,IAAI,KAAK,WAAW,OAAO,IAAI,IAAI,KAAK,WAAW,SAAS,CAC9D,QAAO,MAAM;AAEf,QAAK,WAAW,IAAI;IACpB;;;CAIJ,AAAQ,WAAW,KAAuB;EACxC,MAAM,YAAY,KAAK,KAAK,KAAK,YAAY,aAAa;AAE1D,MAAI,CAAC,GAAG,WAAW,UAAU,EAAE;AAC7B,OAAI,OAAO,IAAI,CAAC,KAAK,uBAAuB;AAC5C;;EAGF,IAAI,OAAO,GAAG,aAAa,WAAW,QAAQ;AAC9C,SAAO,KAAK,QAAQ,UAAU,SAAS,KAAK,iBAAiB,GAAG;AAChE,MAAI,KAAK,KAAK"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { BasePluginConfig } from "../shared/src/plugin.js";
|
|
2
|
+
import { Plugin } from "../plugin/plugin.js";
|
|
3
|
+
|
|
4
|
+
//#region src/server/types.d.ts
|
|
5
|
+
interface ServerConfig extends BasePluginConfig {
|
|
6
|
+
port?: number;
|
|
7
|
+
plugins?: Record<string, Plugin>;
|
|
8
|
+
staticPath?: string;
|
|
9
|
+
autoStart?: boolean;
|
|
10
|
+
host?: string;
|
|
11
|
+
}
|
|
12
|
+
//#endregion
|
|
13
|
+
export { ServerConfig };
|
|
14
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","names":[],"sources":["../../src/server/types.ts"],"sourcesContent":[],"mappings":";;;;UAGiB,YAAA,SAAqB;;EAArB,OAAA,CAAA,EAEL,MAFkB,CAAA,MAAA,EAEH,MAFG,CAAA;EAAA,UAAA,CAAA,EAAA,MAAA;WAEH,CAAA,EAAA,OAAA;MAAf,CAAA,EAAA,MAAA"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import crypto from "node:crypto";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
|
|
5
|
+
//#region src/server/utils.ts
|
|
6
|
+
function parseCookies(req) {
|
|
7
|
+
const cookieHeader = req.headers.cookie;
|
|
8
|
+
if (!cookieHeader) return {};
|
|
9
|
+
if (cookieHeader.indexOf(";") === -1) {
|
|
10
|
+
const eqIndex = cookieHeader.indexOf("=");
|
|
11
|
+
if (eqIndex === -1) return {};
|
|
12
|
+
return { [cookieHeader.slice(0, eqIndex).trim()]: cookieHeader.slice(eqIndex + 1) };
|
|
13
|
+
}
|
|
14
|
+
const cookies = {};
|
|
15
|
+
const parts = cookieHeader.split(";");
|
|
16
|
+
for (let i = 0; i < parts.length; i++) {
|
|
17
|
+
const eqIndex = parts[i].indexOf("=");
|
|
18
|
+
if (eqIndex !== -1) {
|
|
19
|
+
const key = parts[i].slice(0, eqIndex).trim();
|
|
20
|
+
cookies[key] = parts[i].slice(eqIndex + 1);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return cookies;
|
|
24
|
+
}
|
|
25
|
+
function generateTunnelIdFromEmail(email) {
|
|
26
|
+
if (!email) return void 0;
|
|
27
|
+
return crypto.createHash("sha256").update(email).digest("base64url").slice(0, 8);
|
|
28
|
+
}
|
|
29
|
+
function getRoutes(stack, basePath = "") {
|
|
30
|
+
const routes = [];
|
|
31
|
+
stack.forEach((layer) => {
|
|
32
|
+
if (layer.route) {
|
|
33
|
+
const path$1 = basePath + layer.route.path;
|
|
34
|
+
const methods = Object.keys(layer.route.methods).map((m) => m.toUpperCase());
|
|
35
|
+
routes.push({
|
|
36
|
+
path: path$1,
|
|
37
|
+
methods
|
|
38
|
+
});
|
|
39
|
+
} else if (layer.name === "router" && layer.handle.stack) {
|
|
40
|
+
const nestedBase = basePath + layer.regexp.source.replace("^\\", "").replace("\\/?(?=\\/|$)", "").replace(/\\\//g, "/").replace(/\$$/, "") || "";
|
|
41
|
+
routes.push(...getRoutes(layer.handle.stack, nestedBase));
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
return routes;
|
|
45
|
+
}
|
|
46
|
+
function getQueries(configFolder) {
|
|
47
|
+
const queriesFolder = path.join(configFolder, "queries");
|
|
48
|
+
if (!fs.existsSync(queriesFolder)) return {};
|
|
49
|
+
return Object.fromEntries(fs.readdirSync(queriesFolder).filter((f) => path.extname(f) === ".sql").map((f) => [path.basename(f, ".sql"), path.basename(f, ".sql")]));
|
|
50
|
+
}
|
|
51
|
+
function getRuntimeConfig(endpoints = {}) {
|
|
52
|
+
const configFolder = path.join(process.cwd(), "config");
|
|
53
|
+
return {
|
|
54
|
+
appName: process.env.DATABRICKS_APP_NAME || "",
|
|
55
|
+
queries: getQueries(configFolder),
|
|
56
|
+
endpoints
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
function getConfigScript(endpoints = {}) {
|
|
60
|
+
const config = getRuntimeConfig(endpoints);
|
|
61
|
+
return `
|
|
62
|
+
<script>
|
|
63
|
+
window.__CONFIG__ = ${JSON.stringify(config)};
|
|
64
|
+
<\/script>
|
|
65
|
+
`;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
//#endregion
|
|
69
|
+
export { generateTunnelIdFromEmail, getConfigScript, getRoutes, parseCookies };
|
|
70
|
+
//# sourceMappingURL=utils.js.map
|