@harborclient/sdk 0.4.3
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 +41 -0
- package/dist/client.d.ts +2 -0
- package/dist/clipboard.d.ts +27 -0
- package/dist/clipboard.d.ts.map +1 -0
- package/dist/clipboard.js +17 -0
- package/dist/http/index.d.ts +3 -0
- package/dist/http/index.d.ts.map +1 -0
- package/dist/http/index.js +2 -0
- package/dist/http/resolveRequest.d.ts +66 -0
- package/dist/http/resolveRequest.d.ts.map +1 -0
- package/dist/http/resolveRequest.js +191 -0
- package/dist/http/resolveRequest.test.d.ts +2 -0
- package/dist/http/resolveRequest.test.d.ts.map +1 -0
- package/dist/http/resolveRequest.test.js +40 -0
- package/dist/http/substitute.d.ts +29 -0
- package/dist/http/substitute.d.ts.map +1 -0
- package/dist/http/substitute.js +43 -0
- package/dist/http/substitute.test.d.ts +2 -0
- package/dist/http/substitute.test.d.ts.map +1 -0
- package/dist/http/substitute.test.js +85 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1 -0
- package/dist/main.d.ts +2 -0
- package/dist/main.d.ts.map +1 -0
- package/dist/main.js +1 -0
- package/dist/runtime/index.d.ts +20 -0
- package/dist/runtime/index.js +43 -0
- package/dist/runtime/jsx-dev-runtime.d.ts +12 -0
- package/dist/runtime/jsx-dev-runtime.js +15 -0
- package/dist/runtime/jsx-runtime.d.ts +35 -0
- package/dist/runtime/jsx-runtime.js +30 -0
- package/dist/runtime/react.d.ts +1 -0
- package/dist/runtime/react.js +46 -0
- package/dist/runtime/reactHost.js +26 -0
- package/dist/runtime/store.d.ts +19 -0
- package/dist/runtime/store.d.ts.map +1 -0
- package/dist/runtime/store.js +38 -0
- package/dist/runtime/store.ts +45 -0
- package/dist/runtime-utils.d.ts +36 -0
- package/dist/runtime-utils.d.ts.map +1 -0
- package/dist/runtime-utils.js +101 -0
- package/dist/runtime-utils.test.d.ts +2 -0
- package/dist/runtime-utils.test.d.ts.map +1 -0
- package/dist/runtime-utils.test.js +104 -0
- package/dist/signing/canonical.d.ts +27 -0
- package/dist/signing/canonical.d.ts.map +1 -0
- package/dist/signing/canonical.js +92 -0
- package/dist/signing/cli-sign.d.ts +3 -0
- package/dist/signing/cli-sign.d.ts.map +1 -0
- package/dist/signing/cli-sign.js +4 -0
- package/dist/signing/cli-verify.d.ts +3 -0
- package/dist/signing/cli-verify.d.ts.map +1 -0
- package/dist/signing/cli-verify.js +4 -0
- package/dist/signing/cli.d.ts +13 -0
- package/dist/signing/cli.d.ts.map +1 -0
- package/dist/signing/cli.js +148 -0
- package/dist/signing/index.d.ts +10 -0
- package/dist/signing/index.d.ts.map +1 -0
- package/dist/signing/index.js +7 -0
- package/dist/signing/inventory.d.ts +21 -0
- package/dist/signing/inventory.d.ts.map +1 -0
- package/dist/signing/inventory.js +80 -0
- package/dist/signing/manifest.d.ts +16 -0
- package/dist/signing/manifest.d.ts.map +1 -0
- package/dist/signing/manifest.js +33 -0
- package/dist/signing/sign.d.ts +10 -0
- package/dist/signing/sign.d.ts.map +1 -0
- package/dist/signing/sign.js +48 -0
- package/dist/signing/signing.test.d.ts +2 -0
- package/dist/signing/signing.test.d.ts.map +1 -0
- package/dist/signing/signing.test.js +100 -0
- package/dist/signing/testFixtures.d.ts +21 -0
- package/dist/signing/testFixtures.d.ts.map +1 -0
- package/dist/signing/testFixtures.js +41 -0
- package/dist/signing/types.d.ts +87 -0
- package/dist/signing/types.d.ts.map +1 -0
- package/dist/signing/types.js +12 -0
- package/dist/signing/verify.d.ts +17 -0
- package/dist/signing/verify.d.ts.map +1 -0
- package/dist/signing/verify.js +129 -0
- package/dist/storage/cappedList.d.ts +55 -0
- package/dist/storage/cappedList.d.ts.map +1 -0
- package/dist/storage/cappedList.js +102 -0
- package/dist/storage/cappedList.test.d.ts +2 -0
- package/dist/storage/cappedList.test.d.ts.map +1 -0
- package/dist/storage/cappedList.test.js +11 -0
- package/dist/storage/index.d.ts +2 -0
- package/dist/storage/index.d.ts.map +1 -0
- package/dist/storage/index.js +1 -0
- package/dist/types.d.ts +1282 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +1 -0
- package/dist/ui/format.d.ts +23 -0
- package/dist/ui/format.d.ts.map +1 -0
- package/dist/ui/format.js +45 -0
- package/dist/ui/index.d.ts +3 -0
- package/dist/ui/index.d.ts.map +1 -0
- package/dist/ui/index.js +2 -0
- package/dist/ui/tokens.d.ts +34 -0
- package/dist/ui/tokens.d.ts.map +1 -0
- package/dist/ui/tokens.js +56 -0
- package/dist/utilities.test.d.ts +2 -0
- package/dist/utilities.test.d.ts.map +1 -0
- package/dist/utilities.test.js +16 -0
- package/package.json +130 -0
package/README.md
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# @harborclient/sdk
|
|
2
|
+
|
|
3
|
+
TypeScript definitions, utility modules, and React runtime helpers for [HarborClient](https://harborclient.com/) plugin development.
|
|
4
|
+
|
|
5
|
+
**Documentation:** [https://harborclient.github.io/sdk/](https://harborclient.github.io/sdk/)
|
|
6
|
+
|
|
7
|
+
Install as a **dev dependency** in your plugin project. The package ships type declarations, HTTP/storage/UI helpers, and a JSX runtime that forwards to the host's React instance via `installReact(hc.react)`.
|
|
8
|
+
|
|
9
|
+
Requires HarborClient **>=1.9.0** when using `hc.pluginId`, renderer HTTP lifecycle events, typed IPC invoke, and host request commands.
|
|
10
|
+
|
|
11
|
+
## Install
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
pnpm add -D @harborclient/sdk
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
See the [install guide](https://harborclient.github.io/sdk/install) for version requirements.
|
|
18
|
+
|
|
19
|
+
## Quick start
|
|
20
|
+
|
|
21
|
+
```tsx
|
|
22
|
+
import { installReact } from '@harborclient/sdk';
|
|
23
|
+
import type { PluginContext } from '@harborclient/sdk';
|
|
24
|
+
|
|
25
|
+
export function activate(hc: PluginContext): void {
|
|
26
|
+
installReact(hc.react);
|
|
27
|
+
// register contributions…
|
|
28
|
+
}
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Full guides — package layout, manifest, APIs, examples, and dev workflow — live in the [plugin development docs](https://harborclient.github.io/sdk/).
|
|
32
|
+
|
|
33
|
+
## Trusted publishers
|
|
34
|
+
|
|
35
|
+
HarborClient maintains a [trusted publisher registry](https://harborclient.com/plugins/trusted.json). Authors listed there must sign every plugin they publish; HarborClient rejects installs that claim a trusted author name without a valid signature. See the [signing guide](https://harborclient.github.io/sdk/signing) for key generation and verification.
|
|
36
|
+
|
|
37
|
+
To discuss becoming a trusted publisher, email [contact@harborclient.com](mailto:contact@harborclient.com).
|
|
38
|
+
|
|
39
|
+
## License
|
|
40
|
+
|
|
41
|
+
MIT
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { PluginContext } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Options for {@link copyToClipboard}.
|
|
4
|
+
*/
|
|
5
|
+
export interface CopyToClipboardOptions {
|
|
6
|
+
/**
|
|
7
|
+
* Success toast message shown when copy succeeds.
|
|
8
|
+
*/
|
|
9
|
+
toast?: string;
|
|
10
|
+
/**
|
|
11
|
+
* Toast display duration in milliseconds.
|
|
12
|
+
*/
|
|
13
|
+
duration?: number;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Copies text to the clipboard with optional toast feedback.
|
|
17
|
+
*
|
|
18
|
+
* Prefer {@link PluginUi.copyToClipboard} on the host when available; this helper
|
|
19
|
+
* works with any plugin context that exposes `ui.showToast`.
|
|
20
|
+
*
|
|
21
|
+
* @param hc - Renderer plugin context.
|
|
22
|
+
* @param text - Text to copy.
|
|
23
|
+
* @param options - Optional toast message and duration.
|
|
24
|
+
* @throws When clipboard access fails.
|
|
25
|
+
*/
|
|
26
|
+
export declare function copyToClipboard(hc: PluginContext, text: string, options?: CopyToClipboardOptions): Promise<void>;
|
|
27
|
+
//# sourceMappingURL=clipboard.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"clipboard.d.ts","sourceRoot":"","sources":["../src/clipboard.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAEhD;;GAEG;AACH,MAAM,WAAW,sBAAsB;IACrC;;OAEG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf;;OAEG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,eAAe,CACnC,EAAE,EAAE,aAAa,EACjB,IAAI,EAAE,MAAM,EACZ,OAAO,CAAC,EAAE,sBAAsB,GAC/B,OAAO,CAAC,IAAI,CAAC,CAKf"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copies text to the clipboard with optional toast feedback.
|
|
3
|
+
*
|
|
4
|
+
* Prefer {@link PluginUi.copyToClipboard} on the host when available; this helper
|
|
5
|
+
* works with any plugin context that exposes `ui.showToast`.
|
|
6
|
+
*
|
|
7
|
+
* @param hc - Renderer plugin context.
|
|
8
|
+
* @param text - Text to copy.
|
|
9
|
+
* @param options - Optional toast message and duration.
|
|
10
|
+
* @throws When clipboard access fails.
|
|
11
|
+
*/
|
|
12
|
+
export async function copyToClipboard(hc, text, options) {
|
|
13
|
+
await navigator.clipboard.writeText(text);
|
|
14
|
+
if (options?.toast) {
|
|
15
|
+
hc.ui.showToast(options.toast, { duration: options.duration });
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export { buildAuthHeaderValue, buildHeaders, buildUrl, encodeBasicAuth, resolveRequest, type ResolvedRequest } from './resolveRequest.js';
|
|
2
|
+
export { resolveAuthVariables, substituteKeyValueRows, substituteVariables } from './substitute.js';
|
|
3
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/http/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,oBAAoB,EACpB,YAAY,EACZ,QAAQ,EACR,eAAe,EACf,cAAc,EACd,KAAK,eAAe,EACrB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,oBAAoB,EAAE,sBAAsB,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import type { AuthConfig, RequestDraft, RequestTabContext } from '../types.js';
|
|
2
|
+
type KeyValue = {
|
|
3
|
+
key: string;
|
|
4
|
+
value: string;
|
|
5
|
+
enabled: boolean;
|
|
6
|
+
};
|
|
7
|
+
/**
|
|
8
|
+
* Fully merged, variable-substituted request snapshot matching HarborClient send-time behavior.
|
|
9
|
+
*
|
|
10
|
+
* This mirrors the host send pipeline but is maintained in the SDK — drift is possible when
|
|
11
|
+
* host send logic changes.
|
|
12
|
+
*/
|
|
13
|
+
export interface ResolvedRequest {
|
|
14
|
+
/**
|
|
15
|
+
* HTTP method in uppercase when non-GET.
|
|
16
|
+
*/
|
|
17
|
+
method: string;
|
|
18
|
+
/**
|
|
19
|
+
* Final URL including merged query parameters.
|
|
20
|
+
*/
|
|
21
|
+
url: string;
|
|
22
|
+
/**
|
|
23
|
+
* Outgoing request headers as a flat key/value map.
|
|
24
|
+
*/
|
|
25
|
+
headers: Record<string, string>;
|
|
26
|
+
/**
|
|
27
|
+
* Request body content as a string.
|
|
28
|
+
*/
|
|
29
|
+
body: string;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Encodes username and password as a UTF-8-safe Basic Auth credential string.
|
|
33
|
+
*
|
|
34
|
+
* @param username - Basic Auth username.
|
|
35
|
+
* @param password - Basic Auth password.
|
|
36
|
+
*/
|
|
37
|
+
export declare function encodeBasicAuth(username: string, password: string): string;
|
|
38
|
+
/**
|
|
39
|
+
* Builds the Authorization header value from an auth config.
|
|
40
|
+
*
|
|
41
|
+
* @param auth - Auth configuration from the request or collection.
|
|
42
|
+
*/
|
|
43
|
+
export declare function buildAuthHeaderValue(auth: AuthConfig): string | null;
|
|
44
|
+
/**
|
|
45
|
+
* Merges enabled query parameters into a base URL.
|
|
46
|
+
*
|
|
47
|
+
* @param baseUrl - Request URL before query string merging.
|
|
48
|
+
* @param params - Key-value pairs to append as search params.
|
|
49
|
+
*/
|
|
50
|
+
export declare function buildUrl(baseUrl: string, params: KeyValue[]): string;
|
|
51
|
+
/**
|
|
52
|
+
* Builds outgoing request headers mirroring HarborClient send-time defaults.
|
|
53
|
+
*
|
|
54
|
+
* @param draft - Active request draft.
|
|
55
|
+
* @param collectionHeaders - Collection-level headers.
|
|
56
|
+
* @param authValue - Computed Authorization header value, if any.
|
|
57
|
+
*/
|
|
58
|
+
export declare function buildHeaders(draft: RequestDraft, collectionHeaders: KeyValue[], authValue: string | null): Record<string, string>;
|
|
59
|
+
/**
|
|
60
|
+
* Returns the fully merged, variable-substituted request as HarborClient would send it.
|
|
61
|
+
*
|
|
62
|
+
* @param context - Read-only request tab context from the host.
|
|
63
|
+
*/
|
|
64
|
+
export declare function resolveRequest(context: RequestTabContext): ResolvedRequest;
|
|
65
|
+
export {};
|
|
66
|
+
//# sourceMappingURL=resolveRequest.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resolveRequest.d.ts","sourceRoot":"","sources":["../../src/http/resolveRequest.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAG/E,KAAK,QAAQ,GAAG;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,OAAO,CAAC;CAClB,CAAC;AAEF;;;;;GAKG;AACH,MAAM,WAAW,eAAe;IAC9B;;OAEG;IACH,MAAM,EAAE,MAAM,CAAC;IAEf;;OAEG;IACH,GAAG,EAAE,MAAM,CAAC;IAEZ;;OAEG;IACH,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAEhC;;OAEG;IACH,IAAI,EAAE,MAAM,CAAC;CACd;AAiBD;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAW1E;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,UAAU,GAAG,MAAM,GAAG,IAAI,CAiBpE;AA4BD;;;;;GAKG;AACH,wBAAgB,QAAQ,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,GAAG,MAAM,CAwBpE;AAsBD;;;;;;GAMG;AACH,wBAAgB,YAAY,CAC1B,KAAK,EAAE,YAAY,EACnB,iBAAiB,EAAE,QAAQ,EAAE,EAC7B,SAAS,EAAE,MAAM,GAAG,IAAI,GACvB,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CA2BxB;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,iBAAiB,GAAG,eAAe,CAwB1E"}
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import { resolveAuthVariables, substituteKeyValueRows, substituteVariables } from './substitute.js';
|
|
2
|
+
/**
|
|
3
|
+
* Returns whether a header field contains control characters unsafe for HTTP.
|
|
4
|
+
*
|
|
5
|
+
* @param value - Header name or value to inspect.
|
|
6
|
+
*/
|
|
7
|
+
function hasUnsafeHeaderFieldChars(value) {
|
|
8
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
9
|
+
const code = value.charCodeAt(index);
|
|
10
|
+
if (code <= 0x1f || code === 0x7f) {
|
|
11
|
+
return true;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Encodes username and password as a UTF-8-safe Basic Auth credential string.
|
|
18
|
+
*
|
|
19
|
+
* @param username - Basic Auth username.
|
|
20
|
+
* @param password - Basic Auth password.
|
|
21
|
+
*/
|
|
22
|
+
export function encodeBasicAuth(username, password) {
|
|
23
|
+
const credential = `${username}:${password}`;
|
|
24
|
+
if (typeof TextEncoder !== 'undefined' && typeof globalThis.btoa === 'function') {
|
|
25
|
+
const bytes = new TextEncoder().encode(credential);
|
|
26
|
+
let binary = '';
|
|
27
|
+
for (const byte of bytes) {
|
|
28
|
+
binary += String.fromCharCode(byte);
|
|
29
|
+
}
|
|
30
|
+
return globalThis.btoa(binary);
|
|
31
|
+
}
|
|
32
|
+
return globalThis.btoa?.(credential) ?? credential;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Builds the Authorization header value from an auth config.
|
|
36
|
+
*
|
|
37
|
+
* @param auth - Auth configuration from the request or collection.
|
|
38
|
+
*/
|
|
39
|
+
export function buildAuthHeaderValue(auth) {
|
|
40
|
+
if (auth.type === 'none') {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
if (auth.type === 'basic') {
|
|
44
|
+
const username = auth.basic.username.trim();
|
|
45
|
+
const password = auth.basic.password;
|
|
46
|
+
if (!username && !password.trim()) {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
return `Basic ${encodeBasicAuth(username, password)}`;
|
|
50
|
+
}
|
|
51
|
+
const token = auth.bearer.token.trim();
|
|
52
|
+
if (!token || hasUnsafeHeaderFieldChars(token)) {
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
return `Bearer ${token}`;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Returns whether a URL string is a root-relative path.
|
|
59
|
+
*
|
|
60
|
+
* @param url - Trimmed URL string.
|
|
61
|
+
*/
|
|
62
|
+
function isRootRelativePath(url) {
|
|
63
|
+
return url.startsWith('/') && !url.startsWith('//');
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Appends query parameters via string concatenation for root-relative paths.
|
|
67
|
+
*
|
|
68
|
+
* @param trimmed - Trimmed base URL that failed absolute URL parsing.
|
|
69
|
+
* @param enabledParams - Enabled key-value pairs to append.
|
|
70
|
+
*/
|
|
71
|
+
function appendQueryFallback(trimmed, enabledParams) {
|
|
72
|
+
const separator = trimmed.includes('?') ? '&' : '?';
|
|
73
|
+
const query = enabledParams
|
|
74
|
+
.map((param) => `${encodeURIComponent(param.key.trim())}=${encodeURIComponent(param.value)}`)
|
|
75
|
+
.join('&');
|
|
76
|
+
return `${trimmed}${separator}${query}`;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Merges enabled query parameters into a base URL.
|
|
80
|
+
*
|
|
81
|
+
* @param baseUrl - Request URL before query string merging.
|
|
82
|
+
* @param params - Key-value pairs to append as search params.
|
|
83
|
+
*/
|
|
84
|
+
export function buildUrl(baseUrl, params) {
|
|
85
|
+
const trimmed = baseUrl.trim();
|
|
86
|
+
if (!trimmed) {
|
|
87
|
+
return trimmed;
|
|
88
|
+
}
|
|
89
|
+
const enabledParams = params.filter((param) => param.enabled && param.key.trim());
|
|
90
|
+
if (enabledParams.length === 0) {
|
|
91
|
+
return trimmed;
|
|
92
|
+
}
|
|
93
|
+
try {
|
|
94
|
+
const url = new URL(trimmed);
|
|
95
|
+
if (url.protocol !== 'http:' && url.protocol !== 'https:') {
|
|
96
|
+
return trimmed;
|
|
97
|
+
}
|
|
98
|
+
for (const param of enabledParams) {
|
|
99
|
+
url.searchParams.set(param.key.trim(), param.value);
|
|
100
|
+
}
|
|
101
|
+
return url.toString();
|
|
102
|
+
}
|
|
103
|
+
catch {
|
|
104
|
+
if (!isRootRelativePath(trimmed)) {
|
|
105
|
+
return trimmed;
|
|
106
|
+
}
|
|
107
|
+
return appendQueryFallback(trimmed, enabledParams);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Returns enabled key-value rows with non-empty keys.
|
|
112
|
+
*
|
|
113
|
+
* @param rows - Header or param rows from the draft.
|
|
114
|
+
*/
|
|
115
|
+
function enabledRows(rows) {
|
|
116
|
+
return rows.filter((row) => row.enabled && row.key.trim());
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Returns true when any enabled row is a non-empty Authorization header.
|
|
120
|
+
*
|
|
121
|
+
* @param rows - Header rows to inspect.
|
|
122
|
+
*/
|
|
123
|
+
function hasManualAuthorization(rows) {
|
|
124
|
+
return enabledRows(rows).some((row) => row.key.trim().toLowerCase() === 'authorization' && row.value.trim() !== '');
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Builds outgoing request headers mirroring HarborClient send-time defaults.
|
|
128
|
+
*
|
|
129
|
+
* @param draft - Active request draft.
|
|
130
|
+
* @param collectionHeaders - Collection-level headers.
|
|
131
|
+
* @param authValue - Computed Authorization header value, if any.
|
|
132
|
+
*/
|
|
133
|
+
export function buildHeaders(draft, collectionHeaders, authValue) {
|
|
134
|
+
const mergedRows = [
|
|
135
|
+
...(authValue && !hasManualAuthorization([...collectionHeaders, ...draft.headers])
|
|
136
|
+
? [{ key: 'Authorization', value: authValue, enabled: true }]
|
|
137
|
+
: []),
|
|
138
|
+
...collectionHeaders,
|
|
139
|
+
...draft.headers
|
|
140
|
+
];
|
|
141
|
+
const result = {};
|
|
142
|
+
for (const header of enabledRows(mergedRows)) {
|
|
143
|
+
const key = header.key.trim();
|
|
144
|
+
if (draft.body_type === 'multipart' && key.toLowerCase() === 'content-type') {
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
result[key] = header.value;
|
|
148
|
+
}
|
|
149
|
+
const hasContentType = Object.keys(result).some((key) => key.toLowerCase() === 'content-type');
|
|
150
|
+
if (!hasContentType) {
|
|
151
|
+
if (draft.body_type === 'json') {
|
|
152
|
+
result['Content-Type'] = 'application/json';
|
|
153
|
+
}
|
|
154
|
+
else if (draft.body_type === 'text') {
|
|
155
|
+
result['Content-Type'] = 'text/plain';
|
|
156
|
+
}
|
|
157
|
+
else if (draft.body_type === 'urlencoded') {
|
|
158
|
+
result['Content-Type'] = 'application/x-www-form-urlencoded';
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
return result;
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Returns the fully merged, variable-substituted request as HarborClient would send it.
|
|
165
|
+
*
|
|
166
|
+
* @param context - Read-only request tab context from the host.
|
|
167
|
+
*/
|
|
168
|
+
export function resolveRequest(context) {
|
|
169
|
+
const { draft, collectionAuth, collectionHeaders, variables } = context;
|
|
170
|
+
const substitute = (text) => substituteVariables(text, variables);
|
|
171
|
+
const resolvedDraft = {
|
|
172
|
+
...draft,
|
|
173
|
+
url: substitute(draft.url),
|
|
174
|
+
body: substitute(draft.body),
|
|
175
|
+
params: substituteKeyValueRows(draft.params, variables),
|
|
176
|
+
headers: substituteKeyValueRows(draft.headers, variables),
|
|
177
|
+
auth: resolveAuthVariables(draft.auth, substitute)
|
|
178
|
+
};
|
|
179
|
+
const resolvedCollectionHeaders = substituteKeyValueRows(collectionHeaders, variables);
|
|
180
|
+
const resolvedCollectionAuth = resolveAuthVariables(collectionAuth, substitute);
|
|
181
|
+
const effectiveAuth = resolvedDraft.auth.type !== 'none' ? resolvedDraft.auth : resolvedCollectionAuth;
|
|
182
|
+
const authValue = buildAuthHeaderValue(effectiveAuth);
|
|
183
|
+
const url = buildUrl(resolvedDraft.url, resolvedDraft.params);
|
|
184
|
+
const headers = buildHeaders(resolvedDraft, resolvedCollectionHeaders, authValue);
|
|
185
|
+
return {
|
|
186
|
+
method: resolvedDraft.method,
|
|
187
|
+
url,
|
|
188
|
+
headers,
|
|
189
|
+
body: resolvedDraft.body
|
|
190
|
+
};
|
|
191
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resolveRequest.test.d.ts","sourceRoot":"","sources":["../../src/http/resolveRequest.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { describe, expect, it } from '@jest/globals';
|
|
2
|
+
import { resolveRequest } from './resolveRequest.js';
|
|
3
|
+
import { substituteVariables } from './substitute.js';
|
|
4
|
+
describe('substituteVariables', () => {
|
|
5
|
+
it('replaces known placeholders', () => {
|
|
6
|
+
expect(substituteVariables('{{host}}/api', { host: 'https://example.com' })).toBe('https://example.com/api');
|
|
7
|
+
});
|
|
8
|
+
});
|
|
9
|
+
describe('resolveRequest', () => {
|
|
10
|
+
it('merges collection auth and variables', () => {
|
|
11
|
+
const context = {
|
|
12
|
+
draft: {
|
|
13
|
+
method: 'POST',
|
|
14
|
+
url: '{{base}}/users',
|
|
15
|
+
params: [{ key: 'page', value: '1', enabled: true }],
|
|
16
|
+
headers: [{ key: 'X-Test', value: '1', enabled: true }],
|
|
17
|
+
body: '{"ok":true}',
|
|
18
|
+
auth: {
|
|
19
|
+
type: 'none',
|
|
20
|
+
basic: { username: '', password: '' },
|
|
21
|
+
bearer: { token: '' }
|
|
22
|
+
},
|
|
23
|
+
body_type: 'json'
|
|
24
|
+
},
|
|
25
|
+
response: null,
|
|
26
|
+
readOnly: true,
|
|
27
|
+
collectionAuth: {
|
|
28
|
+
type: 'bearer',
|
|
29
|
+
basic: { username: '', password: '' },
|
|
30
|
+
bearer: { token: '{{token}}' }
|
|
31
|
+
},
|
|
32
|
+
collectionHeaders: [{ key: 'Accept', value: 'application/json', enabled: true }],
|
|
33
|
+
variables: { base: 'https://api.test', token: 'secret' }
|
|
34
|
+
};
|
|
35
|
+
const resolved = resolveRequest(context);
|
|
36
|
+
expect(resolved.url).toBe('https://api.test/users?page=1');
|
|
37
|
+
expect(resolved.headers.Authorization).toBe('Bearer secret');
|
|
38
|
+
expect(resolved.headers['Content-Type']).toBe('application/json');
|
|
39
|
+
});
|
|
40
|
+
});
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { AuthConfig } from '../types.js';
|
|
2
|
+
type KeyValue = {
|
|
3
|
+
key: string;
|
|
4
|
+
value: string;
|
|
5
|
+
enabled: boolean;
|
|
6
|
+
};
|
|
7
|
+
/**
|
|
8
|
+
* Replaces {{key}} placeholders using a runtime variable map.
|
|
9
|
+
*
|
|
10
|
+
* @param text - Text containing variable placeholders.
|
|
11
|
+
* @param runtimeVars - Current runtime variable values.
|
|
12
|
+
*/
|
|
13
|
+
export declare function substituteVariables(text: string, runtimeVars: Record<string, string>): string;
|
|
14
|
+
/**
|
|
15
|
+
* Resolves {{variable}} placeholders in auth credential fields.
|
|
16
|
+
*
|
|
17
|
+
* @param auth - Auth config with raw editor values.
|
|
18
|
+
* @param substitute - Function that resolves placeholders in a string.
|
|
19
|
+
*/
|
|
20
|
+
export declare function resolveAuthVariables(auth: AuthConfig, substitute: (text: string) => string): AuthConfig;
|
|
21
|
+
/**
|
|
22
|
+
* Substitutes variables in key-value row values.
|
|
23
|
+
*
|
|
24
|
+
* @param rows - Header or param rows from the draft.
|
|
25
|
+
* @param runtimeVars - Merged collection and environment variable map.
|
|
26
|
+
*/
|
|
27
|
+
export declare function substituteKeyValueRows(rows: KeyValue[], runtimeVars: Record<string, string>): KeyValue[];
|
|
28
|
+
export {};
|
|
29
|
+
//# sourceMappingURL=substitute.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"substitute.d.ts","sourceRoot":"","sources":["../../src/http/substitute.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAE9C,KAAK,QAAQ,GAAG;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,OAAO,CAAC;CAClB,CAAC;AAIF;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,MAAM,CAK7F;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAClC,IAAI,EAAE,UAAU,EAChB,UAAU,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,GACnC,UAAU,CAWZ;AAED;;;;;GAKG;AACH,wBAAgB,sBAAsB,CACpC,IAAI,EAAE,QAAQ,EAAE,EAChB,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAClC,QAAQ,EAAE,CAKZ"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
const VARIABLE_PATTERN = /\{\{\s*([\w.-]+)\s*\}\}/g;
|
|
2
|
+
/**
|
|
3
|
+
* Replaces {{key}} placeholders using a runtime variable map.
|
|
4
|
+
*
|
|
5
|
+
* @param text - Text containing variable placeholders.
|
|
6
|
+
* @param runtimeVars - Current runtime variable values.
|
|
7
|
+
*/
|
|
8
|
+
export function substituteVariables(text, runtimeVars) {
|
|
9
|
+
return text.replace(VARIABLE_PATTERN, (match, key) => {
|
|
10
|
+
const value = runtimeVars[key];
|
|
11
|
+
return value !== undefined ? value : match;
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Resolves {{variable}} placeholders in auth credential fields.
|
|
16
|
+
*
|
|
17
|
+
* @param auth - Auth config with raw editor values.
|
|
18
|
+
* @param substitute - Function that resolves placeholders in a string.
|
|
19
|
+
*/
|
|
20
|
+
export function resolveAuthVariables(auth, substitute) {
|
|
21
|
+
return {
|
|
22
|
+
...auth,
|
|
23
|
+
basic: {
|
|
24
|
+
username: substitute(auth.basic.username),
|
|
25
|
+
password: substitute(auth.basic.password)
|
|
26
|
+
},
|
|
27
|
+
bearer: {
|
|
28
|
+
token: substitute(auth.bearer.token)
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Substitutes variables in key-value row values.
|
|
34
|
+
*
|
|
35
|
+
* @param rows - Header or param rows from the draft.
|
|
36
|
+
* @param runtimeVars - Merged collection and environment variable map.
|
|
37
|
+
*/
|
|
38
|
+
export function substituteKeyValueRows(rows, runtimeVars) {
|
|
39
|
+
return rows.map((row) => ({
|
|
40
|
+
...row,
|
|
41
|
+
value: substituteVariables(row.value, runtimeVars)
|
|
42
|
+
}));
|
|
43
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"substitute.test.d.ts","sourceRoot":"","sources":["../../src/http/substitute.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { describe, expect, it } from '@jest/globals';
|
|
2
|
+
import { resolveAuthVariables, substituteKeyValueRows, substituteVariables } from './substitute.js';
|
|
3
|
+
describe('substituteVariables', () => {
|
|
4
|
+
it('replaces known placeholders', () => {
|
|
5
|
+
expect(substituteVariables('{{host}}/api', { host: 'https://example.com' })).toBe('https://example.com/api');
|
|
6
|
+
});
|
|
7
|
+
it('allows whitespace inside braces', () => {
|
|
8
|
+
expect(substituteVariables('{{ host }}/api', { host: 'https://example.com' })).toBe('https://example.com/api');
|
|
9
|
+
});
|
|
10
|
+
it('supports dotted and hyphenated keys', () => {
|
|
11
|
+
expect(substituteVariables('{{api.base}}-{{api-key}}', {
|
|
12
|
+
'api.base': 'https://example.com',
|
|
13
|
+
'api-key': 'secret'
|
|
14
|
+
})).toBe('https://example.com-secret');
|
|
15
|
+
});
|
|
16
|
+
it('replaces multiple placeholders in one string', () => {
|
|
17
|
+
expect(substituteVariables('{{scheme}}://{{host}}/{{path}}', {
|
|
18
|
+
scheme: 'https',
|
|
19
|
+
host: 'example.com',
|
|
20
|
+
path: 'users'
|
|
21
|
+
})).toBe('https://example.com/users');
|
|
22
|
+
});
|
|
23
|
+
it('leaves unknown placeholders unchanged', () => {
|
|
24
|
+
expect(substituteVariables('{{missing}}/api', { host: 'https://example.com' })).toBe('{{missing}}/api');
|
|
25
|
+
});
|
|
26
|
+
it('replaces with empty string when variable is defined as empty', () => {
|
|
27
|
+
expect(substituteVariables('prefix{{token}}suffix', { token: '' })).toBe('prefixsuffix');
|
|
28
|
+
});
|
|
29
|
+
it('returns text unchanged when there are no placeholders', () => {
|
|
30
|
+
expect(substituteVariables('plain text', { host: 'https://example.com' })).toBe('plain text');
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
describe('resolveAuthVariables', () => {
|
|
34
|
+
const auth = {
|
|
35
|
+
type: 'basic',
|
|
36
|
+
basic: {
|
|
37
|
+
username: '{{user}}',
|
|
38
|
+
password: '{{pass}}'
|
|
39
|
+
},
|
|
40
|
+
bearer: {
|
|
41
|
+
token: '{{token}}'
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
it('substitutes basic and bearer credential fields', () => {
|
|
45
|
+
const substitute = (text) => text.replace('{{user}}', 'alice').replace('{{pass}}', 'secret').replace('{{token}}', 'tok');
|
|
46
|
+
expect(resolveAuthVariables(auth, substitute)).toEqual({
|
|
47
|
+
type: 'basic',
|
|
48
|
+
basic: {
|
|
49
|
+
username: 'alice',
|
|
50
|
+
password: 'secret'
|
|
51
|
+
},
|
|
52
|
+
bearer: {
|
|
53
|
+
token: 'tok'
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
it('preserves auth type and structure', () => {
|
|
58
|
+
const substitute = (text) => text;
|
|
59
|
+
const resolved = resolveAuthVariables(auth, substitute);
|
|
60
|
+
expect(resolved.type).toBe('basic');
|
|
61
|
+
expect(resolved.basic).toEqual(auth.basic);
|
|
62
|
+
expect(resolved.bearer).toEqual(auth.bearer);
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
describe('substituteKeyValueRows', () => {
|
|
66
|
+
it('substitutes values while preserving row metadata', () => {
|
|
67
|
+
const rows = [
|
|
68
|
+
{ key: 'Accept', value: '{{accept}}', enabled: true },
|
|
69
|
+
{ key: 'X-Api-Key', value: 'static', enabled: false }
|
|
70
|
+
];
|
|
71
|
+
expect(substituteKeyValueRows(rows, { accept: 'application/json' })).toEqual([
|
|
72
|
+
{ key: 'Accept', value: 'application/json', enabled: true },
|
|
73
|
+
{ key: 'X-Api-Key', value: 'static', enabled: false }
|
|
74
|
+
]);
|
|
75
|
+
});
|
|
76
|
+
it('leaves unknown placeholders in row values', () => {
|
|
77
|
+
const rows = [{ key: 'Host', value: '{{host}}', enabled: true }];
|
|
78
|
+
expect(substituteKeyValueRows(rows, {})).toEqual([
|
|
79
|
+
{ key: 'Host', value: '{{host}}', enabled: true }
|
|
80
|
+
]);
|
|
81
|
+
});
|
|
82
|
+
it('returns an empty array for no rows', () => {
|
|
83
|
+
expect(substituteKeyValueRows([], { host: 'example.com' })).toEqual([]);
|
|
84
|
+
});
|
|
85
|
+
});
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,mBAAmB,SAAS,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/main.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,UAAU,EACV,iBAAiB,EACjB,UAAU,EACV,iBAAiB,EACjB,kBAAkB,EAClB,SAAS,EACT,aAAa,EACd,MAAM,SAAS,CAAC"}
|
package/dist/main.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type * as React from 'react';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Installs the HarborClient renderer React instance for plugin JSX and hooks.
|
|
5
|
+
*
|
|
6
|
+
* Call once at the start of `activate(hc)` before registering UI contributions.
|
|
7
|
+
*
|
|
8
|
+
* @param react - React namespace from `hc.react`.
|
|
9
|
+
*/
|
|
10
|
+
export function installReact(react: typeof React): void;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Creates a React component from a factory that receives the host React namespace.
|
|
14
|
+
*
|
|
15
|
+
* @param factory - Builds a component type using hooks or createElement from host React.
|
|
16
|
+
* @returns Component safe to pass to `hc.ui.register*` registration APIs.
|
|
17
|
+
*/
|
|
18
|
+
export function createPluginComponent<P extends Record<string, unknown>>(
|
|
19
|
+
factory: (react: typeof React) => React.ComponentType<P>
|
|
20
|
+
): React.ComponentType<P>;
|