@codewheel/jsonapi-frontend-client 1.0.1 → 1.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/README.md +15 -1
- package/dist/fetch.d.ts +4 -0
- package/dist/fetch.d.ts.map +1 -1
- package/dist/fetch.js +14 -2
- package/dist/transport.d.ts.map +1 -1
- package/dist/transport.js +13 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -12,7 +12,7 @@ npm i @codewheel/jsonapi-frontend-client
|
|
|
12
12
|
|
|
13
13
|
## Usage
|
|
14
14
|
|
|
15
|
-
Set `DRUPAL_BASE_URL
|
|
15
|
+
Set `DRUPAL_BASE_URL` (must be a full `http(s)://` URL), then:
|
|
16
16
|
|
|
17
17
|
```ts
|
|
18
18
|
import { resolvePath, fetchJsonApi } from "@codewheel/jsonapi-frontend-client"
|
|
@@ -23,3 +23,17 @@ if (resolved.resolved && resolved.kind === "entity") {
|
|
|
23
23
|
console.log(doc.data)
|
|
24
24
|
}
|
|
25
25
|
```
|
|
26
|
+
|
|
27
|
+
## URL safety (recommended)
|
|
28
|
+
|
|
29
|
+
By default, `fetchJsonApi()` and `fetchView()` refuse to fetch absolute URLs on a different origin than your `DRUPAL_BASE_URL` (to avoid accidental SSRF in server environments).
|
|
30
|
+
|
|
31
|
+
If you intentionally need to fetch a cross-origin absolute URL, pass `allowExternalUrls: true`:
|
|
32
|
+
|
|
33
|
+
```ts
|
|
34
|
+
import { fetchJsonApi } from "@codewheel/jsonapi-frontend-client"
|
|
35
|
+
|
|
36
|
+
await fetchJsonApi("https://cms.example.com/jsonapi/node/page/...", {
|
|
37
|
+
allowExternalUrls: true,
|
|
38
|
+
})
|
|
39
|
+
```
|
package/dist/fetch.d.ts
CHANGED
|
@@ -13,6 +13,8 @@ export declare function buildViewCacheTags(dataUrl: string): string[];
|
|
|
13
13
|
export declare function fetchJsonApi<T = JsonApiDocument>(jsonapiPath: string, options?: {
|
|
14
14
|
baseUrl?: string;
|
|
15
15
|
envKey?: string;
|
|
16
|
+
/** Allow fetching absolute URLs on other origins (default: false). */
|
|
17
|
+
allowExternalUrls?: boolean;
|
|
16
18
|
include?: string[];
|
|
17
19
|
fields?: Record<string, string[]>;
|
|
18
20
|
revalidate?: number;
|
|
@@ -25,6 +27,8 @@ export declare function fetchJsonApi<T = JsonApiDocument>(jsonapiPath: string, o
|
|
|
25
27
|
export declare function fetchView<T = JsonApiDocument>(dataUrl: string, options?: {
|
|
26
28
|
baseUrl?: string;
|
|
27
29
|
envKey?: string;
|
|
30
|
+
/** Allow fetching absolute URLs on other origins (default: false). */
|
|
31
|
+
allowExternalUrls?: boolean;
|
|
28
32
|
/**
|
|
29
33
|
* JSON:API pagination parameters.
|
|
30
34
|
*
|
package/dist/fetch.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fetch.d.ts","sourceRoot":"","sources":["../src/fetch.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,SAAS,EAAuD,MAAM,aAAa,CAAA;AACvG,OAAO,EAAE,eAAe,EAAE,MAAM,SAAS,CAAA;AAEzC;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,EAAE,CAmBlE;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,EAAE,CAa5D;
|
|
1
|
+
{"version":3,"file":"fetch.d.ts","sourceRoot":"","sources":["../src/fetch.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,SAAS,EAAuD,MAAM,aAAa,CAAA;AACvG,OAAO,EAAE,eAAe,EAAE,MAAM,SAAS,CAAA;AAEzC;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,EAAE,CAmBlE;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,EAAE,CAa5D;AAoBD,wBAAsB,YAAY,CAAC,CAAC,GAAG,eAAe,EACpD,WAAW,EAAE,MAAM,EACnB,OAAO,CAAC,EAAE;IACR,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,sEAAsE;IACtE,iBAAiB,CAAC,EAAE,OAAO,CAAA;IAC3B,OAAO,CAAC,EAAE,MAAM,EAAE,CAAA;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAA;IACjC,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,uDAAuD;IACvD,IAAI,CAAC,EAAE,MAAM,EAAE,CAAA;IACf,KAAK,CAAC,EAAE,SAAS,CAAA;IACjB,OAAO,CAAC,EAAE,WAAW,CAAA;IACrB,IAAI,CAAC,EAAE,SAAS,CAAA;CACjB,GACA,OAAO,CAAC,CAAC,CAAC,CAsCZ;AAED,wBAAsB,SAAS,CAAC,CAAC,GAAG,eAAe,EACjD,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE;IACR,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,sEAAsE;IACtE,iBAAiB,CAAC,EAAE,OAAO,CAAA;IAC3B;;;;;OAKG;IACH,IAAI,CAAC,EAAE,MAAM,GAAG;QAAE,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;IACnD,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,uDAAuD;IACvD,IAAI,CAAC,EAAE,MAAM,EAAE,CAAA;IACf,KAAK,CAAC,EAAE,SAAS,CAAA;IACjB,OAAO,CAAC,EAAE,WAAW,CAAA;IACrB,IAAI,CAAC,EAAE,SAAS,CAAA;CACjB,GACA,OAAO,CAAC,CAAC,CAAC,CAyCZ"}
|
package/dist/fetch.js
CHANGED
|
@@ -34,10 +34,22 @@ export function buildViewCacheTags(dataUrl) {
|
|
|
34
34
|
tags.push(`view:${viewId}--${displayId}`);
|
|
35
35
|
return tags;
|
|
36
36
|
}
|
|
37
|
+
function buildSafeUrl(input, base, options) {
|
|
38
|
+
const baseUrl = new URL(base);
|
|
39
|
+
const url = new URL(input, baseUrl);
|
|
40
|
+
if (!options?.allowExternalUrls && url.origin !== baseUrl.origin) {
|
|
41
|
+
throw new Error(`Refusing to fetch a URL from a different origin (${url.origin}) than base (${baseUrl.origin}). ` +
|
|
42
|
+
"Pass allowExternalUrls: true to override.");
|
|
43
|
+
}
|
|
44
|
+
if (url.protocol !== "http:" && url.protocol !== "https:") {
|
|
45
|
+
throw new Error(`Unsupported URL protocol "${url.protocol}" (expected http/https)`);
|
|
46
|
+
}
|
|
47
|
+
return url;
|
|
48
|
+
}
|
|
37
49
|
export async function fetchJsonApi(jsonapiPath, options) {
|
|
38
50
|
const base = getDrupalBaseUrlFromOptions({ baseUrl: options?.baseUrl, envKey: options?.envKey });
|
|
39
51
|
const fetcher = getFetch(options?.fetch);
|
|
40
|
-
const url =
|
|
52
|
+
const url = buildSafeUrl(jsonapiPath, base, { allowExternalUrls: options?.allowExternalUrls });
|
|
41
53
|
if (options?.include?.length) {
|
|
42
54
|
url.searchParams.set("include", options.include.join(","));
|
|
43
55
|
}
|
|
@@ -68,7 +80,7 @@ export async function fetchJsonApi(jsonapiPath, options) {
|
|
|
68
80
|
export async function fetchView(dataUrl, options) {
|
|
69
81
|
const base = getDrupalBaseUrlFromOptions({ baseUrl: options?.baseUrl, envKey: options?.envKey });
|
|
70
82
|
const fetcher = getFetch(options?.fetch);
|
|
71
|
-
const url =
|
|
83
|
+
const url = buildSafeUrl(dataUrl, base, { allowExternalUrls: options?.allowExternalUrls });
|
|
72
84
|
if (options?.page !== undefined) {
|
|
73
85
|
if (typeof options.page === "number") {
|
|
74
86
|
url.searchParams.set("page[offset]", String(options.page));
|
package/dist/transport.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"transport.d.ts","sourceRoot":"","sources":["../src/transport.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,gBAAgB,GAAG;IAC7B,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAA;CAChB,CAAA;AAED;;GAEG;AACH,MAAM,MAAM,SAAS,GAAG,WAAW,GAAG;IACpC,IAAI,CAAC,EAAE,gBAAgB,CAAA;CACxB,CAAA;AAED,MAAM,MAAM,SAAS,GAAG,CAAC,KAAK,EAAE,WAAW,GAAG,GAAG,EAAE,IAAI,CAAC,EAAE,SAAS,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAA;AAOzF,wBAAgB,2BAA2B,CAAC,OAAO,CAAC,EAAE;IAAE,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,MAAM,
|
|
1
|
+
{"version":3,"file":"transport.d.ts","sourceRoot":"","sources":["../src/transport.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,gBAAgB,GAAG;IAC7B,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAA;CAChB,CAAA;AAED;;GAEG;AACH,MAAM,MAAM,SAAS,GAAG,WAAW,GAAG;IACpC,IAAI,CAAC,EAAE,gBAAgB,CAAA;CACxB,CAAA;AAED,MAAM,MAAM,SAAS,GAAG,CAAC,KAAK,EAAE,WAAW,GAAG,GAAG,EAAE,IAAI,CAAC,EAAE,SAAS,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAA;AAOzF,wBAAgB,2BAA2B,CAAC,OAAO,CAAC,EAAE;IAAE,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,MAAM,CAkBnG;AAED,wBAAgB,QAAQ,CAAC,SAAS,CAAC,EAAE,SAAS,GAAG,SAAS,CAMzD;AAED,wBAAgB,YAAY,CAAC,GAAG,WAAW,EAAE,KAAK,CAAC,WAAW,GAAG,SAAS,CAAC,GAAG,OAAO,CAOpF"}
|
package/dist/transport.js
CHANGED
|
@@ -3,11 +3,21 @@ function getEnvString(key) {
|
|
|
3
3
|
return typeof value === "string" && value.trim() !== "" ? value : undefined;
|
|
4
4
|
}
|
|
5
5
|
export function getDrupalBaseUrlFromOptions(options) {
|
|
6
|
-
const
|
|
7
|
-
if (!
|
|
6
|
+
const rawBaseUrl = options?.baseUrl ?? getEnvString(options?.envKey ?? "DRUPAL_BASE_URL");
|
|
7
|
+
if (!rawBaseUrl) {
|
|
8
8
|
throw new Error(`Missing Drupal base URL (pass baseUrl or set ${options?.envKey ?? "DRUPAL_BASE_URL"})`);
|
|
9
9
|
}
|
|
10
|
-
|
|
10
|
+
let parsed;
|
|
11
|
+
try {
|
|
12
|
+
parsed = new URL(rawBaseUrl);
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
throw new Error(`Invalid Drupal base URL "${rawBaseUrl}" (expected http(s) URL)`);
|
|
16
|
+
}
|
|
17
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
18
|
+
throw new Error(`Invalid Drupal base URL protocol "${parsed.protocol}" (expected http/https)`);
|
|
19
|
+
}
|
|
20
|
+
return parsed.toString().replace(/\/$/, "");
|
|
11
21
|
}
|
|
12
22
|
export function getFetch(fetchLike) {
|
|
13
23
|
const f = fetchLike ?? globalThis.fetch;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@codewheel/jsonapi-frontend-client",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "TypeScript client helpers for Drupal drupal/jsonapi_frontend",
|
|
5
5
|
"homepage": "https://github.com/code-wheel/jsonapi-frontend-client#readme",
|
|
6
6
|
"repository": {
|