@archipelago-js/client 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.
- package/README.md +15 -0
- package/dist/csrf.d.ts +3 -0
- package/dist/csrf.js +20 -0
- package/dist/csrf.js.map +1 -0
- package/dist/fetch.d.ts +13 -0
- package/dist/fetch.js +60 -0
- package/dist/fetch.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -0
- package/dist/types.d.ts +19 -0
- package/dist/types.js +48 -0
- package/dist/types.js.map +1 -0
- package/package.json +38 -0
package/README.md
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# @archipelago-js/client
|
|
2
|
+
|
|
3
|
+
Core client utilities for Archipelago islands.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
yarn add @archipelago-js/client
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## API
|
|
12
|
+
|
|
13
|
+
- `islandFetch(component, operation, payload, options)`
|
|
14
|
+
- `buildIslandPayload(payload, fixedParams, overridePayload)`
|
|
15
|
+
- `parseIslandResponse(value)`
|
package/dist/csrf.d.ts
ADDED
package/dist/csrf.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
let cachedToken = null;
|
|
2
|
+
export function getCsrfToken(doc = document) {
|
|
3
|
+
if (cachedToken !== null) {
|
|
4
|
+
return cachedToken;
|
|
5
|
+
}
|
|
6
|
+
const token = doc
|
|
7
|
+
.querySelector("meta[name='csrf-token']")
|
|
8
|
+
?.getAttribute("content")
|
|
9
|
+
?.trim();
|
|
10
|
+
cachedToken = token && token.length > 0 ? token : null;
|
|
11
|
+
return cachedToken;
|
|
12
|
+
}
|
|
13
|
+
export function refreshCsrfToken(doc = document) {
|
|
14
|
+
cachedToken = null;
|
|
15
|
+
return getCsrfToken(doc);
|
|
16
|
+
}
|
|
17
|
+
export function clearCsrfCache() {
|
|
18
|
+
cachedToken = null;
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=csrf.js.map
|
package/dist/csrf.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"csrf.js","sourceRoot":"","sources":["../src/csrf.ts"],"names":[],"mappings":"AAAA,IAAI,WAAW,GAAkB,IAAI,CAAA;AAErC,MAAM,UAAU,YAAY,CAAC,MAAgB,QAAQ;IACnD,IAAI,WAAW,KAAK,IAAI,EAAE,CAAC;QACzB,OAAO,WAAW,CAAA;IACpB,CAAC;IAED,MAAM,KAAK,GAAG,GAAG;SACd,aAAa,CAAC,yBAAyB,CAAC;QACzC,EAAE,YAAY,CAAC,SAAS,CAAC;QACzB,EAAE,IAAI,EAAE,CAAA;IAEV,WAAW,GAAG,KAAK,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAA;IACtD,OAAO,WAAW,CAAA;AACpB,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,MAAgB,QAAQ;IACvD,WAAW,GAAG,IAAI,CAAA;IAClB,OAAO,YAAY,CAAC,GAAG,CAAC,CAAA;AAC1B,CAAC;AAED,MAAM,UAAU,cAAc;IAC5B,WAAW,GAAG,IAAI,CAAA;AACpB,CAAC"}
|
package/dist/fetch.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { IslandResponse } from "./types";
|
|
2
|
+
export type IslandFetchOptions = {
|
|
3
|
+
endpoint?: string;
|
|
4
|
+
fixedParams?: Record<string, unknown>;
|
|
5
|
+
overridePayload?: Record<string, unknown>;
|
|
6
|
+
headers?: Record<string, string>;
|
|
7
|
+
signal?: AbortSignal;
|
|
8
|
+
fetchImpl?: typeof fetch;
|
|
9
|
+
navigate?: (location: string) => void;
|
|
10
|
+
};
|
|
11
|
+
export type IslandFetchPayload = Record<string, unknown>;
|
|
12
|
+
export declare function buildIslandPayload(payload?: IslandFetchPayload, fixedParams?: Record<string, unknown>, overridePayload?: Record<string, unknown>): Record<string, unknown>;
|
|
13
|
+
export declare function islandFetch(component: string, operation: string, payload?: IslandFetchPayload, options?: IslandFetchOptions): Promise<IslandResponse>;
|
package/dist/fetch.js
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { clearCsrfCache, getCsrfToken } from "./csrf";
|
|
2
|
+
import { parseIslandResponse } from "./types";
|
|
3
|
+
function defaultNavigate(location) {
|
|
4
|
+
const turbo = window.Turbo;
|
|
5
|
+
if (turbo?.visit) {
|
|
6
|
+
turbo.visit(location);
|
|
7
|
+
return;
|
|
8
|
+
}
|
|
9
|
+
window.location.assign(location);
|
|
10
|
+
}
|
|
11
|
+
function hasContent(response) {
|
|
12
|
+
const contentLength = response.headers.get("content-length");
|
|
13
|
+
return contentLength == null || contentLength !== "0";
|
|
14
|
+
}
|
|
15
|
+
export function buildIslandPayload(payload = {}, fixedParams = {}, overridePayload = {}) {
|
|
16
|
+
return {
|
|
17
|
+
...fixedParams,
|
|
18
|
+
...payload,
|
|
19
|
+
...overridePayload
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
export async function islandFetch(component, operation, payload = {}, options = {}) {
|
|
23
|
+
const fetchImpl = options.fetchImpl ?? fetch;
|
|
24
|
+
const endpoint = options.endpoint ?? "/islands";
|
|
25
|
+
const mergedPayload = buildIslandPayload(payload, options.fixedParams, options.overridePayload);
|
|
26
|
+
const csrfToken = getCsrfToken();
|
|
27
|
+
const response = await fetchImpl(`${endpoint}/${encodeURIComponent(component)}/${encodeURIComponent(operation)}`, {
|
|
28
|
+
method: "POST",
|
|
29
|
+
signal: options.signal,
|
|
30
|
+
credentials: "same-origin",
|
|
31
|
+
headers: {
|
|
32
|
+
"content-type": "application/json",
|
|
33
|
+
"x-requested-with": "XMLHttpRequest",
|
|
34
|
+
...(csrfToken ? { "x-csrf-token": csrfToken } : {}),
|
|
35
|
+
...(options.headers ?? {})
|
|
36
|
+
},
|
|
37
|
+
body: JSON.stringify(mergedPayload)
|
|
38
|
+
});
|
|
39
|
+
if (response.status === 422) {
|
|
40
|
+
// Rails may rotate CSRF token; force re-read on next request.
|
|
41
|
+
clearCsrfCache();
|
|
42
|
+
}
|
|
43
|
+
if (response.status === 403 && !hasContent(response)) {
|
|
44
|
+
return { status: "forbidden" };
|
|
45
|
+
}
|
|
46
|
+
if (!hasContent(response)) {
|
|
47
|
+
return { status: "ok", props: {}, version: Date.now() };
|
|
48
|
+
}
|
|
49
|
+
const text = await response.text();
|
|
50
|
+
if (text.trim().length === 0) {
|
|
51
|
+
return { status: "ok", props: {}, version: Date.now() };
|
|
52
|
+
}
|
|
53
|
+
const parsed = parseIslandResponse(JSON.parse(text));
|
|
54
|
+
if (parsed.status === "redirect") {
|
|
55
|
+
const navigate = options.navigate ?? defaultNavigate;
|
|
56
|
+
navigate(parsed.location);
|
|
57
|
+
}
|
|
58
|
+
return parsed;
|
|
59
|
+
}
|
|
60
|
+
//# sourceMappingURL=fetch.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fetch.js","sourceRoot":"","sources":["../src/fetch.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,QAAQ,CAAA;AACtB,cAAc,SAAS,CAAA;AACvB,cAAc,SAAS,CAAA"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export type IslandProps = Record<string, unknown>;
|
|
2
|
+
export type IslandOkResponse = {
|
|
3
|
+
status: "ok";
|
|
4
|
+
props: IslandProps;
|
|
5
|
+
version?: number;
|
|
6
|
+
};
|
|
7
|
+
export type IslandRedirectResponse = {
|
|
8
|
+
status: "redirect";
|
|
9
|
+
location: string;
|
|
10
|
+
};
|
|
11
|
+
export type IslandErrorResponse = {
|
|
12
|
+
status: "error";
|
|
13
|
+
errors: Record<string, string[]>;
|
|
14
|
+
};
|
|
15
|
+
export type IslandForbiddenResponse = {
|
|
16
|
+
status: "forbidden";
|
|
17
|
+
};
|
|
18
|
+
export type IslandResponse = IslandOkResponse | IslandRedirectResponse | IslandErrorResponse | IslandForbiddenResponse;
|
|
19
|
+
export declare function parseIslandResponse(value: unknown): IslandResponse;
|
package/dist/types.js
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
function isRecord(value) {
|
|
2
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3
|
+
}
|
|
4
|
+
function isErrorMap(value) {
|
|
5
|
+
if (!isRecord(value)) {
|
|
6
|
+
return false;
|
|
7
|
+
}
|
|
8
|
+
return Object.values(value).every((messages) => {
|
|
9
|
+
return Array.isArray(messages) && messages.every((message) => typeof message === "string");
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
export function parseIslandResponse(value) {
|
|
13
|
+
if (!isRecord(value) || typeof value.status !== "string") {
|
|
14
|
+
throw new Error("Invalid island response payload");
|
|
15
|
+
}
|
|
16
|
+
switch (value.status) {
|
|
17
|
+
case "ok": {
|
|
18
|
+
if (!isRecord(value.props)) {
|
|
19
|
+
throw new Error("Invalid ok payload");
|
|
20
|
+
}
|
|
21
|
+
if (value.version != null && typeof value.version !== "number") {
|
|
22
|
+
throw new Error("Invalid ok version");
|
|
23
|
+
}
|
|
24
|
+
return {
|
|
25
|
+
status: "ok",
|
|
26
|
+
props: value.props,
|
|
27
|
+
version: typeof value.version === "number" ? value.version : undefined
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
case "redirect": {
|
|
31
|
+
if (typeof value.location !== "string") {
|
|
32
|
+
throw new Error("Invalid redirect payload");
|
|
33
|
+
}
|
|
34
|
+
return { status: "redirect", location: value.location };
|
|
35
|
+
}
|
|
36
|
+
case "error": {
|
|
37
|
+
if (!isErrorMap(value.errors)) {
|
|
38
|
+
throw new Error("Invalid error payload");
|
|
39
|
+
}
|
|
40
|
+
return { status: "error", errors: value.errors };
|
|
41
|
+
}
|
|
42
|
+
case "forbidden":
|
|
43
|
+
return { status: "forbidden" };
|
|
44
|
+
default:
|
|
45
|
+
throw new Error("Unknown island response status");
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AA4BA,SAAS,QAAQ,CAAC,KAAc;IAC9B,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAA;AAC7E,CAAC;AAED,SAAS,UAAU,CAAC,KAAc;IAChC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;QACrB,OAAO,KAAK,CAAA;IACd,CAAC;IAED,OAAO,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,QAAQ,EAAE,EAAE;QAC7C,OAAO,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,IAAI,QAAQ,CAAC,KAAK,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,OAAO,KAAK,QAAQ,CAAC,CAAA;IAC5F,CAAC,CAAC,CAAA;AACJ,CAAC;AAED,MAAM,UAAU,mBAAmB,CAAC,KAAc;IAChD,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,OAAO,KAAK,CAAC,MAAM,KAAK,QAAQ,EAAE,CAAC;QACzD,MAAM,IAAI,KAAK,CAAC,iCAAiC,CAAC,CAAA;IACpD,CAAC;IAED,QAAQ,KAAK,CAAC,MAAM,EAAE,CAAC;QACrB,KAAK,IAAI,CAAC,CAAC,CAAC;YACV,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,CAAC;gBAC3B,MAAM,IAAI,KAAK,CAAC,oBAAoB,CAAC,CAAA;YACvC,CAAC;YAED,IAAI,KAAK,CAAC,OAAO,IAAI,IAAI,IAAI,OAAO,KAAK,CAAC,OAAO,KAAK,QAAQ,EAAE,CAAC;gBAC/D,MAAM,IAAI,KAAK,CAAC,oBAAoB,CAAC,CAAA;YACvC,CAAC;YAED,OAAO;gBACL,MAAM,EAAE,IAAI;gBACZ,KAAK,EAAE,KAAK,CAAC,KAAK;gBAClB,OAAO,EAAE,OAAO,KAAK,CAAC,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS;aACvE,CAAA;QACH,CAAC;QACD,KAAK,UAAU,CAAC,CAAC,CAAC;YAChB,IAAI,OAAO,KAAK,CAAC,QAAQ,KAAK,QAAQ,EAAE,CAAC;gBACvC,MAAM,IAAI,KAAK,CAAC,0BAA0B,CAAC,CAAA;YAC7C,CAAC;YAED,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,QAAQ,EAAE,KAAK,CAAC,QAAQ,EAAE,CAAA;QACzD,CAAC;QACD,KAAK,OAAO,CAAC,CAAC,CAAC;YACb,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC;gBAC9B,MAAM,IAAI,KAAK,CAAC,uBAAuB,CAAC,CAAA;YAC1C,CAAC;YAED,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,CAAA;QAClD,CAAC;QACD,KAAK,WAAW;YACd,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,CAAA;QAChC;YACE,MAAM,IAAI,KAAK,CAAC,gCAAgC,CAAC,CAAA;IACrD,CAAC;AACH,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@archipelago-js/client",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Core client utilities for Archipelago islands",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "./dist/index.js",
|
|
8
|
+
"module": "./dist/index.js",
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"import": "./dist/index.js"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"dist",
|
|
18
|
+
"README.md"
|
|
19
|
+
],
|
|
20
|
+
"scripts": {
|
|
21
|
+
"clean": "rm -rf dist",
|
|
22
|
+
"build": "yarn clean && tsc -p tsconfig.build.json",
|
|
23
|
+
"test": "vitest run packages/client/test",
|
|
24
|
+
"typecheck": "tsc -p tsconfig.json --noEmit"
|
|
25
|
+
},
|
|
26
|
+
"publishConfig": {
|
|
27
|
+
"access": "public"
|
|
28
|
+
},
|
|
29
|
+
"repository": {
|
|
30
|
+
"type": "git",
|
|
31
|
+
"url": "git+https://github.com/robrace/archipelago.git",
|
|
32
|
+
"directory": "packages/client"
|
|
33
|
+
},
|
|
34
|
+
"bugs": {
|
|
35
|
+
"url": "https://github.com/robrace/archipelago/issues"
|
|
36
|
+
},
|
|
37
|
+
"homepage": "https://github.com/robrace/archipelago/tree/main/packages/client"
|
|
38
|
+
}
|