@duckity/js 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/examples/example.html +11 -0
- package/package.json +23 -0
- package/rollup.config.ts +11 -0
- package/src/index.ts +56 -0
- package/src/processing/worker.ts +16 -0
- package/src/processing/wrapper.ts +18 -0
- package/src/requests.ts +74 -0
- package/tsconfig.json +31 -0
package/package.json
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@duckity/js",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"module": "src/index.ts",
|
|
5
|
+
"scripts": {
|
|
6
|
+
"bundle": "rollup --config rollup.config.ts --configPlugin typescript"
|
|
7
|
+
},
|
|
8
|
+
"devDependencies": {
|
|
9
|
+
"@rollup/plugin-typescript": "^12.3.0",
|
|
10
|
+
"@rollup/plugin-wasm": "^6.2.2",
|
|
11
|
+
"@types/bun": "latest",
|
|
12
|
+
"rollup": "^4.62.3",
|
|
13
|
+
"rollup-plugin-web-worker-loader": "^1.7.0",
|
|
14
|
+
"tsdown": "^0.22.14"
|
|
15
|
+
},
|
|
16
|
+
"peerDependencies": {
|
|
17
|
+
"typescript": "^5"
|
|
18
|
+
},
|
|
19
|
+
"collaborators": [
|
|
20
|
+
"Rafael Bradley <rafabradleyrb@gmail.com>"
|
|
21
|
+
],
|
|
22
|
+
"type": "module"
|
|
23
|
+
}
|
package/rollup.config.ts
ADDED
package/src/index.ts
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import wrapper from "./processing/wrapper";
|
|
2
|
+
import { post } from "./requests";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Options for getting a Duckity challenge from the API.
|
|
6
|
+
*/
|
|
7
|
+
export interface GetDuckityChallengeOptions {
|
|
8
|
+
/**
|
|
9
|
+
* The custom-context threat correlation keys to be sent with the request.
|
|
10
|
+
*/
|
|
11
|
+
keys?: { [key: string]: string };
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* The base URL to the API endpoint. Defaults to `https://quack.duckity.com` if not provided.
|
|
15
|
+
*
|
|
16
|
+
* Update this when self-hosting a Duckling. No trailing slash must be included in the URL.
|
|
17
|
+
*/
|
|
18
|
+
api?: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* The response from the Duckling API when requesting a challenge.
|
|
23
|
+
*/
|
|
24
|
+
interface ChallengeResponse {
|
|
25
|
+
/**
|
|
26
|
+
* The encoded challenge string returned by the Duckling API.
|
|
27
|
+
*/
|
|
28
|
+
challenge: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Fetches, solves, and returns the solution to a Duckity challenge for the given protection
|
|
33
|
+
* profile ID.
|
|
34
|
+
*
|
|
35
|
+
* @param protectionProfileId The ID of the protection profile to get the challenge for.
|
|
36
|
+
* @param options Optional parameters for the challenge issuance.
|
|
37
|
+
* @returns The solution to the challenge issued by Duckity.
|
|
38
|
+
*/
|
|
39
|
+
export async function solve(
|
|
40
|
+
protectionProfileId: string,
|
|
41
|
+
options?: GetDuckityChallengeOptions,
|
|
42
|
+
): Promise<string> {
|
|
43
|
+
let response: ChallengeResponse = await post(
|
|
44
|
+
`${options?.api || "https://quack.duckity.com"}/v1/challenge`,
|
|
45
|
+
{
|
|
46
|
+
body: {
|
|
47
|
+
id: protectionProfileId,
|
|
48
|
+
keys: options?.keys || {},
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
let solution = await wrapper.solve(response.challenge);
|
|
54
|
+
|
|
55
|
+
return solution;
|
|
56
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import * as Comlink from "comlink";
|
|
2
|
+
import { process } from "@duckity/wasm";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Expose the `process` function from the WASM module to the main thread via Comlink.
|
|
6
|
+
*
|
|
7
|
+
* This allows the main thread to call the `process` function in the worker thread, which will
|
|
8
|
+
* handle the processing of the challenges without blocking the main thread.
|
|
9
|
+
*/
|
|
10
|
+
const api = {
|
|
11
|
+
async process(challenge: string) {
|
|
12
|
+
return process(challenge);
|
|
13
|
+
},
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
Comlink.expose(api);
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import wasm from "@duckity/wasm/duckity_wasm_bg.wasm";
|
|
2
|
+
import * as Comlink from "comlink";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Web Worker that handles the processing of the challenges.
|
|
6
|
+
*/
|
|
7
|
+
const worker = new Worker(, {
|
|
8
|
+
type: "module",
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Wrap the worker with Comlink to allow for easy communication between the main thread and the
|
|
13
|
+
* worker thread. The worker exposes a `solve` function that takes a challenge string and returns a
|
|
14
|
+
* promise that resolves to the solution string.
|
|
15
|
+
*/
|
|
16
|
+
export default Comlink.wrap<{
|
|
17
|
+
solve(challenge: string): Promise<string>;
|
|
18
|
+
}>(worker);
|
package/src/requests.ts
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error schema for API responses. This is used to extract error messages from failed requests.
|
|
3
|
+
*/
|
|
4
|
+
interface ErrorMessage {
|
|
5
|
+
title: string;
|
|
6
|
+
message: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Wrapper function for making HTTP requests using the Fetch API. It handles JSON parsing and error
|
|
11
|
+
* handling, throwing an error with a message extracted from the response if the request fails.
|
|
12
|
+
*
|
|
13
|
+
* @param url The URL to make the request to.
|
|
14
|
+
* @param options Extra options to pass to the Fetch API, such as method, headers, and body.
|
|
15
|
+
* @returns The parsed JSON response from the server if the request is successful.
|
|
16
|
+
*/
|
|
17
|
+
async function request<T>(url: string, options?: RequestInit): Promise<T> {
|
|
18
|
+
const response = await fetch(url, options);
|
|
19
|
+
|
|
20
|
+
let data;
|
|
21
|
+
|
|
22
|
+
try {
|
|
23
|
+
data = await response.json();
|
|
24
|
+
} catch {
|
|
25
|
+
data = await response.text();
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (!response.ok) {
|
|
29
|
+
throw new Error(
|
|
30
|
+
(data as ErrorMessage | undefined)?.message || "Request failed",
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return data as T;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Makes a GET request to the specified URL and returns the parsed JSON response.
|
|
39
|
+
*
|
|
40
|
+
* If the request fails, it throws an error with a message extracted from the response if
|
|
41
|
+
* available, or a generic message if not.
|
|
42
|
+
*
|
|
43
|
+
* @param url The URL to make the request to.
|
|
44
|
+
* @param options Extra options to pass to the Fetch API, such as headers and body.
|
|
45
|
+
* @returns The parsed JSON response from the server if the request is successful.
|
|
46
|
+
*/
|
|
47
|
+
export function get<T>(url: string, options?: RequestInit): Promise<T> {
|
|
48
|
+
return request(url, {
|
|
49
|
+
method: "GET",
|
|
50
|
+
...options,
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Makes a POST request to the specified URL and returns the parsed JSON response.
|
|
56
|
+
*
|
|
57
|
+
* If the request fails, it throws an error with a message extracted from the response if
|
|
58
|
+
* available, or a generic message if not.
|
|
59
|
+
*
|
|
60
|
+
* @param url The URL to make the request to.
|
|
61
|
+
* @param options Extra options to pass to the Fetch API, such as headers and body.
|
|
62
|
+
* @returns The parsed JSON response from the server if the request is successful.
|
|
63
|
+
*/
|
|
64
|
+
export function post<T>(url: string, options?: Omit<RequestInit, "body"> & { body: any }): Promise<T> {
|
|
65
|
+
return request(url, {
|
|
66
|
+
method: "POST",
|
|
67
|
+
headers: {
|
|
68
|
+
"Content-Type": "application/json",
|
|
69
|
+
...options?.headers,
|
|
70
|
+
},
|
|
71
|
+
body: options?.body ? JSON.stringify(options.body) : undefined,
|
|
72
|
+
...options,
|
|
73
|
+
});
|
|
74
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
// Environment setup & latest features
|
|
4
|
+
"lib": ["ESNext"],
|
|
5
|
+
"target": "ESNext",
|
|
6
|
+
"module": "Preserve",
|
|
7
|
+
"moduleDetection": "force",
|
|
8
|
+
"jsx": "react-jsx",
|
|
9
|
+
"allowJs": true,
|
|
10
|
+
"types": ["bun"],
|
|
11
|
+
|
|
12
|
+
// Bundler mode
|
|
13
|
+
"moduleResolution": "bundler",
|
|
14
|
+
"allowImportingTsExtensions": true,
|
|
15
|
+
"verbatimModuleSyntax": true,
|
|
16
|
+
"noEmit": true,
|
|
17
|
+
"outDir": "dist",
|
|
18
|
+
|
|
19
|
+
// Best practices
|
|
20
|
+
"strict": true,
|
|
21
|
+
"skipLibCheck": true,
|
|
22
|
+
"noFallthroughCasesInSwitch": true,
|
|
23
|
+
"noUncheckedIndexedAccess": true,
|
|
24
|
+
"noImplicitOverride": true,
|
|
25
|
+
|
|
26
|
+
// Some stricter flags (disabled by default)
|
|
27
|
+
"noUnusedLocals": false,
|
|
28
|
+
"noUnusedParameters": false,
|
|
29
|
+
"noPropertyAccessFromIndexSignature": false
|
|
30
|
+
}
|
|
31
|
+
}
|