@stackshift-cloud/sdk 1.3.1 → 1.4.1
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 +68 -0
- package/dist/asset-media-v2.d.ts +15 -1
- package/dist/asset-workflows.d.ts +5 -13
- package/dist/assets-dam-types.d.ts +35 -0
- package/dist/assets.d.ts +11 -2
- package/dist/assets.js +22 -0
- package/dist/client.d.ts +2 -0
- package/dist/client.js +5 -1
- package/dist/generated/assets-contract.d.ts +41 -6
- package/dist/generated/assets-contract.js +8 -1
- package/dist/index.d.ts +12 -3
- package/dist/index.js +7 -0
- package/dist/mail-billing.d.ts +12 -0
- package/dist/mail-billing.js +3 -0
- package/dist/mail-brands.d.ts +14 -1
- package/dist/mail-campaigns.d.ts +6 -3
- package/dist/mail-campaigns.js +6 -4
- package/dist/mail-delivery-types.d.ts +1 -0
- package/dist/mail-events-webhooks.d.ts +8 -3
- package/dist/mail-events-webhooks.js +13 -0
- package/dist/mail-mailboxes.d.ts +358 -0
- package/dist/mail-mailboxes.js +202 -0
- package/dist/mail-webhook-types.d.ts +40 -1
- package/dist/mail.d.ts +8 -0
- package/dist/mail.js +15 -0
- package/dist/partner.d.ts +369 -0
- package/dist/partner.js +70 -0
- package/dist/support-action-manifest.d.ts +3 -0
- package/dist/support-action-manifest.js +40 -0
- package/dist/support-action-receiver.d.ts +20 -0
- package/dist/support-action-receiver.js +37 -0
- package/dist/support-action-sqlite.d.ts +17 -0
- package/dist/support-action-sqlite.js +30 -0
- package/dist/support-action-types.d.ts +93 -0
- package/dist/support-action-types.js +1 -0
- package/dist/support-actions.d.ts +19 -0
- package/dist/support-actions.js +213 -0
- package/dist/types.d.ts +5 -0
- package/package.json +3 -2
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { SupportActionClaim, SupportActionDurableStore } from './support-action-receiver.js';
|
|
2
|
+
/** Compatible with node:sqlite DatabaseSync; supply a persistent disk database. */
|
|
3
|
+
export interface SupportActionSQLiteDatabase {
|
|
4
|
+
exec(sql: string): void;
|
|
5
|
+
prepare(sql: string): {
|
|
6
|
+
run(...values: any[]): {
|
|
7
|
+
changes: number | bigint;
|
|
8
|
+
};
|
|
9
|
+
get(...values: any[]): unknown;
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
export declare class SupportActionSQLiteStore implements SupportActionDurableStore {
|
|
13
|
+
private readonly database;
|
|
14
|
+
constructor(database: SupportActionSQLiteDatabase);
|
|
15
|
+
claim(namespace: 'delivery' | 'execution', key: string, digest: string): Promise<SupportActionClaim>;
|
|
16
|
+
complete(namespace: 'delivery' | 'execution', key: string, fence: string, response: Uint8Array, unknown?: boolean): Promise<void>;
|
|
17
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { actionCheck } from './support-actions.js';
|
|
2
|
+
export class SupportActionSQLiteStore {
|
|
3
|
+
database;
|
|
4
|
+
constructor(database) {
|
|
5
|
+
this.database = database;
|
|
6
|
+
database.exec('PRAGMA journal_mode=WAL; PRAGMA synchronous=FULL; PRAGMA busy_timeout=10000;');
|
|
7
|
+
database.exec(`CREATE TABLE IF NOT EXISTS support_action_claims (
|
|
8
|
+
namespace TEXT NOT NULL, claim_key TEXT NOT NULL, digest TEXT NOT NULL,
|
|
9
|
+
state TEXT NOT NULL, fence TEXT NOT NULL, response BLOB,
|
|
10
|
+
PRIMARY KEY(namespace, claim_key))`);
|
|
11
|
+
}
|
|
12
|
+
async claim(namespace, key, digest) {
|
|
13
|
+
const db = this.database, fence = crypto.randomUUID();
|
|
14
|
+
db.exec('BEGIN IMMEDIATE');
|
|
15
|
+
try {
|
|
16
|
+
const inserted = db.prepare('INSERT OR IGNORE INTO support_action_claims VALUES (?, ?, ?, ?, ?, NULL)').run(namespace, key, digest, 'running', fence);
|
|
17
|
+
const row = db.prepare('SELECT digest,state,fence,response FROM support_action_claims WHERE namespace=? AND claim_key=?').get(namespace, key);
|
|
18
|
+
db.exec('COMMIT');
|
|
19
|
+
return { state: inserted.changes ? 'acquired' : row.digest === digest ? row.state : 'conflict', fence: row.fence, ...(row.response ? { response: new Uint8Array(row.response) } : {}) };
|
|
20
|
+
}
|
|
21
|
+
catch (error) {
|
|
22
|
+
db.exec('ROLLBACK');
|
|
23
|
+
throw error;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
async complete(namespace, key, fence, response, unknown = false) {
|
|
27
|
+
const result = this.database.prepare('UPDATE support_action_claims SET state=?,response=? WHERE namespace=? AND claim_key=? AND fence=? AND state=?').run(unknown ? 'unknown' : 'completed', response, namespace, key, fence, 'running');
|
|
28
|
+
actionCheck(Number(result.changes) === 1, 'fence_lost');
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
export interface SupportActionField {
|
|
2
|
+
type: 'string' | 'number' | 'integer' | 'boolean';
|
|
3
|
+
required: boolean;
|
|
4
|
+
max_length?: number;
|
|
5
|
+
minimum?: number;
|
|
6
|
+
maximum?: number;
|
|
7
|
+
enum?: string[];
|
|
8
|
+
}
|
|
9
|
+
export interface SupportActionDescriptor {
|
|
10
|
+
name: string;
|
|
11
|
+
version: string;
|
|
12
|
+
endpoint: string;
|
|
13
|
+
reconciliation_endpoint?: string;
|
|
14
|
+
input: Record<string, SupportActionField>;
|
|
15
|
+
output: Record<string, SupportActionField>;
|
|
16
|
+
risk: 'read' | 'bounded_write' | 'customer_change' | 'financial' | 'security';
|
|
17
|
+
identity_required: true;
|
|
18
|
+
idempotency_supported: boolean;
|
|
19
|
+
safe_retry: boolean;
|
|
20
|
+
timeout_seconds: number;
|
|
21
|
+
verification_operation?: string;
|
|
22
|
+
}
|
|
23
|
+
export interface SupportActionManifest {
|
|
24
|
+
schema: 'stackshift.support.app/v2';
|
|
25
|
+
version: string;
|
|
26
|
+
permissions: string[];
|
|
27
|
+
event_types: string[];
|
|
28
|
+
placements: string[];
|
|
29
|
+
webhook_url: string;
|
|
30
|
+
description: string;
|
|
31
|
+
actions: SupportActionDescriptor[];
|
|
32
|
+
}
|
|
33
|
+
export interface SupportActionRequest {
|
|
34
|
+
schema: 'stackshift.support.action/v1';
|
|
35
|
+
request_id: string;
|
|
36
|
+
installation_id: string;
|
|
37
|
+
installation_revision: number;
|
|
38
|
+
app_version_id: string;
|
|
39
|
+
action: {
|
|
40
|
+
name: string;
|
|
41
|
+
version: string;
|
|
42
|
+
};
|
|
43
|
+
scope: {
|
|
44
|
+
workspace_id: string;
|
|
45
|
+
environment_id: string;
|
|
46
|
+
inbox_id: string;
|
|
47
|
+
conversation_id: string;
|
|
48
|
+
contact_id: string;
|
|
49
|
+
};
|
|
50
|
+
customer: {
|
|
51
|
+
external_id: string;
|
|
52
|
+
};
|
|
53
|
+
arguments: Record<string, string | number | boolean>;
|
|
54
|
+
authorization: {
|
|
55
|
+
grant_id?: string;
|
|
56
|
+
revision: number;
|
|
57
|
+
mode: 'automatic' | 'customer' | 'staff';
|
|
58
|
+
};
|
|
59
|
+
issued_at: number;
|
|
60
|
+
expires_at: number;
|
|
61
|
+
mode: 'execute' | 'reconcile';
|
|
62
|
+
target_request_id?: string;
|
|
63
|
+
}
|
|
64
|
+
export interface SupportActionReceipt {
|
|
65
|
+
schema: 'stackshift.support.action-receipt/v1';
|
|
66
|
+
request_id: string;
|
|
67
|
+
state: 'accepted' | 'succeeded' | 'failed' | 'unknown';
|
|
68
|
+
result: Record<string, unknown>;
|
|
69
|
+
error_code?: string;
|
|
70
|
+
}
|
|
71
|
+
export interface SupportActionVerification {
|
|
72
|
+
keyID: string;
|
|
73
|
+
secret: string;
|
|
74
|
+
installationID: string;
|
|
75
|
+
actions: SupportActionDescriptor[];
|
|
76
|
+
expectedCustomerExternalID: string;
|
|
77
|
+
now?: number;
|
|
78
|
+
tolerance?: number;
|
|
79
|
+
}
|
|
80
|
+
export interface SupportActionInstallationInput {
|
|
81
|
+
version_id: string;
|
|
82
|
+
environment_id: string;
|
|
83
|
+
inbox_id: string;
|
|
84
|
+
permissions: string[];
|
|
85
|
+
action_operations: string[];
|
|
86
|
+
confirm_consent: true;
|
|
87
|
+
revision: number;
|
|
88
|
+
}
|
|
89
|
+
export interface SupportActionInstallationCredentials {
|
|
90
|
+
action_key_id: string;
|
|
91
|
+
action_secret: string;
|
|
92
|
+
action_operations: string[];
|
|
93
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { SupportActionField, SupportActionReceipt, SupportActionRequest, SupportActionVerification } from './support-action-types.js';
|
|
2
|
+
export declare const ACTION_CONTEXT = "stackshift.support.action/v1";
|
|
3
|
+
export declare const ACTION_RECEIPT_CONTEXT = "stackshift.support.action-receipt/v1";
|
|
4
|
+
export declare class SupportActionError extends Error {
|
|
5
|
+
}
|
|
6
|
+
export declare function actionCheck(ok: unknown, code?: string): asserts ok;
|
|
7
|
+
export declare function signSupportAction(raw: Uint8Array, context: string, keyID: string, secret: string, timestamp: number, deliveryID: string): Promise<Record<string, string>>;
|
|
8
|
+
export declare function actionHeaders(headers: Record<string, string>): Record<string, string>;
|
|
9
|
+
export declare function validateSupportActionFields(value: unknown, fields: Record<string, SupportActionField>): void;
|
|
10
|
+
export declare function verifySupportAction(raw: Uint8Array, headers: Record<string, string>, options: SupportActionVerification): Promise<SupportActionRequest>;
|
|
11
|
+
export declare function encodeSupportActionReceipt(requestID: string, state: SupportActionReceipt['state'], result: Record<string, unknown>, errorCode?: string): Uint8Array<ArrayBuffer>;
|
|
12
|
+
export declare function verifySupportActionReceipt(raw: Uint8Array, headers: Record<string, string>, options: {
|
|
13
|
+
keyID: string;
|
|
14
|
+
secret: string;
|
|
15
|
+
requestID: string;
|
|
16
|
+
deliveryID: string;
|
|
17
|
+
now: number;
|
|
18
|
+
tolerance?: number;
|
|
19
|
+
}): Promise<SupportActionReceipt>;
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
export const ACTION_CONTEXT = 'stackshift.support.action/v1';
|
|
2
|
+
export const ACTION_RECEIPT_CONTEXT = 'stackshift.support.action-receipt/v1';
|
|
3
|
+
export class SupportActionError extends Error {
|
|
4
|
+
}
|
|
5
|
+
const encoder = new TextEncoder();
|
|
6
|
+
const uuidPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
|
|
7
|
+
export function actionCheck(ok, code = 'invalid_request') { if (!ok)
|
|
8
|
+
throw new SupportActionError(code); }
|
|
9
|
+
function uuid(value) { return typeof value === 'string' && uuidPattern.test(value) && value !== '00000000-0000-0000-0000-000000000000'; }
|
|
10
|
+
function integer(value) { return typeof value === 'number' && Number.isSafeInteger(value) && value >= 0; }
|
|
11
|
+
function object(value, required, optional = []) {
|
|
12
|
+
actionCheck(value !== null && typeof value === 'object' && !Array.isArray(value));
|
|
13
|
+
const keys = Object.keys(value);
|
|
14
|
+
actionCheck(required.every(k => keys.includes(k)) && keys.every(k => required.includes(k) || optional.includes(k)));
|
|
15
|
+
}
|
|
16
|
+
function joined(context, keyID, timestamp, deliveryID, raw) {
|
|
17
|
+
const prefix = encoder.encode(`${context}\n${keyID}\n${timestamp}\n${deliveryID}\n`);
|
|
18
|
+
const result = new Uint8Array(prefix.length + raw.length);
|
|
19
|
+
result.set(prefix);
|
|
20
|
+
result.set(raw, prefix.length);
|
|
21
|
+
return result;
|
|
22
|
+
}
|
|
23
|
+
export async function signSupportAction(raw, context, keyID, secret, timestamp, deliveryID) {
|
|
24
|
+
actionCheck([ACTION_CONTEXT, ACTION_RECEIPT_CONTEXT].includes(context) && /^[A-Za-z0-9_-]{1,128}$/.test(keyID) && uuid(deliveryID) && integer(timestamp) && timestamp > 0 && secret.length >= 32);
|
|
25
|
+
const key = await crypto.subtle.importKey('raw', encoder.encode(secret), { name: 'HMAC', hash: 'SHA-256' }, false, ['sign']);
|
|
26
|
+
const signature = await crypto.subtle.sign('HMAC', key, joined(context, keyID, String(timestamp), deliveryID, raw));
|
|
27
|
+
return { 'x-stackshift-action-key-id': keyID, 'x-stackshift-timestamp': String(timestamp), 'x-stackshift-delivery-id': deliveryID, 'x-stackshift-action-signature': `v1=${Array.from(new Uint8Array(signature), b => b.toString(16).padStart(2, '0')).join('')}` };
|
|
28
|
+
}
|
|
29
|
+
export function actionHeaders(headers) {
|
|
30
|
+
const result = Object.create(null);
|
|
31
|
+
for (const [name, value] of Object.entries(headers)) {
|
|
32
|
+
const lower = name.toLowerCase();
|
|
33
|
+
actionCheck(!(lower in result), 'invalid_headers');
|
|
34
|
+
result[lower] = value;
|
|
35
|
+
}
|
|
36
|
+
return result;
|
|
37
|
+
}
|
|
38
|
+
async function verifySignature(raw, headers, context, keyID, secret, now, tolerance, limit) {
|
|
39
|
+
actionCheck(raw instanceof Uint8Array && raw.length > 0 && raw.length <= limit, 'body_limit');
|
|
40
|
+
actionCheck(integer(now) && Number.isInteger(tolerance) && tolerance > 0 && tolerance <= 300);
|
|
41
|
+
const h = actionHeaders(headers), stamp = h['x-stackshift-timestamp'], delivery = h['x-stackshift-delivery-id'];
|
|
42
|
+
actionCheck(typeof stamp === 'string' && /^[1-9][0-9]{0,15}$/.test(stamp) && integer(Number(stamp)), 'invalid_headers');
|
|
43
|
+
actionCheck(h['x-stackshift-action-key-id'] === keyID && uuid(delivery) && /^[A-Za-z0-9_-]{1,128}$/.test(keyID) && secret.length >= 32, 'binding_mismatch');
|
|
44
|
+
actionCheck(Math.abs(now - Number(stamp)) <= tolerance, 'stale_signature');
|
|
45
|
+
const signature = h['x-stackshift-action-signature'];
|
|
46
|
+
actionCheck(typeof signature === 'string' && /^v1=[0-9a-f]{64}$/.test(signature), 'invalid_signature');
|
|
47
|
+
const bytes = Uint8Array.from(signature.slice(3).match(/../g), v => parseInt(v, 16));
|
|
48
|
+
const key = await crypto.subtle.importKey('raw', encoder.encode(secret), { name: 'HMAC', hash: 'SHA-256' }, false, ['verify']);
|
|
49
|
+
actionCheck(await crypto.subtle.verify('HMAC', key, bytes, joined(context, keyID, stamp, delivery, raw)), 'invalid_signature');
|
|
50
|
+
return delivery;
|
|
51
|
+
}
|
|
52
|
+
// Reject duplicate keys before JSON.parse; ordinary JSON.parse silently overwrites them.
|
|
53
|
+
function decode(raw) {
|
|
54
|
+
let text;
|
|
55
|
+
try {
|
|
56
|
+
text = new TextDecoder('utf-8', { fatal: true }).decode(raw);
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
throw new SupportActionError('invalid_request');
|
|
60
|
+
}
|
|
61
|
+
let i = 0;
|
|
62
|
+
function space() { while (/\s/.test(text[i] ?? '') && i < text.length)
|
|
63
|
+
i++; }
|
|
64
|
+
function string() { const start = i++; while (i < text.length) {
|
|
65
|
+
if (text[i++] === '\\') {
|
|
66
|
+
i++;
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
if (text[i - 1] === '"')
|
|
70
|
+
return JSON.parse(text.slice(start, i));
|
|
71
|
+
} throw new SupportActionError('invalid_request'); }
|
|
72
|
+
function value(depth) {
|
|
73
|
+
actionCheck(depth <= 32);
|
|
74
|
+
space();
|
|
75
|
+
if (text[i] === '{') {
|
|
76
|
+
i++;
|
|
77
|
+
space();
|
|
78
|
+
const keys = new Set();
|
|
79
|
+
if (text[i] === '}') {
|
|
80
|
+
i++;
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
while (i < text.length) {
|
|
84
|
+
space();
|
|
85
|
+
actionCheck(text[i] === '"');
|
|
86
|
+
const key = string();
|
|
87
|
+
actionCheck(!keys.has(key));
|
|
88
|
+
keys.add(key);
|
|
89
|
+
space();
|
|
90
|
+
actionCheck(text[i++] === ':');
|
|
91
|
+
value(depth + 1);
|
|
92
|
+
space();
|
|
93
|
+
const next = text[i++];
|
|
94
|
+
if (next === '}')
|
|
95
|
+
return;
|
|
96
|
+
actionCheck(next === ',');
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
else if (text[i] === '[') {
|
|
100
|
+
i++;
|
|
101
|
+
space();
|
|
102
|
+
if (text[i] === ']') {
|
|
103
|
+
i++;
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
;
|
|
107
|
+
while (i < text.length) {
|
|
108
|
+
value(depth + 1);
|
|
109
|
+
space();
|
|
110
|
+
const next = text[i++];
|
|
111
|
+
if (next === ']')
|
|
112
|
+
return;
|
|
113
|
+
actionCheck(next === ',');
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
else if (text[i] === '"') {
|
|
117
|
+
string();
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
else {
|
|
121
|
+
const start = i;
|
|
122
|
+
while (i < text.length && !/[\s,\]}]/.test(text[i]))
|
|
123
|
+
i++;
|
|
124
|
+
actionCheck(i > start);
|
|
125
|
+
JSON.parse(text.slice(start, i));
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
throw new SupportActionError('invalid_request');
|
|
129
|
+
}
|
|
130
|
+
try {
|
|
131
|
+
value(0);
|
|
132
|
+
space();
|
|
133
|
+
actionCheck(i === text.length);
|
|
134
|
+
return JSON.parse(text);
|
|
135
|
+
}
|
|
136
|
+
catch {
|
|
137
|
+
throw new SupportActionError('invalid_request');
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
export function validateSupportActionFields(value, fields) {
|
|
141
|
+
actionCheck(value !== null && typeof value === 'object' && !Array.isArray(value));
|
|
142
|
+
const data = value;
|
|
143
|
+
actionCheck(Object.keys(data).every(k => Object.hasOwn(fields, k)));
|
|
144
|
+
for (const [name, field] of Object.entries(fields)) {
|
|
145
|
+
if (!Object.hasOwn(data, name)) {
|
|
146
|
+
actionCheck(!field.required);
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
const item = data[name];
|
|
150
|
+
actionCheck((field.type === 'string' && typeof item === 'string') || (field.type === 'boolean' && typeof item === 'boolean') || (field.type === 'integer' && typeof item === 'number' && Number.isSafeInteger(item)) || (field.type === 'number' && typeof item === 'number' && Number.isFinite(item)));
|
|
151
|
+
if (typeof item === 'string')
|
|
152
|
+
actionCheck([...item].length <= (field.max_length ?? 16000) && (!field.enum?.length || field.enum.includes(item)));
|
|
153
|
+
if (typeof item === 'number')
|
|
154
|
+
actionCheck(item >= (field.minimum ?? -Infinity) && item <= (field.maximum ?? Infinity));
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
export async function verifySupportAction(raw, headers, options) {
|
|
158
|
+
const now = options.now ?? Math.floor(Date.now() / 1000), tolerance = options.tolerance ?? 300;
|
|
159
|
+
await verifySignature(raw, headers, ACTION_CONTEXT, options.keyID, options.secret, now, tolerance, 256 * 1024);
|
|
160
|
+
const body = decode(raw);
|
|
161
|
+
object(body, ['schema', 'request_id', 'installation_id', 'installation_revision', 'app_version_id', 'action', 'scope', 'customer', 'arguments', 'authorization', 'issued_at', 'expires_at', 'mode'], ['target_request_id']);
|
|
162
|
+
actionCheck(body.schema === ACTION_CONTEXT && body.installation_id === options.installationID, 'binding_mismatch');
|
|
163
|
+
actionCheck(['request_id', 'installation_id', 'app_version_id'].every(k => uuid(body[k])) && integer(body.installation_revision) && body.installation_revision > 0);
|
|
164
|
+
object(body.action, ['name', 'version']);
|
|
165
|
+
object(body.scope, ['workspace_id', 'environment_id', 'inbox_id', 'conversation_id', 'contact_id']);
|
|
166
|
+
actionCheck(Object.values(body.scope).every(uuid));
|
|
167
|
+
object(body.customer, ['external_id']);
|
|
168
|
+
actionCheck(options.expectedCustomerExternalID.length > 0 && [...options.expectedCustomerExternalID].length <= 256 && body.customer.external_id === options.expectedCustomerExternalID, 'binding_mismatch');
|
|
169
|
+
object(body.authorization, ['revision', 'mode'], ['grant_id']);
|
|
170
|
+
const auth = body.authorization;
|
|
171
|
+
actionCheck(integer(auth.revision) && auth.revision > 0 && ['automatic', 'customer', 'staff'].includes(auth.mode) && (!Object.hasOwn(auth, 'grant_id') || uuid(auth.grant_id)));
|
|
172
|
+
actionCheck(integer(body.issued_at) && integer(body.expires_at) && body.issued_at <= now + tolerance && body.expires_at > now && body.expires_at > body.issued_at, 'request_expired');
|
|
173
|
+
const action = options.actions.find(a => a.name === body.action.name && a.version === body.action.version);
|
|
174
|
+
actionCheck(action, 'action_not_registered');
|
|
175
|
+
if (body.mode === 'reconcile') {
|
|
176
|
+
actionCheck(uuid(body.target_request_id) && body.target_request_id !== body.request_id);
|
|
177
|
+
object(body.arguments, []);
|
|
178
|
+
}
|
|
179
|
+
else {
|
|
180
|
+
actionCheck(body.mode === 'execute' && !Object.hasOwn(body, 'target_request_id'));
|
|
181
|
+
validateSupportActionFields(body.arguments, action.input);
|
|
182
|
+
}
|
|
183
|
+
return body;
|
|
184
|
+
}
|
|
185
|
+
function validateReceipt(body) {
|
|
186
|
+
object(body, ['schema', 'request_id', 'state', 'result'], ['error_code']);
|
|
187
|
+
actionCheck(body.schema === ACTION_RECEIPT_CONTEXT && uuid(body.request_id) && ['accepted', 'succeeded', 'failed', 'unknown'].includes(body.state));
|
|
188
|
+
actionCheck(body.result !== null && typeof body.result === 'object' && !Array.isArray(body.result));
|
|
189
|
+
actionCheck(body.state === 'succeeded' || Object.keys(body.result).length === 0);
|
|
190
|
+
actionCheck(!Object.hasOwn(body, 'error_code') || (typeof body.error_code === 'string' && /^[a-z][a-z0-9_]{0,79}$/.test(body.error_code)));
|
|
191
|
+
}
|
|
192
|
+
export function encodeSupportActionReceipt(requestID, state, result, errorCode) {
|
|
193
|
+
const body = { schema: ACTION_RECEIPT_CONTEXT, request_id: requestID, state, result, ...(errorCode !== undefined ? { error_code: errorCode } : {}) };
|
|
194
|
+
validateReceipt(body);
|
|
195
|
+
let serialized;
|
|
196
|
+
try {
|
|
197
|
+
serialized = JSON.stringify(body, (_key, value) => { actionCheck(value !== undefined && typeof value !== 'function' && typeof value !== 'symbol' && (typeof value !== 'number' || Number.isFinite(value))); return value; });
|
|
198
|
+
}
|
|
199
|
+
catch {
|
|
200
|
+
throw new SupportActionError('invalid_request');
|
|
201
|
+
}
|
|
202
|
+
const raw = encoder.encode(serialized);
|
|
203
|
+
actionCheck(raw.length <= 64 * 1024, 'body_limit');
|
|
204
|
+
decode(raw);
|
|
205
|
+
return raw;
|
|
206
|
+
}
|
|
207
|
+
export async function verifySupportActionReceipt(raw, headers, options) {
|
|
208
|
+
const delivery = await verifySignature(raw, headers, ACTION_RECEIPT_CONTEXT, options.keyID, options.secret, options.now, options.tolerance ?? 300, 64 * 1024);
|
|
209
|
+
const body = decode(raw);
|
|
210
|
+
validateReceipt(body);
|
|
211
|
+
actionCheck(delivery === options.deliveryID && body.request_id === options.requestID, 'binding_mismatch');
|
|
212
|
+
return body;
|
|
213
|
+
}
|
package/dist/types.d.ts
CHANGED
|
@@ -256,6 +256,11 @@ export interface AssetAIAnnotation {
|
|
|
256
256
|
colors: string[];
|
|
257
257
|
raw?: JsonValue;
|
|
258
258
|
}
|
|
259
|
+
export interface AssetManualDescriptionInput {
|
|
260
|
+
caption?: string;
|
|
261
|
+
alt_text?: string;
|
|
262
|
+
tags?: string[];
|
|
263
|
+
}
|
|
259
264
|
export interface AssetModerationResult {
|
|
260
265
|
id: string;
|
|
261
266
|
provider: string;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stackshift-cloud/sdk",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.1",
|
|
4
4
|
"description": "Official JavaScript and TypeScript SDK for StackShift.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -17,7 +17,8 @@
|
|
|
17
17
|
],
|
|
18
18
|
"scripts": {
|
|
19
19
|
"build": "tsc -p tsconfig.json",
|
|
20
|
-
"test": "npm run build && node test/mail.test.mjs && node test/mail-simulation.test.mjs && node test/mail-platform.test.mjs && node test/mail-webhooks.test.mjs && node test/mail-advanced.test.mjs && node test/mail-tracking.test.mjs && node test/mail-subscribers.test.mjs && node test/mail-exports.test.mjs && node test/mail-diagnostics.test.mjs && node test/assets.test.mjs && node test/assets-platform.test.mjs && node test/assets-video.test.mjs && node test/assets-dam.test.mjs && node test/ai-credits.test.mjs && node --test test/s2-upload.test.mjs"
|
|
20
|
+
"test": "npm run build && node test/mail.test.mjs && node test/mail-simulation.test.mjs && node test/mail-platform.test.mjs && node test/mail-webhooks.test.mjs && node test/mail-advanced.test.mjs && node test/mail-tracking.test.mjs && node test/mail-subscribers.test.mjs && node test/mail-exports.test.mjs && node test/mail-mailboxes.test.mjs && node test/mail-diagnostics.test.mjs && node test/partner.test.mjs && node test/assets.test.mjs && node test/assets-platform.test.mjs && node test/assets-video.test.mjs && node test/assets-dam.test.mjs && node test/ai-credits.test.mjs && node --test test/s2-upload.test.mjs",
|
|
21
|
+
"test:support-actions": "npm run build && node --test tests/support-actions.test.mjs"
|
|
21
22
|
},
|
|
22
23
|
"devDependencies": {
|
|
23
24
|
"typescript": "^5.4.0"
|