@orbit-work/block-sdk 0.1.0 → 0.2.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.
- package/README.md +51 -3
- package/dist/chunk-MCUJICYI.js +11 -0
- package/dist/chunk-MCUJICYI.js.map +1 -0
- package/dist/index.d.ts +11 -1
- package/dist/index.js +33 -2
- package/dist/index.js.map +1 -1
- package/dist/vite.d.ts +9 -0
- package/dist/vite.js +97 -0
- package/dist/vite.js.map +1 -0
- package/package.json +14 -2
package/README.md
CHANGED
|
@@ -24,10 +24,58 @@ offline fallback. `BlockDataError.code` distinguishes `invalid-options`, `networ
|
|
|
24
24
|
The generic is a TypeScript return type, not runtime validation. Each consumer
|
|
25
25
|
validates its own JSON fields and handles failures.
|
|
26
26
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
27
|
+
The browser and Node.js 20+ use their native Fetch API. Each host may configure an
|
|
28
|
+
isolated client for all of its blocks:
|
|
29
|
+
|
|
30
|
+
```ts
|
|
31
|
+
import { createBlockClient, BLOCK_DEV_PROXY_PATH } from "@orbit-work/block-sdk"
|
|
32
|
+
|
|
33
|
+
export const blocks = createBlockClient({
|
|
34
|
+
baseUrl: import.meta.env.DEV ? BLOCK_DEV_PROXY_PATH : undefined,
|
|
35
|
+
})
|
|
36
|
+
const features = await blocks.getBlockData("orbit-features-preview", { env: "prod" })
|
|
37
|
+
const other = await blocks.getBlockData("another-block", { env: "uat" })
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
`baseUrl` accepts a root-relative path or an absolute HTTPS base, without
|
|
41
|
+
credentials, queries, fragments or traversal segments. Its default is the public
|
|
42
|
+
CDN base. Client configuration does not mutate `getBlockData` or other clients;
|
|
43
|
+
environment selection remains explicit on every read. The core has no Vite,
|
|
44
|
+
Node-only, UI or authentication imports.
|
|
45
|
+
|
|
46
|
+
## Local Vite development
|
|
47
|
+
|
|
48
|
+
Add the optional Vite 8 plugin to the application's Vite config:
|
|
49
|
+
|
|
50
|
+
```ts
|
|
51
|
+
import { blockDataDevProxy } from "@orbit-work/block-sdk/vite"
|
|
52
|
+
|
|
53
|
+
export default defineConfig({ plugins: [blockDataDevProxy()] })
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
The development server serves `GET /__orbit_blocks__/<key>/<uat|pre|prod>/data.json`
|
|
57
|
+
and fetches the real public CDN on the server. The browser reads its own origin,
|
|
58
|
+
so localhost needs no additional CDN CORS registration. All legal keys use this
|
|
59
|
+
same transport. The plugin validates the entire path and method, accepts no target
|
|
60
|
+
URL, forwards no incoming headers or credentials, follows no redirects and
|
|
61
|
+
forwards no upstream cookies. Responses use `Cache-Control: no-store`.
|
|
62
|
+
|
|
63
|
+
The upstream timeout defaults to three seconds, includes body streaming and can be
|
|
64
|
+
set with `blockDataDevProxy({ timeoutMs: 5000 })`. A caller disconnect cancels the
|
|
65
|
+
upstream read. Network/timeout failures return bounded JSON with 502/504; upstream
|
|
66
|
+
HTTP failures retain their status without exposing their response body. Consumers
|
|
67
|
+
still own schema validation and recovery policy.
|
|
68
|
+
|
|
69
|
+
The plugin installs only development middleware, with no production build or
|
|
70
|
+
preview endpoint. Production clients with no `baseUrl` continue using the CDN.
|
|
71
|
+
Use localhost HTTP for the existing local login registration; custom hosts still
|
|
72
|
+
need trusted HTTPS and their own identity callback registration for login.
|
|
30
73
|
|
|
31
74
|
Build and test with `pnpm --filter @orbit-work/block-sdk test`.
|
|
32
75
|
`pnpm --filter @orbit-work/block-sdk pack` builds an installable tarball; it does
|
|
33
76
|
not publish to the registry.
|
|
77
|
+
|
|
78
|
+
Both the core and `/vite` entries are included in the tarball. Tests exercise an
|
|
79
|
+
isolated npm consumer as well as real Vite middleware for multiple keys and
|
|
80
|
+
environments, anonymity, invalid routes, failure recovery, body timeout,
|
|
81
|
+
disconnection and production preview isolation.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// src/paths.ts
|
|
2
|
+
var BLOCK_CDN_BASE_URL = "https://s.fzcdn1.com/cdn/h5-data";
|
|
3
|
+
var BLOCK_DEV_PROXY_PATH = "/__orbit_blocks__";
|
|
4
|
+
var BLOCK_KEY = /^[A-Za-z0-9][A-Za-z0-9_-]*$/;
|
|
5
|
+
|
|
6
|
+
export {
|
|
7
|
+
BLOCK_CDN_BASE_URL,
|
|
8
|
+
BLOCK_DEV_PROXY_PATH,
|
|
9
|
+
BLOCK_KEY
|
|
10
|
+
};
|
|
11
|
+
//# sourceMappingURL=chunk-MCUJICYI.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/paths.ts"],"sourcesContent":["export const BLOCK_CDN_BASE_URL = \"https://s.fzcdn1.com/cdn/h5-data\"\nexport const BLOCK_DEV_PROXY_PATH = \"/__orbit_blocks__\"\n\nexport const BLOCK_KEY = /^[A-Za-z0-9][A-Za-z0-9_-]*$/\n"],"mappings":";AAAO,IAAM,qBAAqB;AAC3B,IAAM,uBAAuB;AAE7B,IAAM,YAAY;","names":[]}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,9 +1,17 @@
|
|
|
1
|
+
declare const BLOCK_DEV_PROXY_PATH = "/__orbit_blocks__";
|
|
2
|
+
|
|
1
3
|
type BlockEnvironment = "uat" | "pre" | "prod";
|
|
2
4
|
type GetBlockDataOptions = {
|
|
3
5
|
env: BlockEnvironment;
|
|
4
6
|
signal?: AbortSignal;
|
|
5
7
|
timeoutMs?: number;
|
|
6
8
|
};
|
|
9
|
+
type BlockClientOptions = {
|
|
10
|
+
baseUrl?: string;
|
|
11
|
+
};
|
|
12
|
+
type BlockClient = {
|
|
13
|
+
getBlockData: typeof getBlockData;
|
|
14
|
+
};
|
|
7
15
|
type BlockErrorCode = "invalid-options" | "network" | "http" | "timeout" | "cancelled" | "json";
|
|
8
16
|
declare class BlockDataError extends Error {
|
|
9
17
|
readonly code: BlockErrorCode;
|
|
@@ -12,5 +20,7 @@ declare class BlockDataError extends Error {
|
|
|
12
20
|
}
|
|
13
21
|
/** Dora's config key identifies a block, independently of any application ID. */
|
|
14
22
|
declare function getBlockData<T = unknown>(configKey: string, options: GetBlockDataOptions): Promise<T>;
|
|
23
|
+
/** Configure one isolated client for all of a host's blocks, without global or framework state. */
|
|
24
|
+
declare function createBlockClient(options?: BlockClientOptions): BlockClient;
|
|
15
25
|
|
|
16
|
-
export { BlockDataError, type BlockEnvironment, type BlockErrorCode, type GetBlockDataOptions, getBlockData };
|
|
26
|
+
export { BLOCK_DEV_PROXY_PATH, type BlockClient, type BlockClientOptions, BlockDataError, type BlockEnvironment, type BlockErrorCode, type GetBlockDataOptions, createBlockClient, getBlockData };
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
import {
|
|
2
|
+
BLOCK_CDN_BASE_URL,
|
|
3
|
+
BLOCK_DEV_PROXY_PATH,
|
|
4
|
+
BLOCK_KEY
|
|
5
|
+
} from "./chunk-MCUJICYI.js";
|
|
6
|
+
|
|
1
7
|
// src/index.ts
|
|
2
8
|
var BlockDataError = class extends Error {
|
|
3
9
|
code;
|
|
@@ -10,8 +16,31 @@ var BlockDataError = class extends Error {
|
|
|
10
16
|
}
|
|
11
17
|
};
|
|
12
18
|
async function getBlockData(configKey, options) {
|
|
19
|
+
return readBlock(BLOCK_CDN_BASE_URL, configKey, options);
|
|
20
|
+
}
|
|
21
|
+
function createBlockClient(options = {}) {
|
|
22
|
+
const baseUrl = normalizeBaseUrl(options.baseUrl);
|
|
23
|
+
return {
|
|
24
|
+
getBlockData: (key, options2) => readBlock(baseUrl, key, options2)
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
function normalizeBaseUrl(value) {
|
|
28
|
+
if (value === void 0) return BLOCK_CDN_BASE_URL;
|
|
29
|
+
if (typeof value !== "string" || !value || /[\\\s?#%]/.test(value)) throw new BlockDataError("invalid-options");
|
|
30
|
+
const path = value.startsWith("/") ? value : value.replace(/^https:\/\/[^/]+/, "");
|
|
31
|
+
if (path.split("/").some((part) => part === "." || part === "..")) throw new BlockDataError("invalid-options");
|
|
32
|
+
if (/^\/(?!\/)[A-Za-z0-9_/-]*$/.test(value)) return value.replace(/\/+$/, "");
|
|
33
|
+
try {
|
|
34
|
+
const url = new URL(value);
|
|
35
|
+
if (url.protocol !== "https:" || url.username || url.password) throw new Error();
|
|
36
|
+
return url.href.replace(/\/+$/, "");
|
|
37
|
+
} catch {
|
|
38
|
+
throw new BlockDataError("invalid-options");
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
async function readBlock(baseUrl, configKey, options) {
|
|
13
42
|
const timeoutMs = options.timeoutMs ?? 3e3;
|
|
14
|
-
if (typeof configKey !== "string" ||
|
|
43
|
+
if (typeof configKey !== "string" || !BLOCK_KEY.test(configKey) || !["uat", "pre", "prod"].includes(options.env) || !Number.isFinite(timeoutMs) || timeoutMs <= 0)
|
|
15
44
|
throw new BlockDataError("invalid-options");
|
|
16
45
|
const controller = new AbortController();
|
|
17
46
|
let timedOut = false;
|
|
@@ -25,7 +54,7 @@ async function getBlockData(configKey, options) {
|
|
|
25
54
|
try {
|
|
26
55
|
let response;
|
|
27
56
|
try {
|
|
28
|
-
response = await fetch(
|
|
57
|
+
response = await fetch(`${baseUrl}/${configKey}/${options.env}/data.json`, {
|
|
29
58
|
method: "GET",
|
|
30
59
|
credentials: "omit",
|
|
31
60
|
cache: "no-store",
|
|
@@ -54,7 +83,9 @@ async function getBlockData(configKey, options) {
|
|
|
54
83
|
}
|
|
55
84
|
}
|
|
56
85
|
export {
|
|
86
|
+
BLOCK_DEV_PROXY_PATH,
|
|
57
87
|
BlockDataError,
|
|
88
|
+
createBlockClient,
|
|
58
89
|
getBlockData
|
|
59
90
|
};
|
|
60
91
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["import { BLOCK_CDN_BASE_URL, BLOCK_KEY } from \"./paths\"\n\nexport { BLOCK_DEV_PROXY_PATH } from \"./paths\"\n\nexport type BlockEnvironment = \"uat\" | \"pre\" | \"prod\"\n\nexport type GetBlockDataOptions = { env: BlockEnvironment; signal?: AbortSignal; timeoutMs?: number }\n\nexport type BlockClientOptions = { baseUrl?: string }\nexport type BlockClient = { getBlockData: typeof getBlockData }\n\nexport type BlockErrorCode = \"invalid-options\" | \"network\" | \"http\" | \"timeout\" | \"cancelled\" | \"json\"\n\nexport class BlockDataError extends Error {\n readonly code: BlockErrorCode\n readonly status?: number\n constructor(code: BlockErrorCode, status?: number) {\n super(`Block data request failed: ${code}`)\n this.name = \"BlockDataError\"\n this.code = code\n this.status = status\n }\n}\n\n/** Dora's config key identifies a block, independently of any application ID. */\nexport async function getBlockData<T = unknown>(configKey: string, options: GetBlockDataOptions): Promise<T> {\n return readBlock<T>(BLOCK_CDN_BASE_URL, configKey, options)\n}\n\n/** Configure one isolated client for all of a host's blocks, without global or framework state. */\nexport function createBlockClient(options: BlockClientOptions = {}): BlockClient {\n const baseUrl = normalizeBaseUrl(options.baseUrl)\n return {\n getBlockData: <T = unknown>(key: string, options: GetBlockDataOptions) => readBlock<T>(baseUrl, key, options),\n }\n}\n\nfunction normalizeBaseUrl(value: string | undefined): string {\n if (value === undefined) return BLOCK_CDN_BASE_URL\n if (typeof value !== \"string\" || !value || /[\\\\\\s?#%]/.test(value)) throw new BlockDataError(\"invalid-options\")\n const path = value.startsWith(\"/\") ? value : value.replace(/^https:\\/\\/[^/]+/, \"\")\n if (path.split(\"/\").some((part) => part === \".\" || part === \"..\")) throw new BlockDataError(\"invalid-options\")\n if (/^\\/(?!\\/)[A-Za-z0-9_/-]*$/.test(value)) return value.replace(/\\/+$/, \"\")\n try {\n const url = new URL(value)\n if (url.protocol !== \"https:\" || url.username || url.password) throw new Error()\n return url.href.replace(/\\/+$/, \"\")\n } catch {\n throw new BlockDataError(\"invalid-options\")\n }\n}\n\nasync function readBlock<T>(baseUrl: string, configKey: string, options: GetBlockDataOptions): Promise<T> {\n const timeoutMs = options.timeoutMs ?? 3000\n if (\n typeof configKey !== \"string\" ||\n !BLOCK_KEY.test(configKey) ||\n ![\"uat\", \"pre\", \"prod\"].includes(options.env) ||\n !Number.isFinite(timeoutMs) ||\n timeoutMs <= 0\n )\n throw new BlockDataError(\"invalid-options\")\n\n const controller = new AbortController()\n let timedOut = false\n const cancel = () => controller.abort()\n if (options.signal?.aborted) throw new BlockDataError(\"cancelled\")\n options.signal?.addEventListener(\"abort\", cancel, { once: true })\n const timer = setTimeout(() => {\n timedOut = true\n controller.abort()\n }, timeoutMs)\n try {\n let response: Response\n try {\n response = await fetch(`${baseUrl}/${configKey}/${options.env}/data.json`, {\n method: \"GET\",\n credentials: \"omit\",\n cache: \"no-store\",\n redirect: \"error\",\n referrerPolicy: \"no-referrer\",\n signal: controller.signal,\n })\n } catch {\n throw new BlockDataError(\"network\")\n }\n if (!response.ok) throw new BlockDataError(\"http\", response.status)\n try {\n const data = (await response.json()) as T\n if (controller.signal.aborted) throw new BlockDataError(\"cancelled\")\n return data\n } catch {\n throw new BlockDataError(\"json\")\n }\n } catch (error) {\n if (options.signal?.aborted) throw new BlockDataError(\"cancelled\")\n if (timedOut) throw new BlockDataError(\"timeout\")\n throw error instanceof BlockDataError ? error : new BlockDataError(\"network\")\n } finally {\n clearTimeout(timer)\n options.signal?.removeEventListener(\"abort\", cancel)\n }\n}\n"],"mappings":"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","names":["options"]}
|
package/dist/vite.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { Plugin } from 'vite';
|
|
2
|
+
|
|
3
|
+
type BlockDataDevProxyOptions = {
|
|
4
|
+
timeoutMs?: number;
|
|
5
|
+
};
|
|
6
|
+
/** Anonymous development transport only. Production build and preview install no middleware. */
|
|
7
|
+
declare function blockDataDevProxy(options?: BlockDataDevProxyOptions): Plugin;
|
|
8
|
+
|
|
9
|
+
export { type BlockDataDevProxyOptions, blockDataDevProxy };
|
package/dist/vite.js
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import {
|
|
2
|
+
BLOCK_CDN_BASE_URL,
|
|
3
|
+
BLOCK_DEV_PROXY_PATH
|
|
4
|
+
} from "./chunk-MCUJICYI.js";
|
|
5
|
+
|
|
6
|
+
// src/vite.ts
|
|
7
|
+
import { once } from "events";
|
|
8
|
+
function blockDataDevProxy(options = {}) {
|
|
9
|
+
const timeoutMs = options.timeoutMs ?? 3e3;
|
|
10
|
+
if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) throw new Error("Invalid block proxy timeout");
|
|
11
|
+
return {
|
|
12
|
+
name: "orbit-block-data-dev-proxy",
|
|
13
|
+
apply: "serve",
|
|
14
|
+
configureServer(server) {
|
|
15
|
+
server.middlewares.use((request, response, next) => {
|
|
16
|
+
const path = request.url ?? "";
|
|
17
|
+
const pathname = path.split("?")[0];
|
|
18
|
+
if (pathname !== BLOCK_DEV_PROXY_PATH && !pathname.startsWith(`${BLOCK_DEV_PROXY_PATH}/`)) return next();
|
|
19
|
+
response.setHeader("Cache-Control", "no-store");
|
|
20
|
+
const reply = (status, error) => {
|
|
21
|
+
response.statusCode = status;
|
|
22
|
+
response.setHeader("Content-Type", "application/json; charset=utf-8");
|
|
23
|
+
response.end(JSON.stringify({ error }));
|
|
24
|
+
};
|
|
25
|
+
if (request.method !== "GET") {
|
|
26
|
+
response.setHeader("Allow", "GET");
|
|
27
|
+
reply(405, "method-not-allowed");
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
const match = /^\/__orbit_blocks__\/([A-Za-z0-9][A-Za-z0-9_-]*)\/(uat|pre|prod)\/data\.json$/.exec(path);
|
|
31
|
+
if (!match) {
|
|
32
|
+
reply(400, "invalid-block-path");
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
const controller = new AbortController();
|
|
36
|
+
let timedOut = false;
|
|
37
|
+
const cancel = () => controller.abort();
|
|
38
|
+
request.once("aborted", cancel);
|
|
39
|
+
response.once("close", cancel);
|
|
40
|
+
const timer = setTimeout(() => {
|
|
41
|
+
timedOut = true;
|
|
42
|
+
cancel();
|
|
43
|
+
}, timeoutMs);
|
|
44
|
+
timer.unref();
|
|
45
|
+
void (async () => {
|
|
46
|
+
try {
|
|
47
|
+
const upstream = await fetch(`${BLOCK_CDN_BASE_URL}/${match[1]}/${match[2]}/data.json`, {
|
|
48
|
+
method: "GET",
|
|
49
|
+
credentials: "omit",
|
|
50
|
+
cache: "no-store",
|
|
51
|
+
redirect: "error",
|
|
52
|
+
referrerPolicy: "no-referrer",
|
|
53
|
+
signal: controller.signal
|
|
54
|
+
});
|
|
55
|
+
if (controller.signal.aborted) throw new Error("cancelled");
|
|
56
|
+
if (!upstream.ok) {
|
|
57
|
+
await upstream.body?.cancel();
|
|
58
|
+
reply(upstream.status, "block-http");
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
response.statusCode = upstream.status;
|
|
62
|
+
response.setHeader("Content-Type", "application/json; charset=utf-8");
|
|
63
|
+
await forwardBody(upstream, response, controller.signal);
|
|
64
|
+
response.end();
|
|
65
|
+
} catch {
|
|
66
|
+
if (response.destroyed) return;
|
|
67
|
+
if (response.headersSent) response.destroy();
|
|
68
|
+
else reply(timedOut ? 504 : 502, timedOut ? "block-timeout" : "block-network");
|
|
69
|
+
} finally {
|
|
70
|
+
clearTimeout(timer);
|
|
71
|
+
request.off("aborted", cancel);
|
|
72
|
+
response.off("close", cancel);
|
|
73
|
+
}
|
|
74
|
+
})();
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
async function forwardBody(upstream, response, signal) {
|
|
80
|
+
const reader = upstream.body?.getReader();
|
|
81
|
+
if (!reader) return;
|
|
82
|
+
try {
|
|
83
|
+
while (true) {
|
|
84
|
+
const { done, value } = await reader.read();
|
|
85
|
+
if (signal.aborted) throw new Error("cancelled");
|
|
86
|
+
if (done) return;
|
|
87
|
+
if (!response.write(value)) await once(response, "drain", { signal });
|
|
88
|
+
}
|
|
89
|
+
} finally {
|
|
90
|
+
await reader.cancel().catch(() => void 0);
|
|
91
|
+
reader.releaseLock();
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
export {
|
|
95
|
+
blockDataDevProxy
|
|
96
|
+
};
|
|
97
|
+
//# sourceMappingURL=vite.js.map
|
package/dist/vite.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/vite.ts"],"sourcesContent":["import { once } from \"node:events\"\nimport type { ServerResponse } from \"node:http\"\nimport type { Plugin } from \"vite\"\nimport { BLOCK_CDN_BASE_URL, BLOCK_DEV_PROXY_PATH } from \"./paths\"\n\nexport type BlockDataDevProxyOptions = { timeoutMs?: number }\n\n/** Anonymous development transport only. Production build and preview install no middleware. */\nexport function blockDataDevProxy(options: BlockDataDevProxyOptions = {}): Plugin {\n const timeoutMs = options.timeoutMs ?? 3000\n if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) throw new Error(\"Invalid block proxy timeout\")\n return {\n name: \"orbit-block-data-dev-proxy\",\n apply: \"serve\",\n configureServer(server) {\n server.middlewares.use((request, response, next) => {\n const path = request.url ?? \"\"\n const pathname = path.split(\"?\")[0]\n if (pathname !== BLOCK_DEV_PROXY_PATH && !pathname.startsWith(`${BLOCK_DEV_PROXY_PATH}/`)) return next()\n response.setHeader(\"Cache-Control\", \"no-store\")\n const reply = (status: number, error: string) => {\n response.statusCode = status\n response.setHeader(\"Content-Type\", \"application/json; charset=utf-8\")\n response.end(JSON.stringify({ error }))\n }\n if (request.method !== \"GET\") {\n response.setHeader(\"Allow\", \"GET\")\n reply(405, \"method-not-allowed\")\n return\n }\n const match = /^\\/__orbit_blocks__\\/([A-Za-z0-9][A-Za-z0-9_-]*)\\/(uat|pre|prod)\\/data\\.json$/.exec(path)\n if (!match) {\n reply(400, \"invalid-block-path\")\n return\n }\n const controller = new AbortController()\n let timedOut = false\n const cancel = () => controller.abort()\n request.once(\"aborted\", cancel)\n response.once(\"close\", cancel)\n const timer = setTimeout(() => {\n timedOut = true\n cancel()\n }, timeoutMs)\n timer.unref()\n void (async () => {\n try {\n // Construct the destination here; never forward request headers, a target URL, cookies or redirects.\n const upstream = await fetch(`${BLOCK_CDN_BASE_URL}/${match[1]}/${match[2]}/data.json`, {\n method: \"GET\",\n credentials: \"omit\",\n cache: \"no-store\",\n redirect: \"error\",\n referrerPolicy: \"no-referrer\",\n signal: controller.signal,\n })\n if (controller.signal.aborted) throw new Error(\"cancelled\")\n if (!upstream.ok) {\n await upstream.body?.cancel()\n reply(upstream.status, \"block-http\")\n return\n }\n response.statusCode = upstream.status\n response.setHeader(\"Content-Type\", \"application/json; charset=utf-8\")\n await forwardBody(upstream, response, controller.signal)\n response.end()\n } catch {\n if (response.destroyed) return\n if (response.headersSent) response.destroy()\n else reply(timedOut ? 504 : 502, timedOut ? \"block-timeout\" : \"block-network\")\n } finally {\n clearTimeout(timer)\n request.off(\"aborted\", cancel)\n response.off(\"close\", cancel)\n }\n })()\n })\n },\n }\n}\n\nasync function forwardBody(upstream: Response, response: ServerResponse, signal: AbortSignal) {\n const reader = upstream.body?.getReader()\n if (!reader) return\n try {\n while (true) {\n const { done, value } = await reader.read()\n if (signal.aborted) throw new Error(\"cancelled\")\n if (done) return\n if (!response.write(value)) await once(response, \"drain\", { signal })\n }\n } finally {\n await reader.cancel().catch(() => undefined)\n reader.releaseLock()\n }\n}\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@orbit-work/block-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Anonymous JSON block client for CDN-published Dora data.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"publishConfig": {
|
|
@@ -13,6 +13,10 @@
|
|
|
13
13
|
".": {
|
|
14
14
|
"types": "./dist/index.d.ts",
|
|
15
15
|
"import": "./dist/index.js"
|
|
16
|
+
},
|
|
17
|
+
"./vite": {
|
|
18
|
+
"types": "./dist/vite.d.ts",
|
|
19
|
+
"import": "./dist/vite.js"
|
|
16
20
|
}
|
|
17
21
|
},
|
|
18
22
|
"files": ["dist"],
|
|
@@ -24,8 +28,16 @@
|
|
|
24
28
|
"test": "pnpm run build && node --test \"src/**/*.test.mjs\""
|
|
25
29
|
},
|
|
26
30
|
"devDependencies": {
|
|
31
|
+
"@types/node": "^22.10.2",
|
|
27
32
|
"tsup": "^8.5.1",
|
|
28
|
-
"typescript": "^5.6.3"
|
|
33
|
+
"typescript": "^5.6.3",
|
|
34
|
+
"vite": "^8.3.0"
|
|
35
|
+
},
|
|
36
|
+
"peerDependencies": {
|
|
37
|
+
"vite": "^8.3.0"
|
|
38
|
+
},
|
|
39
|
+
"peerDependenciesMeta": {
|
|
40
|
+
"vite": { "optional": true }
|
|
29
41
|
},
|
|
30
42
|
"engines": {
|
|
31
43
|
"node": ">=20.0.0"
|