@cloudcannon/sdk 0.0.4 → 0.0.5
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 +23 -3
- package/dist/index.d.ts +12 -3
- package/dist/index.js +41 -16
- package/dist/src/helpers/query.d.ts +1 -1
- package/dist/src/helpers/query.js +5 -1
- package/dist/src/inbox.js +1 -1
- package/dist/src/org.js +3 -3
- package/dist/src/site.js +3 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -30,29 +30,49 @@ npm install @cloudcannon/sdk
|
|
|
30
30
|
|
|
31
31
|
## Creating and configuring the client
|
|
32
32
|
|
|
33
|
-
Import `CloudCannonClient` and instantiate it with your API key:
|
|
33
|
+
Import `CloudCannonClient` and instantiate it with your API key or user access key:
|
|
34
34
|
|
|
35
35
|
```typescript
|
|
36
36
|
import CloudCannonClient from '@cloudcannon/sdk';
|
|
37
37
|
|
|
38
|
+
// Authenticate with an API key
|
|
38
39
|
const client = new CloudCannonClient({
|
|
39
40
|
key: 'your-api-key-here',
|
|
40
41
|
});
|
|
42
|
+
|
|
43
|
+
// Authenticate with a user access key
|
|
44
|
+
const client = new CloudCannonClient({
|
|
45
|
+
userAccessKey: {
|
|
46
|
+
id: 'your-access-key-id',
|
|
47
|
+
secret: 'your-access-key-secret',
|
|
48
|
+
},
|
|
49
|
+
});
|
|
41
50
|
```
|
|
42
51
|
|
|
43
52
|
### Optional configuration
|
|
44
53
|
|
|
45
54
|
| Option | Type | Description |
|
|
46
55
|
|--------|------|-------------|
|
|
47
|
-
| `key` | `string` |
|
|
56
|
+
| `key` | `string` | Your CloudCannon API key. Required if `userAccessKey` is not provided. |
|
|
57
|
+
| `userAccessKey` | `{ id: string, secret: string }` | User access key for HMAC-SHA256 request signing. Required if `key` is not provided. |
|
|
48
58
|
| `apiOrigin` | `string` | The API host. Defaults to `app.cloudcannon.com`. |
|
|
49
|
-
| `getCustomAuthHeaders` | `() => Record<string, string>` | Provide custom authentication headers instead of the default
|
|
59
|
+
| `getCustomAuthHeaders` | `() => Record<string, string>` | Provide custom authentication headers instead of the default auth headers. |
|
|
50
60
|
|
|
51
61
|
```typescript
|
|
62
|
+
// API key authentication
|
|
52
63
|
const client = new CloudCannonClient({
|
|
53
64
|
key: 'your-api-key-here',
|
|
54
65
|
apiOrigin: 'app.cloudcannon.com',
|
|
55
66
|
});
|
|
67
|
+
|
|
68
|
+
// User access key authentication
|
|
69
|
+
const client = new CloudCannonClient({
|
|
70
|
+
userAccessKey: {
|
|
71
|
+
id: 'your-access-key-id',
|
|
72
|
+
secret: 'your-access-key-secret',
|
|
73
|
+
},
|
|
74
|
+
apiOrigin: 'app.cloudcannon.com',
|
|
75
|
+
});
|
|
56
76
|
```
|
|
57
77
|
|
|
58
78
|
## Client functions
|
package/dist/index.d.ts
CHANGED
|
@@ -72,15 +72,24 @@ type MatchURL<M extends keyof paths[keyof paths], U extends string> = {
|
|
|
72
72
|
type ValidURL<M extends keyof paths[keyof paths], U extends string> = MatchURL<M, U> extends never ? {
|
|
73
73
|
[K in keyof paths]: RequestParams<paths[K][M]> extends never ? never : StripPrefix<K>;
|
|
74
74
|
}[keyof paths] : U;
|
|
75
|
-
|
|
76
|
-
key: string;
|
|
75
|
+
type BaseCloudCannonClientConfig = {
|
|
77
76
|
apiOrigin?: string;
|
|
78
77
|
getCustomAuthHeaders?: () => Record<string, string>;
|
|
79
78
|
};
|
|
79
|
+
export type CloudCannonClientConfig = ({
|
|
80
|
+
key: string;
|
|
81
|
+
} & BaseCloudCannonClientConfig) | ({
|
|
82
|
+
userAccessKey: UserAccessKey;
|
|
83
|
+
} & BaseCloudCannonClientConfig);
|
|
84
|
+
export type UserAccessKey = {
|
|
85
|
+
id: string;
|
|
86
|
+
secret: string;
|
|
87
|
+
};
|
|
80
88
|
export default class CloudCannonClient {
|
|
81
89
|
#private;
|
|
82
90
|
constructor(config: CloudCannonClientConfig);
|
|
83
|
-
getAuthHeaders(): Record<string, string
|
|
91
|
+
getAuthHeaders(url: string, body?: string): Promise<Record<string, string>>;
|
|
92
|
+
signRequest(userAccessKey: UserAccessKey, url: string, body?: string | null): Promise<Record<string, string>>;
|
|
84
93
|
fetch<const U extends string, const M extends Uppercase<keyof paths[keyof paths]> = 'GET'>(url: ValidURL<Lowercase<M>, U>, options?: Omit<RequestInit, keyof RequestMixin<M, MatchURL<Lowercase<M>, U>[Lowercase<M>]>> & RequestMixin<M, MatchURL<Lowercase<M>, U>[Lowercase<M>]>): Promise<APIResponse<MatchURL<Lowercase<M>, U>[Lowercase<M>]>>;
|
|
85
94
|
org(uuid: string): OrgClient;
|
|
86
95
|
site(uuid: string): SiteClient;
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { createHmac, randomUUID } from 'node:crypto';
|
|
1
2
|
import { BackupClient } from "./src/backup.js";
|
|
2
3
|
import { BuildClient } from "./src/build.js";
|
|
3
4
|
import { EditingSessionClient, } from "./src/editing-session.js";
|
|
@@ -10,41 +11,65 @@ import { SiteInboxClient } from "./src/site-inbox.js";
|
|
|
10
11
|
import { SyncClient } from "./src/sync.js";
|
|
11
12
|
export default class CloudCannonClient {
|
|
12
13
|
#apiKey;
|
|
14
|
+
#userAccessKey;
|
|
13
15
|
#appDomain;
|
|
14
16
|
#getCustomAuthHeaders;
|
|
15
17
|
constructor(config) {
|
|
16
|
-
|
|
18
|
+
if ('userAccessKey' in config) {
|
|
19
|
+
this.#userAccessKey = config.userAccessKey;
|
|
20
|
+
}
|
|
21
|
+
else {
|
|
22
|
+
this.#apiKey = config.key;
|
|
23
|
+
}
|
|
17
24
|
this.#appDomain = config.apiOrigin ?? 'app.cloudcannon.com';
|
|
18
25
|
this.#getCustomAuthHeaders = config.getCustomAuthHeaders;
|
|
19
26
|
}
|
|
20
|
-
getAuthHeaders() {
|
|
27
|
+
async getAuthHeaders(url, body) {
|
|
21
28
|
if (this.#getCustomAuthHeaders) {
|
|
22
|
-
return
|
|
23
|
-
|
|
24
|
-
|
|
29
|
+
return this.#getCustomAuthHeaders();
|
|
30
|
+
}
|
|
31
|
+
if (this.#userAccessKey) {
|
|
32
|
+
return this.signRequest(this.#userAccessKey, url, body);
|
|
25
33
|
}
|
|
26
34
|
return {
|
|
27
35
|
'X-API-KEY': `${this.#apiKey}`,
|
|
28
36
|
};
|
|
29
37
|
}
|
|
38
|
+
async signRequest(userAccessKey, url, body) {
|
|
39
|
+
const signedAtISO = new Date().toISOString();
|
|
40
|
+
const nonce = randomUUID();
|
|
41
|
+
const key = Buffer.from(userAccessKey.secret, 'base64');
|
|
42
|
+
const message = JSON.stringify({ url, signed_at: signedAtISO, body: body ?? '', nonce });
|
|
43
|
+
const digest = createHmac('sha256', key).update(message).digest('hex');
|
|
44
|
+
return {
|
|
45
|
+
'X-CC-ACCESS-KEY': userAccessKey.id,
|
|
46
|
+
'X-CC-SIGNED-AT': signedAtISO,
|
|
47
|
+
'X-CC-CHECKSUM': digest,
|
|
48
|
+
'X-CC-NONCE': nonce,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
30
51
|
async fetch(url, options) {
|
|
52
|
+
const fullUrl = `https://${this.#appDomain}/api/v0${url}`;
|
|
53
|
+
let body;
|
|
54
|
+
if (options?.body) {
|
|
55
|
+
if (typeof options?.body !== 'string') {
|
|
56
|
+
body = JSON.stringify(options.body);
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
body = options.body;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
const authHeaders = await this.getAuthHeaders(fullUrl, body);
|
|
31
63
|
const requestInit = {
|
|
32
64
|
...options,
|
|
33
65
|
headers: {
|
|
34
|
-
...
|
|
66
|
+
...authHeaders,
|
|
35
67
|
'Content-Type': 'application/json',
|
|
36
68
|
...options?.headers,
|
|
37
69
|
},
|
|
70
|
+
body,
|
|
38
71
|
};
|
|
39
|
-
|
|
40
|
-
if (typeof options?.body !== 'string') {
|
|
41
|
-
requestInit.body = JSON.stringify(options.body);
|
|
42
|
-
}
|
|
43
|
-
else {
|
|
44
|
-
requestInit.body = options.body;
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
return fetch(`https://${this.#appDomain}/api/v0${url}`, requestInit);
|
|
72
|
+
return fetch(fullUrl, requestInit);
|
|
48
73
|
}
|
|
49
74
|
org(uuid) {
|
|
50
75
|
return new OrgClient(uuid, this);
|
|
@@ -75,7 +100,7 @@ export default class CloudCannonClient {
|
|
|
75
100
|
}
|
|
76
101
|
async orgs(options = {}) {
|
|
77
102
|
const query = buildQuery(options);
|
|
78
|
-
const resp = await this.fetch(`/orgs
|
|
103
|
+
const resp = await this.fetch(`/orgs${query}`);
|
|
79
104
|
if (resp.status === 403) {
|
|
80
105
|
throw new Error('Error fetching orgs. Permission denied');
|
|
81
106
|
}
|
|
@@ -24,5 +24,5 @@ export declare function buildQuery(options?: PaginationOptions & {
|
|
|
24
24
|
sort_attribute?: string;
|
|
25
25
|
sort_direction?: 'ASC' | 'DESC';
|
|
26
26
|
filters?: Record<string, unknown>;
|
|
27
|
-
}): string;
|
|
27
|
+
}): `?${string}` | '';
|
|
28
28
|
export declare function paginatedResponse<T>(items: T[], headers: Headers): PaginatedResponse<T>;
|
|
@@ -15,7 +15,11 @@ export function buildQuery(options = {}) {
|
|
|
15
15
|
}
|
|
16
16
|
}
|
|
17
17
|
}
|
|
18
|
-
|
|
18
|
+
const result = params.toString();
|
|
19
|
+
if (result.length > 0) {
|
|
20
|
+
return `?${result}`;
|
|
21
|
+
}
|
|
22
|
+
return '';
|
|
19
23
|
}
|
|
20
24
|
export function paginatedResponse(items, headers) {
|
|
21
25
|
const parse = (name) => {
|
package/dist/src/inbox.js
CHANGED
|
@@ -8,7 +8,7 @@ export class InboxClient {
|
|
|
8
8
|
}
|
|
9
9
|
async getSubmissions(options = {}) {
|
|
10
10
|
const query = buildQuery(options);
|
|
11
|
-
const resp = await this.#client.fetch(`/inboxes/${this.#uuid}/form-hooks
|
|
11
|
+
const resp = await this.#client.fetch(`/inboxes/${this.#uuid}/form-hooks${query}`);
|
|
12
12
|
if (resp.status === 401 || resp.status === 403) {
|
|
13
13
|
throw new Error('Error fetching submissions. Permission denied');
|
|
14
14
|
}
|
package/dist/src/org.js
CHANGED
|
@@ -9,7 +9,7 @@ export class OrgClient {
|
|
|
9
9
|
}
|
|
10
10
|
async sites(options = {}) {
|
|
11
11
|
const query = buildQuery(options);
|
|
12
|
-
const resp = await this.#client.fetch(`/orgs/${this.#uuid}/sites
|
|
12
|
+
const resp = await this.#client.fetch(`/orgs/${this.#uuid}/sites${query}`);
|
|
13
13
|
if (resp.status === 401 || resp.status === 403) {
|
|
14
14
|
throw new Error('Error fetching sites. Permission denied');
|
|
15
15
|
}
|
|
@@ -67,7 +67,7 @@ export class OrgClient {
|
|
|
67
67
|
}
|
|
68
68
|
async getInboxes(options = {}) {
|
|
69
69
|
const query = buildQuery(options);
|
|
70
|
-
const resp = await this.#client.fetch(`/orgs/${this.#uuid}/inboxes
|
|
70
|
+
const resp = await this.#client.fetch(`/orgs/${this.#uuid}/inboxes${query}`);
|
|
71
71
|
if (resp.status === 403) {
|
|
72
72
|
throw new Error('Error fetching inboxes. Permission denied');
|
|
73
73
|
}
|
|
@@ -91,7 +91,7 @@ export class OrgClient {
|
|
|
91
91
|
}
|
|
92
92
|
async getDams(options = {}) {
|
|
93
93
|
const query = buildQuery(options);
|
|
94
|
-
const resp = await this.#client.fetch(`/orgs/${this.#uuid}/dams
|
|
94
|
+
const resp = await this.#client.fetch(`/orgs/${this.#uuid}/dams${query}`);
|
|
95
95
|
const dams = await resp.json();
|
|
96
96
|
return paginatedResponse(dams, resp.headers);
|
|
97
97
|
}
|
package/dist/src/site.js
CHANGED
|
@@ -73,7 +73,7 @@ export class SiteClient {
|
|
|
73
73
|
}
|
|
74
74
|
async getBuilds(options = {}) {
|
|
75
75
|
const query = buildQuery(options);
|
|
76
|
-
const resp = await this.#client.fetch(`/sites/${this.#uuid}/builds
|
|
76
|
+
const resp = await this.#client.fetch(`/sites/${this.#uuid}/builds${query}`);
|
|
77
77
|
if (resp.status === 401 || resp.status === 403) {
|
|
78
78
|
throw new Error('Error fetching builds. Permission denied');
|
|
79
79
|
}
|
|
@@ -90,7 +90,7 @@ export class SiteClient {
|
|
|
90
90
|
}
|
|
91
91
|
async listBackups(options = {}) {
|
|
92
92
|
const query = buildQuery(options);
|
|
93
|
-
const resp = await this.#client.fetch(`/sites/${this.#uuid}/archives
|
|
93
|
+
const resp = await this.#client.fetch(`/sites/${this.#uuid}/archives${query}`);
|
|
94
94
|
if (resp.status === 401) {
|
|
95
95
|
throw new Error('Error fetching backups. Permission denied');
|
|
96
96
|
}
|
|
@@ -140,7 +140,7 @@ export class SiteClient {
|
|
|
140
140
|
}
|
|
141
141
|
async getSyncs(options = {}) {
|
|
142
142
|
const query = buildQuery(options);
|
|
143
|
-
const resp = await this.#client.fetch(`/sites/${this.#uuid}/syncs
|
|
143
|
+
const resp = await this.#client.fetch(`/sites/${this.#uuid}/syncs${query}`);
|
|
144
144
|
if (resp.status === 401) {
|
|
145
145
|
throw new Error('Error fetching syncs. Permission denied');
|
|
146
146
|
}
|