Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion plugins/kapso/skills/integrate-whatsapp/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ Env vars:
- `KAPSO_API_BASE_URL` (host only, no `/platform/v1`)
- `KAPSO_API_KEY`
- `META_GRAPH_VERSION` (optional, default `v24.0`)
- `KAPSO_HTTP_TIMEOUT_MS` (optional, default `30000`) - aborts a slow/hung Meta or Platform call with a clear timeout error instead of spinning forever. Increase it for large template/flow syncs over slow links.

Auth header (direct API calls):
```
Expand Down Expand Up @@ -281,9 +282,11 @@ async function handler(request, env) {

### Troubleshooting

- Preview shows `"flow_token is missing"`: flow is dynamic without a data endpoint. Attach one and refresh.
- Preview shows `"flow_token is missing"` or renders blank with a preview-URL warning: flow is dynamic (`data_api_version` set / `routing_model` present) but has no working data endpoint. Attach and register one (see "Attach a data endpoint"), then refresh. Confirm it is live with `node scripts/get-data-endpoint.js --flow-id <id>`.
- Encryption setup errors: enable encryption in Settings for the phone number/WABA.
- OAuthException 139000 (Integrity): WABA must be verified in Meta security center.
- `list-flows.js` / `list-templates.mjs` returns nothing where you expect data: confirm you are scoping by the same `phone_number_id` / `business_account_id` (WABA) the flow or template was created under — listings are scoped, so a mismatched ID returns an empty list rather than an error.
- A flow/template sync or listing hangs: requests now abort after `KAPSO_HTTP_TIMEOUT_MS` (default 30s) and return a `408` timeout error (`timedOut: true`) instead of spinning forever. Retry, or raise the timeout for large syncs.

## Scripts

Expand Down
41 changes: 36 additions & 5 deletions plugins/kapso/skills/integrate-whatsapp/scripts/lib/http.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
const { getConfig } = require('./env');

const DEFAULT_TIMEOUT_MS = 30000;

class RequestError extends Error {
constructor(message, status, body) {
super(message);
Expand All @@ -8,6 +10,16 @@ class RequestError extends Error {
}
}

// Resolve the per-request timeout. Without a bound, a slow or hung Meta/Platform
// call leaves the script spinning forever with no feedback. Override with
// KAPSO_HTTP_TIMEOUT_MS (milliseconds); 0 or invalid falls back to the default.
function getTimeoutMs() {
const raw = process.env.KAPSO_HTTP_TIMEOUT_MS;
if (!raw) return DEFAULT_TIMEOUT_MS;
const parsed = Number(raw);
return Number.isFinite(parsed) && parsed > 0 ? parsed : DEFAULT_TIMEOUT_MS;
}

function buildUrl(baseUrl, path, query) {
const trimmed = baseUrl.replace(/\/+$/, '');
const safePath = path.startsWith('/') ? path : `/${path}`;
Expand Down Expand Up @@ -56,11 +68,30 @@ async function request({ baseUrl, path, method, query, body, headers }) {
}
}

const response = await fetch(url, {
method,
headers: finalHeaders,
body: finalBody
});
const timeoutMs = getTimeoutMs();
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), timeoutMs);

let response;
try {
response = await fetch(url, {
method,
headers: finalHeaders,
body: finalBody,
signal: controller.signal
});
} catch (error) {
if (error && error.name === 'AbortError') {
throw new RequestError(
`Request timed out after ${timeoutMs}ms (set KAPSO_HTTP_TIMEOUT_MS to adjust)`,
408,
{ timedOut: true, url }
);
}
throw error;
} finally {
clearTimeout(timer);
}

const contentType = response.headers.get('content-type') || '';
const text = await response.text();
Expand Down
45 changes: 40 additions & 5 deletions plugins/kapso/skills/integrate-whatsapp/scripts/lib/request.mjs
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
import { metaProxyConfig } from './env.mjs';

const DEFAULT_TIMEOUT_MS = 30000;

// Resolve the per-request timeout. Without a bound, a slow or hung Meta call
// leaves the script spinning forever with no feedback. Override with
// KAPSO_HTTP_TIMEOUT_MS (milliseconds); 0 or invalid falls back to the default.
function getTimeoutMs() {
const raw = process.env.KAPSO_HTTP_TIMEOUT_MS;
if (!raw) return DEFAULT_TIMEOUT_MS;
const parsed = Number(raw);
return Number.isFinite(parsed) && parsed > 0 ? parsed : DEFAULT_TIMEOUT_MS;
}

function buildUrl(path, query) {
const { baseUrl, graphVersion } = metaProxyConfig();
const cleanedPath = path.replace(/^\/+/, '');
Expand Down Expand Up @@ -30,11 +42,34 @@ export async function metaProxyRequest({ method, path, query, headers, body }) {
finalHeaders.set('Content-Type', 'application/json');
}

const response = await fetch(url, {
method,
headers: finalHeaders,
body
});
const timeoutMs = getTimeoutMs();
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), timeoutMs);

let response;
try {
response = await fetch(url, {
method,
headers: finalHeaders,
body,
signal: controller.signal
});
} catch (error) {
if (error && error.name === 'AbortError') {
return {
ok: false,
status: 408,
url: url.toString(),
timedOut: true,
data: {
error: `Request timed out after ${timeoutMs}ms (set KAPSO_HTTP_TIMEOUT_MS to adjust)`
}
};
}
throw error;
} finally {
clearTimeout(timer);
}

const contentType = response.headers.get('content-type') || '';
const text = await response.text();
Expand Down
Loading