viasocket-apps 0.6.0 → 0.7.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/README.md +42 -17
- package/dist/cjs/client.js +200 -10
- package/dist/cjs/http.js +3 -1
- package/dist/cjs/index.js +4 -1
- package/dist/esm/client.d.ts +58 -4
- package/dist/esm/client.js +199 -9
- package/dist/esm/http.d.ts +2 -0
- package/dist/esm/http.js +3 -1
- package/dist/esm/index.d.ts +4 -2
- package/dist/esm/index.js +3 -1
- package/dist/esm/types.d.ts +26 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -31,11 +31,12 @@ Add `-g` to install it for every project. No install? Paste this into your agent
|
|
|
31
31
|
`Read https://flow.viasocket.com/documentation/skill.md and follow it.`
|
|
32
32
|
|
|
33
33
|
The agent needs three values from the viaSocket dashboard (**Integrations → Create embed →
|
|
34
|
-
Install Code**): `org_id` and `project_id`, which
|
|
35
|
-
goes in your server's `.env`.
|
|
36
|
-
[skills/viasocket-integrations/SKILL.md](skills/viasocket-integrations/SKILL.md)
|
|
37
|
-
|
|
38
|
-
`https://flow.viasocket.com/documentation/skill.md
|
|
34
|
+
Install Code**): `org_id` and `project_id`, which the dashboard's "Copy prompt" hands it, and
|
|
35
|
+
`VIASOCKET_EMBED_SECRET`, which goes in your server's `.env`.
|
|
36
|
+
[skills/viasocket-integrations/SKILL.md](skills/viasocket-integrations/SKILL.md) is the install
|
|
37
|
+
file: the description that makes an agent load the skill, and one instruction — fetch the current
|
|
38
|
+
skill, `https://flow.viasocket.com/documentation/skill.md`, every time it is used — so an install
|
|
39
|
+
never goes stale.
|
|
39
40
|
|
|
40
41
|
Before you ship — with or without an agent — check the code against the live catalog:
|
|
41
42
|
|
|
@@ -66,7 +67,14 @@ const user = viasocket.user('user_123')
|
|
|
66
67
|
// 1. Your frontend needs a token to open the connect popup.
|
|
67
68
|
const embedToken = await user.token()
|
|
68
69
|
|
|
69
|
-
//
|
|
70
|
+
// The app by its slug, and the event by its action_id and the version your code was written for —
|
|
71
|
+
// both read from the live catalog, never copied ids. Each event's page lists them:
|
|
72
|
+
// https://flow.viasocket.com/documentation/google-sheets.md
|
|
73
|
+
const serviceId = await viasocket.catalog.serviceIdFor('google-sheets')
|
|
74
|
+
const actionVersionId = await viasocket.catalog.versionIdFor(serviceId, '<action_id>', '<version>')
|
|
75
|
+
|
|
76
|
+
// 2. After the popup posts back an auth_id: enable the app, once, to run actions. A paused app is
|
|
77
|
+
// found too, and resumed with enableFlow rather than enabled twice.
|
|
70
78
|
const scriptId = (await user.findEnabled(serviceId)) ?? (await user.enable(serviceId, authId)).scriptId
|
|
71
79
|
|
|
72
80
|
// 3. Fill a dropdown with the user's real data.
|
|
@@ -165,9 +173,27 @@ The whole contract — tools, JSON answers, the websocket path and what arrives
|
|
|
165
173
|
| `viasocket.runAction(scriptId, actionVersionId, inputData)` | Run one action. Returns the app's own response. |
|
|
166
174
|
| `viasocket.catalog.search(key)` | The best 30 apps for what the user typed, as `{ serviceId, name, iconUrl, description }`. No token. |
|
|
167
175
|
| `viasocket.catalog.list({ limit, offset, category })` | Every app, most used first, 200 a page; an empty page ends it. Drop apps whose `versions()` is `null` or empty. No token. |
|
|
168
|
-
| `viasocket.catalog.versions(serviceId)` | One app and every published action and trigger of it, as `{ service, actions, triggers }`. Each action has `action_id` and `action_version_id`, each trigger `trigger_id` and `trigger_version_id`, named as the calls take them: the version id for `runAction`, `listOptions` and `subscribe`; the other for `connect`, automations and the prebuilt UI. `input_schema` is the form schema. `null` for an unknown id. No token. |
|
|
176
|
+
| `viasocket.catalog.versions(serviceId)` | One app and every published action and trigger of it, as `{ service, actions, triggers }`. Each action has `action_id` and `action_version_id`, each trigger `trigger_id` and `trigger_version_id`, named as the calls take them: the version id for `runAction`, `listOptions` and `subscribe`; the other for `connect`, automations and the prebuilt UI. `versionid` is the published version (`V107`). `input_schema` is the form schema. `null` for an unknown id. No token. |
|
|
177
|
+
| `viasocket.catalog.serviceIdFor(slug)` | The `service_id` of the app with this slug (`slack`, `google-sheets`). Exact and case-sensitive; an unknown slug throws. Cached. No token. |
|
|
178
|
+
| `viasocket.catalog.versionIdFor(serviceId, event, version?)` | The version id the calls take, for one event: `event` is its `action_id` / `trigger_id` (survives a rename) or its page path (`send-message`). When the app has published a version other than `version`, it warns and returns the published one. Cached 10 minutes. No token. |
|
|
179
|
+
| `viasocket.catalog.listBuiltIn()` | The built-in plugs (Run AI Model, Pause Workflow, Memory, scraping, GST…): every plug tagged `SDK`, with its actions and whether it needs a sign-in. No token. |
|
|
180
|
+
| `viasocket.sampleEvent(triggerId, authId, authType, inputData?)` | One real event of a trigger for one connection: what a handler reads at `context.req.body`. Takes the `trigger_id`; `authType` is `service.auth_type`. Map from this, never from `sample_output`. |
|
|
169
181
|
| `user.subscribe(triggerVersionId, { authId, inputData, code \| webhook, meta })` | Subscribe to an app event with a handler we run per event. Returns `{ scriptId, hookUrl, … }`. |
|
|
170
182
|
| `user.updateSubscription(scriptId, { code, meta })` | Change a live subscription in place. |
|
|
183
|
+
| `user.listFlows()` | Every enabled app, subscription and automation this user has, each `active`, `paused` or `deleted`. |
|
|
184
|
+
| `user.setFlowStatus(scriptId, n)` | Pause (`2`), resume or restore (`1`), or delete (`0`) one flow. Setting the state it is already in succeeds. |
|
|
185
|
+
| `user.disableFlow(scriptId)` / `user.enableFlow(scriptId)` / `user.deleteFlow(scriptId)` | Pause, resume, delete. |
|
|
186
|
+
| `user.renameFlow(scriptId, { title, description, meta })` | Rename a flow; `meta` comes back as `metadata` in the prebuilt UI's events. |
|
|
187
|
+
| `user.listConnections()` | Every app this user has connected; `isExpired: true` means reconnect it in place with `connect({ authId })`. |
|
|
188
|
+
| `user.revokeConnection(authId)` | Disconnect an app. Pause its flows first. |
|
|
189
|
+
| `user.ai.send({ message, threadId, prompt, tools, responseType, delivery })` | One message to the assistant we run for this user. Returns `{ threadId, messageId, content, finishReason }`, or `{ threadId, messageId, channel }` with `delivery: 'rtlayer'`. |
|
|
190
|
+
| `user.ai.threads()` / `user.ai.history(threadId, { page })` | This user's threads, newest first (`title` is `null` until the AI writes it); one thread's messages, 40 exchanges a page. |
|
|
191
|
+
| `user.ai.rtlayerToken()` | A 48-hour subscribe token for the browser, with the `channelPrefix` every thread's channel starts with. |
|
|
192
|
+
| `signEmbedToken({ orgId, projectId, uniqueIdentifier, secret })` | The signing step on its own, if you want it without a client. |
|
|
193
|
+
|
|
194
|
+
**Upgrading from 0.6:** `disableFlow` now pauses a flow (status `2`). In 0.6 it sent status `0`,
|
|
195
|
+
which deletes — a deleted flow is still listed as `deleted` and `enableFlow` restores it.
|
|
196
|
+
`findEnabled` now also returns a paused app.
|
|
171
197
|
|
|
172
198
|
## Events
|
|
173
199
|
|
|
@@ -189,10 +215,17 @@ const handler = `
|
|
|
189
215
|
headers: { "Content-Type": "application/json" },
|
|
190
216
|
body: JSON.stringify({
|
|
191
217
|
action_version_id: "${slackSendMessageVersionId}",
|
|
192
|
-
|
|
218
|
+
// Slack Send Message V107's inputData; the keys of event come from a real sampleEvent
|
|
219
|
+
inputData: {
|
|
220
|
+
destination: { messageto: "channel", channel_id: ["${channelPickedAtSetup}"] },
|
|
221
|
+
markdown_content: "New mail from " + event.from + ": " + event.subject
|
|
222
|
+
}
|
|
193
223
|
})
|
|
194
224
|
})
|
|
195
|
-
|
|
225
|
+
const result = await response.json()
|
|
226
|
+
// Envelope { success, data }, or the action's own body: throw so the run history shows the failure.
|
|
227
|
+
if ("data" in result ? !result.success : result.success === false) throw new Error(result.message || "action failed")
|
|
228
|
+
return result
|
|
196
229
|
`
|
|
197
230
|
|
|
198
231
|
await user.subscribe(gmailNewMailVersionId, { authId, inputData: {}, code: handler, meta: { user_id: uniqueIdentifier } })
|
|
@@ -203,14 +236,6 @@ Use it only when that is what you want, and never both.
|
|
|
203
236
|
|
|
204
237
|
Some triggers are polled rather than pushed. For those, `inputData` may carry `scheduledTime`, the
|
|
205
238
|
minutes between checks, as a string: `"5"` or `"15"`.
|
|
206
|
-
| `user.listFlows()` | Every enabled app and subscription this user has. |
|
|
207
|
-
| `user.disableFlow(scriptId)` / `user.enableFlow(scriptId)` | Turn a flow off or back on. Disabling a subscription ends it. |
|
|
208
|
-
| `user.listConnections()` | Every app this user has connected. |
|
|
209
|
-
| `user.revokeConnection(authId)` | Disconnect an app. Disable its flows first. |
|
|
210
|
-
| `user.ai.send({ message, threadId, prompt, tools, responseType, delivery })` | One message to the assistant we run for this user. Returns `{ threadId, messageId, content, finishReason }`, or `{ threadId, messageId, channel }` with `delivery: 'rtlayer'`. |
|
|
211
|
-
| `user.ai.threads()` / `user.ai.history(threadId, { page })` | This user's threads, newest first (`title` is `null` until the AI writes it); one thread's messages, 40 exchanges a page. |
|
|
212
|
-
| `user.ai.rtlayerToken()` | A 48-hour subscribe token for the browser, with the `channelPrefix` every thread's channel starts with. |
|
|
213
|
-
| `signEmbedToken({ orgId, projectId, uniqueIdentifier, secret })` | The signing step on its own, if you want it without a client. |
|
|
214
239
|
|
|
215
240
|
Every failure — a non-2xx status or a body with `success: false` — throws a `ViaSocketError`
|
|
216
241
|
carrying the API's own `message`, the HTTP `status`, and the parsed `body`.
|
package/dist/cjs/client.js
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* reader has to remember.
|
|
10
10
|
*/
|
|
11
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
-
exports.UserScope = exports.ViaSocket = void 0;
|
|
12
|
+
exports.UserScope = exports.ViaSocket = exports.BUILT_IN_PLUG_CATEGORIES = void 0;
|
|
13
13
|
const errors_js_1 = require("./errors.js");
|
|
14
14
|
const http_js_1 = require("./http.js");
|
|
15
15
|
const token_js_1 = require("./token.js");
|
|
@@ -22,9 +22,25 @@ const DEFAULT_RUN_BASE_URL = 'https://flow.sokt.io';
|
|
|
22
22
|
const CATALOG = {
|
|
23
23
|
search: 'https://flow.sokt.io/func/scri12BSufQM',
|
|
24
24
|
list: 'https://plug-service.viasocket.com/api/v1/plugins/all',
|
|
25
|
-
versions: 'https://flow.sokt.io/func/scriK4LFg2kc'
|
|
25
|
+
versions: 'https://flow.sokt.io/func/scriK4LFg2kc',
|
|
26
|
+
slug: 'https://plug-service.viasocket.com/api/v1/plugins/recommend/services'
|
|
26
27
|
};
|
|
27
28
|
const LIST_LIMIT_MAX = 200;
|
|
29
|
+
/** The run-host function that fetches one real event of a trigger for one connection. */
|
|
30
|
+
const SAMPLE_EVENT_FUNCTION = 'scrijZpnr8zJ';
|
|
31
|
+
/** Plugs tagged with these categories in Developer Hub are the built-ins `catalog.listBuiltIn()` returns. */
|
|
32
|
+
exports.BUILT_IN_PLUG_CATEGORIES = ['SDK'];
|
|
33
|
+
/** The list answers an empty page for a category with `limit=200`; stay under it. */
|
|
34
|
+
const BUILT_IN_PAGE = 100;
|
|
35
|
+
/** An unknown slug is answered by nothing, slowly; a known one in well under a second. */
|
|
36
|
+
const SLUG_TIMEOUT_MS = 30_000;
|
|
37
|
+
/** How long `versionIdFor` trusts its copy of an app's catalog. */
|
|
38
|
+
const VERSIONS_TTL_MS = 10 * 60_000;
|
|
39
|
+
/** `Send Message` → `send-message`: an event's path on its documentation page. */
|
|
40
|
+
const eventPath = (name) => `${name || ''}`
|
|
41
|
+
.toLowerCase()
|
|
42
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
43
|
+
.replace(/^-+|-+$/g, '');
|
|
28
44
|
function required(name, value) {
|
|
29
45
|
if (typeof value !== 'string' || !value.trim())
|
|
30
46
|
throw new errors_js_1.ViaSocketError(`${name} is required`, { status: null });
|
|
@@ -39,6 +55,10 @@ class ViaSocket {
|
|
|
39
55
|
secret;
|
|
40
56
|
/** @internal */
|
|
41
57
|
transport;
|
|
58
|
+
/** slug → service_id. Slugs never change their app, so for the life of the process. */
|
|
59
|
+
serviceIds = new Map();
|
|
60
|
+
/** service_id → its catalog, for `versionIdFor`; a failed read is dropped, never kept. */
|
|
61
|
+
versionsCache = new Map;
|
|
42
62
|
constructor(options) {
|
|
43
63
|
this.orgId = required('orgId', options?.orgId);
|
|
44
64
|
this.projectId = required('projectId', options?.projectId);
|
|
@@ -93,10 +113,77 @@ class ViaSocket {
|
|
|
93
113
|
name: String(row.name ?? ''),
|
|
94
114
|
description: String(row.description ?? ''),
|
|
95
115
|
iconUrl: String(row.iconurl ?? ''),
|
|
116
|
+
...(typeof row.appslugname === 'string' && row.appslugname ? { slug: row.appslugname } : {}),
|
|
96
117
|
category: Array.isArray(row.category) ? row.category : [],
|
|
97
118
|
domain: typeof row.domain === 'string' ? row.domain : undefined
|
|
98
119
|
}));
|
|
99
120
|
},
|
|
121
|
+
/**
|
|
122
|
+
* The `service_id` of the app with this slug (`slack`, `google-sheets`) — how code names a fixed
|
|
123
|
+
* app. Exact and case-sensitive; an unknown slug is a ViaSocketError, never a guess (it can take
|
|
124
|
+
* up to 30 s to be told so). Cached for the life of the process.
|
|
125
|
+
*/
|
|
126
|
+
serviceIdFor: (slug) => {
|
|
127
|
+
const key = required('slug', slug);
|
|
128
|
+
let found = this.serviceIds.get(key);
|
|
129
|
+
if (!found) {
|
|
130
|
+
found = this.resolveSlug(key);
|
|
131
|
+
this.serviceIds.set(key, found);
|
|
132
|
+
found.catch(() => this.serviceIds.delete(key));
|
|
133
|
+
}
|
|
134
|
+
return found;
|
|
135
|
+
},
|
|
136
|
+
/**
|
|
137
|
+
* The version id `runAction`, `listOptions` and `subscribe` take, for one event of an app, read
|
|
138
|
+
* from the live catalog. `event` is the event's `action_id` / `trigger_id` (it survives a rename)
|
|
139
|
+
* or its path on the app's page (`send-message`). `version` is the one your code was written
|
|
140
|
+
* against (`V107`): when the app has published another, this warns and returns the published one —
|
|
141
|
+
* reread the event's page and update the code. Cached 10 minutes per app.
|
|
142
|
+
*/
|
|
143
|
+
versionIdFor: async (serviceId, event, version) => {
|
|
144
|
+
const wanted = required('event', event).trim();
|
|
145
|
+
const app = await this.cachedVersions(required('serviceId', serviceId));
|
|
146
|
+
if (!app)
|
|
147
|
+
throw new errors_js_1.ViaSocketError(`versionIdFor: no app has the service id "${serviceId}"`, { status: null });
|
|
148
|
+
// Each event with the version id the calls take, whichever kind it is.
|
|
149
|
+
const all = [
|
|
150
|
+
...app.actions.map((e) => ({ id: e.action_id, versionId: e.action_version_id, name: e.name, versionid: e.versionid })),
|
|
151
|
+
...app.triggers.map((e) => ({ id: e.trigger_id, versionId: e.trigger_version_id, name: e.name, versionid: e.versionid }))
|
|
152
|
+
];
|
|
153
|
+
const byId = all.filter((e) => e.id === wanted);
|
|
154
|
+
const found = byId.length ? byId : all.filter((e) => eventPath(e.name) === wanted);
|
|
155
|
+
if (found.length !== 1) {
|
|
156
|
+
throw new errors_js_1.ViaSocketError(`versionIdFor: ${app.service.name} has ${found.length ? 'several events' : 'no published event'} "${wanted}" — use its action_id / trigger_id from the app's page`, { status: null });
|
|
157
|
+
}
|
|
158
|
+
const [match] = found;
|
|
159
|
+
if (version && match.versionid && match.versionid !== version) {
|
|
160
|
+
console.warn(`viaSocket: ${app.service.name} "${wanted}" is published as ${match.versionid}, not ${version}: reread its page and update the code`);
|
|
161
|
+
}
|
|
162
|
+
return match.versionId;
|
|
163
|
+
},
|
|
164
|
+
/**
|
|
165
|
+
* The built-in plugs — Run AI Model, Pause Workflow, Memory, scraping, GST and more: every plug
|
|
166
|
+
* tagged `SDK` in Developer Hub, with its published actions. Live, so a newly tagged plug appears
|
|
167
|
+
* without a release.
|
|
168
|
+
*/
|
|
169
|
+
listBuiltIn: async () => {
|
|
170
|
+
const pages = await Promise.all(exports.BUILT_IN_PLUG_CATEGORIES.map((category) => this.catalog.list({ category, limit: BUILT_IN_PAGE })));
|
|
171
|
+
const apps = [...new Map(pages.flat().map((app) => [app.serviceId, app])).values()].filter((app) => app.serviceId);
|
|
172
|
+
const withActions = await Promise.all(apps.map(async (app) => {
|
|
173
|
+
const versions = await this.catalog.versions(app.serviceId);
|
|
174
|
+
if (!versions?.actions.length)
|
|
175
|
+
return null;
|
|
176
|
+
return {
|
|
177
|
+
serviceId: app.serviceId,
|
|
178
|
+
slug: app.slug ?? '',
|
|
179
|
+
name: versions.service.name || app.name,
|
|
180
|
+
iconUrl: versions.service.icon_url || app.iconUrl,
|
|
181
|
+
requiresAuth: versions.service.requires_auth !== false && `${versions.service.auth_type ?? ''}`.toLowerCase() !== 'noauth',
|
|
182
|
+
actions: versions.actions
|
|
183
|
+
};
|
|
184
|
+
}));
|
|
185
|
+
return withActions.filter((app) => app !== null).sort((a, b) => a.name.localeCompare(b.name));
|
|
186
|
+
},
|
|
100
187
|
/**
|
|
101
188
|
* One app and every published action and trigger of it, as `{ service, actions, triggers }`.
|
|
102
189
|
* Each action carries `action_id` and `action_version_id`, each trigger `trigger_id` and
|
|
@@ -154,6 +241,78 @@ class ViaSocket {
|
|
|
154
241
|
});
|
|
155
242
|
return payload.data;
|
|
156
243
|
}
|
|
244
|
+
/**
|
|
245
|
+
* One real event of a trigger for one connection: exactly what a handler reads at
|
|
246
|
+
* `context.req.body`, and what an automation's steps read there. Map from it, never from the
|
|
247
|
+
* catalog's `sample_output`, which the connector's author recorded on another account. It takes
|
|
248
|
+
* the `trigger_id` (a version id answers "No response"); `authType` is the app's
|
|
249
|
+
* `service.auth_type` from `catalog.versions()`; `inputData` is the trigger's own settings, the
|
|
250
|
+
* same the subscription will carry. No token, but the `auth_id` is a credential: server only.
|
|
251
|
+
*/
|
|
252
|
+
async sampleEvent(triggerId, authId, authType, inputData = {}) {
|
|
253
|
+
const query = new URLSearchParams({
|
|
254
|
+
action_id: required('triggerId', triggerId),
|
|
255
|
+
auth_id: required('authId', authId),
|
|
256
|
+
authType: required('authType', authType)
|
|
257
|
+
});
|
|
258
|
+
const { data } = await (0, http_js_1.request)({
|
|
259
|
+
method: 'POST',
|
|
260
|
+
url: `${this.runBaseUrl}/func/${SAMPLE_EVENT_FUNCTION}?${query}`,
|
|
261
|
+
body: JSON.stringify({ context: { inputData: inputData ?? {} } }),
|
|
262
|
+
fetchImpl: this.transport
|
|
263
|
+
});
|
|
264
|
+
// An unreadable connection (revoked, another user's, the wrong auth_id) answers 200 with the
|
|
265
|
+
// failure nested inside, as list-options does.
|
|
266
|
+
const nested = data?.response;
|
|
267
|
+
if (nested && Number(nested.status) >= 400) {
|
|
268
|
+
throw new errors_js_1.ViaSocketError(`sampleEvent: ${nested.data?.message || 'the connection could not be read'}`, {
|
|
269
|
+
status: Number(nested.status),
|
|
270
|
+
body: data
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
if (!data || typeof data !== 'object' || Array.isArray(data) || data.message === 'No response') {
|
|
274
|
+
throw new errors_js_1.ViaSocketError(`sampleEvent: no event came back (${JSON.stringify(data)?.slice(0, 120)}) — pass the trigger_id, not its version id, and a live auth_id`, { status: null, body: data });
|
|
275
|
+
}
|
|
276
|
+
// `viasocket_help` is a note for the developer, not a field of the event.
|
|
277
|
+
const { viasocket_help: _help, ...event } = data;
|
|
278
|
+
return event;
|
|
279
|
+
}
|
|
280
|
+
async resolveSlug(slug) {
|
|
281
|
+
let data;
|
|
282
|
+
try {
|
|
283
|
+
;
|
|
284
|
+
({ data } = await (0, http_js_1.request)({
|
|
285
|
+
method: 'GET',
|
|
286
|
+
url: `${CATALOG.slug}?service=${encodeURIComponent(slug)}`,
|
|
287
|
+
fetchImpl: this.transport,
|
|
288
|
+
signal: AbortSignal.timeout(SLUG_TIMEOUT_MS)
|
|
289
|
+
}));
|
|
290
|
+
}
|
|
291
|
+
catch (error) {
|
|
292
|
+
if (error?.name !== 'TimeoutError')
|
|
293
|
+
throw error;
|
|
294
|
+
}
|
|
295
|
+
const serviceId = data?.data?.plugins?.[slug]?.rowid;
|
|
296
|
+
if (!serviceId) {
|
|
297
|
+
throw new errors_js_1.ViaSocketError(`No viaSocket app has the slug "${slug}" — slugs are exact and case-sensitive; find it with catalog.list()`, {
|
|
298
|
+
status: null
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
return serviceId;
|
|
302
|
+
}
|
|
303
|
+
cachedVersions(serviceId) {
|
|
304
|
+
const cached = this.versionsCache.get(serviceId);
|
|
305
|
+
if (cached && Date.now() - cached.at < VERSIONS_TTL_MS)
|
|
306
|
+
return cached.value;
|
|
307
|
+
const value = this.catalog.versions(serviceId);
|
|
308
|
+
this.versionsCache.set(serviceId, { at: Date.now(), value });
|
|
309
|
+
// Neither a failure nor an empty answer is kept: one bad read must not block runs for ten minutes.
|
|
310
|
+
value.then((app) => {
|
|
311
|
+
if (!app)
|
|
312
|
+
this.versionsCache.delete(serviceId);
|
|
313
|
+
}, () => this.versionsCache.delete(serviceId));
|
|
314
|
+
return value;
|
|
315
|
+
}
|
|
157
316
|
}
|
|
158
317
|
exports.ViaSocket = ViaSocket;
|
|
159
318
|
/**
|
|
@@ -269,25 +428,56 @@ class UserScope {
|
|
|
269
428
|
}));
|
|
270
429
|
}
|
|
271
430
|
/**
|
|
272
|
-
* The script_id of an app this user already enabled
|
|
273
|
-
* enabling twice leaves you with two script_ids for one app.
|
|
431
|
+
* The script_id of an app this user already enabled — active or paused — or null. Call it before
|
|
432
|
+
* `enable()`: enabling twice leaves you with two script_ids for one app. A paused one is resumed
|
|
433
|
+
* with `enableFlow()`, never enabled again.
|
|
274
434
|
*/
|
|
275
435
|
async findEnabled(serviceId) {
|
|
276
436
|
const flows = await this.listFlows();
|
|
277
|
-
|
|
437
|
+
const wanted = required('serviceId', serviceId);
|
|
438
|
+
return flows.find((flow) => flow.serviceId === wanted && (flow.status === 'active' || flow.status === 'paused'))?.id ?? null;
|
|
278
439
|
}
|
|
279
|
-
/**
|
|
440
|
+
/**
|
|
441
|
+
* Pauses (2), resumes or restores (1), or deletes (0) one flow: an enabled app, a trigger
|
|
442
|
+
* subscription or an automation. A deleted flow is listed as `deleted` and can be restored with 1.
|
|
443
|
+
*/
|
|
280
444
|
async setFlowStatus(scriptId, status) {
|
|
281
|
-
const
|
|
282
|
-
|
|
283
|
-
|
|
445
|
+
const id = required('scriptId', scriptId);
|
|
446
|
+
try {
|
|
447
|
+
const { data } = await this.call('PUT', `/embed/updatestatus/${id}?status=${status}`);
|
|
448
|
+
// The response is the whole flow record; nothing else in it is anything a caller acts on.
|
|
449
|
+
return { id: data.id, status: String(data.status) };
|
|
450
|
+
}
|
|
451
|
+
catch (error) {
|
|
452
|
+
// Already in that state: what the caller asked for is true, so it is not a failure.
|
|
453
|
+
if (error instanceof errors_js_1.ViaSocketError && /already set/i.test(error.message))
|
|
454
|
+
return { id, status: String(status) };
|
|
455
|
+
throw error;
|
|
456
|
+
}
|
|
284
457
|
}
|
|
458
|
+
/** Pauses a flow (status 2). Before 0.7.0 this sent 0, which deletes. */
|
|
285
459
|
disableFlow(scriptId) {
|
|
286
|
-
return this.setFlowStatus(scriptId,
|
|
460
|
+
return this.setFlowStatus(scriptId, 2);
|
|
287
461
|
}
|
|
462
|
+
/** Resumes a paused flow, or restores a deleted one (status 1). */
|
|
288
463
|
enableFlow(scriptId) {
|
|
289
464
|
return this.setFlowStatus(scriptId, 1);
|
|
290
465
|
}
|
|
466
|
+
/** Deletes a flow (status 0). It stays listed as `deleted`; `enableFlow` restores it. */
|
|
467
|
+
deleteFlow(scriptId) {
|
|
468
|
+
return this.setFlowStatus(scriptId, 0);
|
|
469
|
+
}
|
|
470
|
+
/** Renames a flow; `meta` comes back as `metadata` in the prebuilt UI's flow events. */
|
|
471
|
+
async renameFlow(scriptId, params) {
|
|
472
|
+
const body = { title: required('title', params?.title) };
|
|
473
|
+
if (params.description !== undefined)
|
|
474
|
+
body.description = params.description;
|
|
475
|
+
if (params.meta !== undefined)
|
|
476
|
+
body.meta = params.meta;
|
|
477
|
+
const { data } = await this.call('PUT', `/projects/updateflowembed/${required('scriptId', scriptId)}`, body);
|
|
478
|
+
// The response is the whole script, code included; only these are anything a caller acts on.
|
|
479
|
+
return { id: data.id, title: data.title, description: data.description ?? '' };
|
|
480
|
+
}
|
|
291
481
|
/**
|
|
292
482
|
* The AI API: a chat assistant viaSocket runs for this user — threads and history kept per
|
|
293
483
|
* `unique_identifier`, your prompt per call, your own endpoints as its tools. Backend only, like
|
package/dist/cjs/http.js
CHANGED
|
@@ -23,9 +23,11 @@ async function request(options) {
|
|
|
23
23
|
headers.authorization = options.token;
|
|
24
24
|
let response;
|
|
25
25
|
try {
|
|
26
|
-
response = await options.fetchImpl(options.url, { method: options.method, headers, body: options.body });
|
|
26
|
+
response = await options.fetchImpl(options.url, { method: options.method, headers, body: options.body, signal: options.signal });
|
|
27
27
|
}
|
|
28
28
|
catch (error) {
|
|
29
|
+
if (error?.name === 'TimeoutError' || error?.name === 'AbortError')
|
|
30
|
+
throw error;
|
|
29
31
|
throw new errors_js_1.ViaSocketError(`Request to ${options.url} failed: ${error?.message || 'network error'}`, { status: null });
|
|
30
32
|
}
|
|
31
33
|
const text = await response.text();
|
package/dist/cjs/index.js
CHANGED
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
* const user = viasocket.user(uniqueIdentifier)
|
|
9
9
|
*
|
|
10
10
|
* const token = await user.token() // for the connect popup
|
|
11
|
+
* const serviceId = await viasocket.catalog.serviceIdFor('slack') // an app, by its slug
|
|
12
|
+
* const actionVersionId = await viasocket.catalog.versionIdFor(serviceId, 'row1jrbor', 'V107')
|
|
11
13
|
* const { scriptId } = await user.enable(serviceId, authId) // once, for actions
|
|
12
14
|
* const { options } = await user.listOptions(actionVersionId, {...}) // fill a dropdown
|
|
13
15
|
* const result = await viasocket.runAction(scriptId, actionVersionId, inputData)
|
|
@@ -17,10 +19,11 @@
|
|
|
17
19
|
* The browser half — opening the connect popup — lives at 'viasocket-apps/browser'.
|
|
18
20
|
*/
|
|
19
21
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
20
|
-
exports.signEmbedToken = exports.ViaSocketError = exports.UserScope = exports.ViaSocket = void 0;
|
|
22
|
+
exports.signEmbedToken = exports.ViaSocketError = exports.BUILT_IN_PLUG_CATEGORIES = exports.UserScope = exports.ViaSocket = void 0;
|
|
21
23
|
var client_js_1 = require("./client.js");
|
|
22
24
|
Object.defineProperty(exports, "ViaSocket", { enumerable: true, get: function () { return client_js_1.ViaSocket; } });
|
|
23
25
|
Object.defineProperty(exports, "UserScope", { enumerable: true, get: function () { return client_js_1.UserScope; } });
|
|
26
|
+
Object.defineProperty(exports, "BUILT_IN_PLUG_CATEGORIES", { enumerable: true, get: function () { return client_js_1.BUILT_IN_PLUG_CATEGORIES; } });
|
|
24
27
|
var errors_js_1 = require("./errors.js");
|
|
25
28
|
Object.defineProperty(exports, "ViaSocketError", { enumerable: true, get: function () { return errors_js_1.ViaSocketError; } });
|
|
26
29
|
var token_js_1 = require("./token.js");
|
package/dist/esm/client.d.ts
CHANGED
|
@@ -7,13 +7,19 @@
|
|
|
7
7
|
* everything else behind `user(id)` makes that distinction structural rather than something a
|
|
8
8
|
* reader has to remember.
|
|
9
9
|
*/
|
|
10
|
-
import type { AiHistoryPage, AiRtLayerToken, AiSend, AiThread, CatalogApp, CatalogVersions, EnableResult, Flow, FlowStatus, ListOptionsParams, ListAppsParams, ListOptionsResult, SubscribeParams, SubscribeResult, UpdateSubscriptionParams, ViaSocketOptions } from './types.js';
|
|
10
|
+
import type { AiHistoryPage, AiRtLayerToken, AiSend, AiThread, BuiltInApp, CatalogApp, CatalogVersions, EnableResult, Flow, FlowStatus, ListOptionsParams, ListAppsParams, ListOptionsResult, RenameFlowParams, SubscribeParams, SubscribeResult, UpdateSubscriptionParams, ViaSocketOptions } from './types.js';
|
|
11
|
+
/** Plugs tagged with these categories in Developer Hub are the built-ins `catalog.listBuiltIn()` returns. */
|
|
12
|
+
export declare const BUILT_IN_PLUG_CATEGORIES: readonly ["SDK"];
|
|
11
13
|
export declare class ViaSocket {
|
|
12
14
|
readonly orgId: string;
|
|
13
15
|
readonly projectId: string;
|
|
14
16
|
readonly apiBaseUrl: string;
|
|
15
17
|
readonly runBaseUrl: string;
|
|
16
18
|
private readonly secret;
|
|
19
|
+
/** slug → service_id. Slugs never change their app, so for the life of the process. */
|
|
20
|
+
private readonly serviceIds;
|
|
21
|
+
/** service_id → its catalog, for `versionIdFor`; a failed read is dropped, never kept. */
|
|
22
|
+
private readonly versionsCache;
|
|
17
23
|
constructor(options: ViaSocketOptions);
|
|
18
24
|
/**
|
|
19
25
|
* The catalog: which apps exist and what each can do. Public, no token, nothing per user — so
|
|
@@ -28,6 +34,26 @@ export declare class ViaSocket {
|
|
|
28
34
|
* app whose `versions()` comes back empty.
|
|
29
35
|
*/
|
|
30
36
|
list: (params?: ListAppsParams) => Promise<CatalogApp[]>;
|
|
37
|
+
/**
|
|
38
|
+
* The `service_id` of the app with this slug (`slack`, `google-sheets`) — how code names a fixed
|
|
39
|
+
* app. Exact and case-sensitive; an unknown slug is a ViaSocketError, never a guess (it can take
|
|
40
|
+
* up to 30 s to be told so). Cached for the life of the process.
|
|
41
|
+
*/
|
|
42
|
+
serviceIdFor: (slug: string) => Promise<string>;
|
|
43
|
+
/**
|
|
44
|
+
* The version id `runAction`, `listOptions` and `subscribe` take, for one event of an app, read
|
|
45
|
+
* from the live catalog. `event` is the event's `action_id` / `trigger_id` (it survives a rename)
|
|
46
|
+
* or its path on the app's page (`send-message`). `version` is the one your code was written
|
|
47
|
+
* against (`V107`): when the app has published another, this warns and returns the published one —
|
|
48
|
+
* reread the event's page and update the code. Cached 10 minutes per app.
|
|
49
|
+
*/
|
|
50
|
+
versionIdFor: (serviceId: string, event: string, version?: string) => Promise<string>;
|
|
51
|
+
/**
|
|
52
|
+
* The built-in plugs — Run AI Model, Pause Workflow, Memory, scraping, GST and more: every plug
|
|
53
|
+
* tagged `SDK` in Developer Hub, with its published actions. Live, so a newly tagged plug appears
|
|
54
|
+
* without a release.
|
|
55
|
+
*/
|
|
56
|
+
listBuiltIn: () => Promise<BuiltInApp[]>;
|
|
31
57
|
/**
|
|
32
58
|
* One app and every published action and trigger of it, as `{ service, actions, triggers }`.
|
|
33
59
|
* Each action carries `action_id` and `action_version_id`, each trigger `trigger_id` and
|
|
@@ -51,6 +77,17 @@ export declare class ViaSocket {
|
|
|
51
77
|
* exactly why this call belongs on your server.
|
|
52
78
|
*/
|
|
53
79
|
runAction<T = unknown>(scriptId: string, actionVersionId: string, inputData: Record<string, unknown>): Promise<T>;
|
|
80
|
+
/**
|
|
81
|
+
* One real event of a trigger for one connection: exactly what a handler reads at
|
|
82
|
+
* `context.req.body`, and what an automation's steps read there. Map from it, never from the
|
|
83
|
+
* catalog's `sample_output`, which the connector's author recorded on another account. It takes
|
|
84
|
+
* the `trigger_id` (a version id answers "No response"); `authType` is the app's
|
|
85
|
+
* `service.auth_type` from `catalog.versions()`; `inputData` is the trigger's own settings, the
|
|
86
|
+
* same the subscription will carry. No token, but the `auth_id` is a credential: server only.
|
|
87
|
+
*/
|
|
88
|
+
sampleEvent<T = Record<string, unknown>>(triggerId: string, authId: string, authType: string, inputData?: Record<string, unknown>): Promise<T>;
|
|
89
|
+
private resolveSlug;
|
|
90
|
+
private cachedVersions;
|
|
54
91
|
}
|
|
55
92
|
/**
|
|
56
93
|
* Calls made on behalf of one end user. Obtained from `viasocket.user(id)`.
|
|
@@ -91,23 +128,40 @@ export declare class UserScope {
|
|
|
91
128
|
/** Every flow this user has: one per enabled app, one per trigger subscription. */
|
|
92
129
|
listFlows(): Promise<Flow[]>;
|
|
93
130
|
/**
|
|
94
|
-
* The script_id of an app this user already enabled
|
|
95
|
-
* enabling twice leaves you with two script_ids for one app.
|
|
131
|
+
* The script_id of an app this user already enabled — active or paused — or null. Call it before
|
|
132
|
+
* `enable()`: enabling twice leaves you with two script_ids for one app. A paused one is resumed
|
|
133
|
+
* with `enableFlow()`, never enabled again.
|
|
96
134
|
*/
|
|
97
135
|
findEnabled(serviceId: string): Promise<string | null>;
|
|
98
|
-
/**
|
|
136
|
+
/**
|
|
137
|
+
* Pauses (2), resumes or restores (1), or deletes (0) one flow: an enabled app, a trigger
|
|
138
|
+
* subscription or an automation. A deleted flow is listed as `deleted` and can be restored with 1.
|
|
139
|
+
*/
|
|
99
140
|
setFlowStatus(scriptId: string, status: FlowStatus): Promise<{
|
|
100
141
|
id: string;
|
|
101
142
|
status: string;
|
|
102
143
|
}>;
|
|
144
|
+
/** Pauses a flow (status 2). Before 0.7.0 this sent 0, which deletes. */
|
|
103
145
|
disableFlow(scriptId: string): Promise<{
|
|
104
146
|
id: string;
|
|
105
147
|
status: string;
|
|
106
148
|
}>;
|
|
149
|
+
/** Resumes a paused flow, or restores a deleted one (status 1). */
|
|
107
150
|
enableFlow(scriptId: string): Promise<{
|
|
108
151
|
id: string;
|
|
109
152
|
status: string;
|
|
110
153
|
}>;
|
|
154
|
+
/** Deletes a flow (status 0). It stays listed as `deleted`; `enableFlow` restores it. */
|
|
155
|
+
deleteFlow(scriptId: string): Promise<{
|
|
156
|
+
id: string;
|
|
157
|
+
status: string;
|
|
158
|
+
}>;
|
|
159
|
+
/** Renames a flow; `meta` comes back as `metadata` in the prebuilt UI's flow events. */
|
|
160
|
+
renameFlow(scriptId: string, params: RenameFlowParams): Promise<{
|
|
161
|
+
id: string;
|
|
162
|
+
title: string;
|
|
163
|
+
description: string;
|
|
164
|
+
}>;
|
|
111
165
|
/**
|
|
112
166
|
* The AI API: a chat assistant viaSocket runs for this user — threads and history kept per
|
|
113
167
|
* `unique_identifier`, your prompt per call, your own endpoints as its tools. Backend only, like
|
package/dist/esm/client.js
CHANGED
|
@@ -19,9 +19,25 @@ const DEFAULT_RUN_BASE_URL = 'https://flow.sokt.io';
|
|
|
19
19
|
const CATALOG = {
|
|
20
20
|
search: 'https://flow.sokt.io/func/scri12BSufQM',
|
|
21
21
|
list: 'https://plug-service.viasocket.com/api/v1/plugins/all',
|
|
22
|
-
versions: 'https://flow.sokt.io/func/scriK4LFg2kc'
|
|
22
|
+
versions: 'https://flow.sokt.io/func/scriK4LFg2kc',
|
|
23
|
+
slug: 'https://plug-service.viasocket.com/api/v1/plugins/recommend/services'
|
|
23
24
|
};
|
|
24
25
|
const LIST_LIMIT_MAX = 200;
|
|
26
|
+
/** The run-host function that fetches one real event of a trigger for one connection. */
|
|
27
|
+
const SAMPLE_EVENT_FUNCTION = 'scrijZpnr8zJ';
|
|
28
|
+
/** Plugs tagged with these categories in Developer Hub are the built-ins `catalog.listBuiltIn()` returns. */
|
|
29
|
+
export const BUILT_IN_PLUG_CATEGORIES = ['SDK'];
|
|
30
|
+
/** The list answers an empty page for a category with `limit=200`; stay under it. */
|
|
31
|
+
const BUILT_IN_PAGE = 100;
|
|
32
|
+
/** An unknown slug is answered by nothing, slowly; a known one in well under a second. */
|
|
33
|
+
const SLUG_TIMEOUT_MS = 30_000;
|
|
34
|
+
/** How long `versionIdFor` trusts its copy of an app's catalog. */
|
|
35
|
+
const VERSIONS_TTL_MS = 10 * 60_000;
|
|
36
|
+
/** `Send Message` → `send-message`: an event's path on its documentation page. */
|
|
37
|
+
const eventPath = (name) => `${name || ''}`
|
|
38
|
+
.toLowerCase()
|
|
39
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
40
|
+
.replace(/^-+|-+$/g, '');
|
|
25
41
|
function required(name, value) {
|
|
26
42
|
if (typeof value !== 'string' || !value.trim())
|
|
27
43
|
throw new ViaSocketError(`${name} is required`, { status: null });
|
|
@@ -36,6 +52,10 @@ export class ViaSocket {
|
|
|
36
52
|
secret;
|
|
37
53
|
/** @internal */
|
|
38
54
|
transport;
|
|
55
|
+
/** slug → service_id. Slugs never change their app, so for the life of the process. */
|
|
56
|
+
serviceIds = new Map();
|
|
57
|
+
/** service_id → its catalog, for `versionIdFor`; a failed read is dropped, never kept. */
|
|
58
|
+
versionsCache = new Map;
|
|
39
59
|
constructor(options) {
|
|
40
60
|
this.orgId = required('orgId', options?.orgId);
|
|
41
61
|
this.projectId = required('projectId', options?.projectId);
|
|
@@ -90,10 +110,77 @@ export class ViaSocket {
|
|
|
90
110
|
name: String(row.name ?? ''),
|
|
91
111
|
description: String(row.description ?? ''),
|
|
92
112
|
iconUrl: String(row.iconurl ?? ''),
|
|
113
|
+
...(typeof row.appslugname === 'string' && row.appslugname ? { slug: row.appslugname } : {}),
|
|
93
114
|
category: Array.isArray(row.category) ? row.category : [],
|
|
94
115
|
domain: typeof row.domain === 'string' ? row.domain : undefined
|
|
95
116
|
}));
|
|
96
117
|
},
|
|
118
|
+
/**
|
|
119
|
+
* The `service_id` of the app with this slug (`slack`, `google-sheets`) — how code names a fixed
|
|
120
|
+
* app. Exact and case-sensitive; an unknown slug is a ViaSocketError, never a guess (it can take
|
|
121
|
+
* up to 30 s to be told so). Cached for the life of the process.
|
|
122
|
+
*/
|
|
123
|
+
serviceIdFor: (slug) => {
|
|
124
|
+
const key = required('slug', slug);
|
|
125
|
+
let found = this.serviceIds.get(key);
|
|
126
|
+
if (!found) {
|
|
127
|
+
found = this.resolveSlug(key);
|
|
128
|
+
this.serviceIds.set(key, found);
|
|
129
|
+
found.catch(() => this.serviceIds.delete(key));
|
|
130
|
+
}
|
|
131
|
+
return found;
|
|
132
|
+
},
|
|
133
|
+
/**
|
|
134
|
+
* The version id `runAction`, `listOptions` and `subscribe` take, for one event of an app, read
|
|
135
|
+
* from the live catalog. `event` is the event's `action_id` / `trigger_id` (it survives a rename)
|
|
136
|
+
* or its path on the app's page (`send-message`). `version` is the one your code was written
|
|
137
|
+
* against (`V107`): when the app has published another, this warns and returns the published one —
|
|
138
|
+
* reread the event's page and update the code. Cached 10 minutes per app.
|
|
139
|
+
*/
|
|
140
|
+
versionIdFor: async (serviceId, event, version) => {
|
|
141
|
+
const wanted = required('event', event).trim();
|
|
142
|
+
const app = await this.cachedVersions(required('serviceId', serviceId));
|
|
143
|
+
if (!app)
|
|
144
|
+
throw new ViaSocketError(`versionIdFor: no app has the service id "${serviceId}"`, { status: null });
|
|
145
|
+
// Each event with the version id the calls take, whichever kind it is.
|
|
146
|
+
const all = [
|
|
147
|
+
...app.actions.map((e) => ({ id: e.action_id, versionId: e.action_version_id, name: e.name, versionid: e.versionid })),
|
|
148
|
+
...app.triggers.map((e) => ({ id: e.trigger_id, versionId: e.trigger_version_id, name: e.name, versionid: e.versionid }))
|
|
149
|
+
];
|
|
150
|
+
const byId = all.filter((e) => e.id === wanted);
|
|
151
|
+
const found = byId.length ? byId : all.filter((e) => eventPath(e.name) === wanted);
|
|
152
|
+
if (found.length !== 1) {
|
|
153
|
+
throw new ViaSocketError(`versionIdFor: ${app.service.name} has ${found.length ? 'several events' : 'no published event'} "${wanted}" — use its action_id / trigger_id from the app's page`, { status: null });
|
|
154
|
+
}
|
|
155
|
+
const [match] = found;
|
|
156
|
+
if (version && match.versionid && match.versionid !== version) {
|
|
157
|
+
console.warn(`viaSocket: ${app.service.name} "${wanted}" is published as ${match.versionid}, not ${version}: reread its page and update the code`);
|
|
158
|
+
}
|
|
159
|
+
return match.versionId;
|
|
160
|
+
},
|
|
161
|
+
/**
|
|
162
|
+
* The built-in plugs — Run AI Model, Pause Workflow, Memory, scraping, GST and more: every plug
|
|
163
|
+
* tagged `SDK` in Developer Hub, with its published actions. Live, so a newly tagged plug appears
|
|
164
|
+
* without a release.
|
|
165
|
+
*/
|
|
166
|
+
listBuiltIn: async () => {
|
|
167
|
+
const pages = await Promise.all(BUILT_IN_PLUG_CATEGORIES.map((category) => this.catalog.list({ category, limit: BUILT_IN_PAGE })));
|
|
168
|
+
const apps = [...new Map(pages.flat().map((app) => [app.serviceId, app])).values()].filter((app) => app.serviceId);
|
|
169
|
+
const withActions = await Promise.all(apps.map(async (app) => {
|
|
170
|
+
const versions = await this.catalog.versions(app.serviceId);
|
|
171
|
+
if (!versions?.actions.length)
|
|
172
|
+
return null;
|
|
173
|
+
return {
|
|
174
|
+
serviceId: app.serviceId,
|
|
175
|
+
slug: app.slug ?? '',
|
|
176
|
+
name: versions.service.name || app.name,
|
|
177
|
+
iconUrl: versions.service.icon_url || app.iconUrl,
|
|
178
|
+
requiresAuth: versions.service.requires_auth !== false && `${versions.service.auth_type ?? ''}`.toLowerCase() !== 'noauth',
|
|
179
|
+
actions: versions.actions
|
|
180
|
+
};
|
|
181
|
+
}));
|
|
182
|
+
return withActions.filter((app) => app !== null).sort((a, b) => a.name.localeCompare(b.name));
|
|
183
|
+
},
|
|
97
184
|
/**
|
|
98
185
|
* One app and every published action and trigger of it, as `{ service, actions, triggers }`.
|
|
99
186
|
* Each action carries `action_id` and `action_version_id`, each trigger `trigger_id` and
|
|
@@ -151,6 +238,78 @@ export class ViaSocket {
|
|
|
151
238
|
});
|
|
152
239
|
return payload.data;
|
|
153
240
|
}
|
|
241
|
+
/**
|
|
242
|
+
* One real event of a trigger for one connection: exactly what a handler reads at
|
|
243
|
+
* `context.req.body`, and what an automation's steps read there. Map from it, never from the
|
|
244
|
+
* catalog's `sample_output`, which the connector's author recorded on another account. It takes
|
|
245
|
+
* the `trigger_id` (a version id answers "No response"); `authType` is the app's
|
|
246
|
+
* `service.auth_type` from `catalog.versions()`; `inputData` is the trigger's own settings, the
|
|
247
|
+
* same the subscription will carry. No token, but the `auth_id` is a credential: server only.
|
|
248
|
+
*/
|
|
249
|
+
async sampleEvent(triggerId, authId, authType, inputData = {}) {
|
|
250
|
+
const query = new URLSearchParams({
|
|
251
|
+
action_id: required('triggerId', triggerId),
|
|
252
|
+
auth_id: required('authId', authId),
|
|
253
|
+
authType: required('authType', authType)
|
|
254
|
+
});
|
|
255
|
+
const { data } = await request({
|
|
256
|
+
method: 'POST',
|
|
257
|
+
url: `${this.runBaseUrl}/func/${SAMPLE_EVENT_FUNCTION}?${query}`,
|
|
258
|
+
body: JSON.stringify({ context: { inputData: inputData ?? {} } }),
|
|
259
|
+
fetchImpl: this.transport
|
|
260
|
+
});
|
|
261
|
+
// An unreadable connection (revoked, another user's, the wrong auth_id) answers 200 with the
|
|
262
|
+
// failure nested inside, as list-options does.
|
|
263
|
+
const nested = data?.response;
|
|
264
|
+
if (nested && Number(nested.status) >= 400) {
|
|
265
|
+
throw new ViaSocketError(`sampleEvent: ${nested.data?.message || 'the connection could not be read'}`, {
|
|
266
|
+
status: Number(nested.status),
|
|
267
|
+
body: data
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
if (!data || typeof data !== 'object' || Array.isArray(data) || data.message === 'No response') {
|
|
271
|
+
throw new ViaSocketError(`sampleEvent: no event came back (${JSON.stringify(data)?.slice(0, 120)}) — pass the trigger_id, not its version id, and a live auth_id`, { status: null, body: data });
|
|
272
|
+
}
|
|
273
|
+
// `viasocket_help` is a note for the developer, not a field of the event.
|
|
274
|
+
const { viasocket_help: _help, ...event } = data;
|
|
275
|
+
return event;
|
|
276
|
+
}
|
|
277
|
+
async resolveSlug(slug) {
|
|
278
|
+
let data;
|
|
279
|
+
try {
|
|
280
|
+
;
|
|
281
|
+
({ data } = await request({
|
|
282
|
+
method: 'GET',
|
|
283
|
+
url: `${CATALOG.slug}?service=${encodeURIComponent(slug)}`,
|
|
284
|
+
fetchImpl: this.transport,
|
|
285
|
+
signal: AbortSignal.timeout(SLUG_TIMEOUT_MS)
|
|
286
|
+
}));
|
|
287
|
+
}
|
|
288
|
+
catch (error) {
|
|
289
|
+
if (error?.name !== 'TimeoutError')
|
|
290
|
+
throw error;
|
|
291
|
+
}
|
|
292
|
+
const serviceId = data?.data?.plugins?.[slug]?.rowid;
|
|
293
|
+
if (!serviceId) {
|
|
294
|
+
throw new ViaSocketError(`No viaSocket app has the slug "${slug}" — slugs are exact and case-sensitive; find it with catalog.list()`, {
|
|
295
|
+
status: null
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
return serviceId;
|
|
299
|
+
}
|
|
300
|
+
cachedVersions(serviceId) {
|
|
301
|
+
const cached = this.versionsCache.get(serviceId);
|
|
302
|
+
if (cached && Date.now() - cached.at < VERSIONS_TTL_MS)
|
|
303
|
+
return cached.value;
|
|
304
|
+
const value = this.catalog.versions(serviceId);
|
|
305
|
+
this.versionsCache.set(serviceId, { at: Date.now(), value });
|
|
306
|
+
// Neither a failure nor an empty answer is kept: one bad read must not block runs for ten minutes.
|
|
307
|
+
value.then((app) => {
|
|
308
|
+
if (!app)
|
|
309
|
+
this.versionsCache.delete(serviceId);
|
|
310
|
+
}, () => this.versionsCache.delete(serviceId));
|
|
311
|
+
return value;
|
|
312
|
+
}
|
|
154
313
|
}
|
|
155
314
|
/**
|
|
156
315
|
* Calls made on behalf of one end user. Obtained from `viasocket.user(id)`.
|
|
@@ -265,25 +424,56 @@ export class UserScope {
|
|
|
265
424
|
}));
|
|
266
425
|
}
|
|
267
426
|
/**
|
|
268
|
-
* The script_id of an app this user already enabled
|
|
269
|
-
* enabling twice leaves you with two script_ids for one app.
|
|
427
|
+
* The script_id of an app this user already enabled — active or paused — or null. Call it before
|
|
428
|
+
* `enable()`: enabling twice leaves you with two script_ids for one app. A paused one is resumed
|
|
429
|
+
* with `enableFlow()`, never enabled again.
|
|
270
430
|
*/
|
|
271
431
|
async findEnabled(serviceId) {
|
|
272
432
|
const flows = await this.listFlows();
|
|
273
|
-
|
|
433
|
+
const wanted = required('serviceId', serviceId);
|
|
434
|
+
return flows.find((flow) => flow.serviceId === wanted && (flow.status === 'active' || flow.status === 'paused'))?.id ?? null;
|
|
274
435
|
}
|
|
275
|
-
/**
|
|
436
|
+
/**
|
|
437
|
+
* Pauses (2), resumes or restores (1), or deletes (0) one flow: an enabled app, a trigger
|
|
438
|
+
* subscription or an automation. A deleted flow is listed as `deleted` and can be restored with 1.
|
|
439
|
+
*/
|
|
276
440
|
async setFlowStatus(scriptId, status) {
|
|
277
|
-
const
|
|
278
|
-
|
|
279
|
-
|
|
441
|
+
const id = required('scriptId', scriptId);
|
|
442
|
+
try {
|
|
443
|
+
const { data } = await this.call('PUT', `/embed/updatestatus/${id}?status=${status}`);
|
|
444
|
+
// The response is the whole flow record; nothing else in it is anything a caller acts on.
|
|
445
|
+
return { id: data.id, status: String(data.status) };
|
|
446
|
+
}
|
|
447
|
+
catch (error) {
|
|
448
|
+
// Already in that state: what the caller asked for is true, so it is not a failure.
|
|
449
|
+
if (error instanceof ViaSocketError && /already set/i.test(error.message))
|
|
450
|
+
return { id, status: String(status) };
|
|
451
|
+
throw error;
|
|
452
|
+
}
|
|
280
453
|
}
|
|
454
|
+
/** Pauses a flow (status 2). Before 0.7.0 this sent 0, which deletes. */
|
|
281
455
|
disableFlow(scriptId) {
|
|
282
|
-
return this.setFlowStatus(scriptId,
|
|
456
|
+
return this.setFlowStatus(scriptId, 2);
|
|
283
457
|
}
|
|
458
|
+
/** Resumes a paused flow, or restores a deleted one (status 1). */
|
|
284
459
|
enableFlow(scriptId) {
|
|
285
460
|
return this.setFlowStatus(scriptId, 1);
|
|
286
461
|
}
|
|
462
|
+
/** Deletes a flow (status 0). It stays listed as `deleted`; `enableFlow` restores it. */
|
|
463
|
+
deleteFlow(scriptId) {
|
|
464
|
+
return this.setFlowStatus(scriptId, 0);
|
|
465
|
+
}
|
|
466
|
+
/** Renames a flow; `meta` comes back as `metadata` in the prebuilt UI's flow events. */
|
|
467
|
+
async renameFlow(scriptId, params) {
|
|
468
|
+
const body = { title: required('title', params?.title) };
|
|
469
|
+
if (params.description !== undefined)
|
|
470
|
+
body.description = params.description;
|
|
471
|
+
if (params.meta !== undefined)
|
|
472
|
+
body.meta = params.meta;
|
|
473
|
+
const { data } = await this.call('PUT', `/projects/updateflowembed/${required('scriptId', scriptId)}`, body);
|
|
474
|
+
// The response is the whole script, code included; only these are anything a caller acts on.
|
|
475
|
+
return { id: data.id, title: data.title, description: data.description ?? '' };
|
|
476
|
+
}
|
|
287
477
|
/**
|
|
288
478
|
* The AI API: a chat assistant viaSocket runs for this user — threads and history kept per
|
|
289
479
|
* `unique_identifier`, your prompt per call, your own endpoints as its tools. Backend only, like
|
package/dist/esm/http.d.ts
CHANGED
|
@@ -22,5 +22,7 @@ export interface RequestOptions {
|
|
|
22
22
|
/** Already serialised. `''` sends an empty body, which is what /embed/enable expects. */
|
|
23
23
|
body?: string;
|
|
24
24
|
fetchImpl: typeof fetch;
|
|
25
|
+
/** Aborts the request; an abort is thrown as is, so a caller can tell a timeout from a refusal. */
|
|
26
|
+
signal?: AbortSignal;
|
|
25
27
|
}
|
|
26
28
|
export declare function request<T = unknown>(options: RequestOptions): Promise<Envelope<T>>;
|
package/dist/esm/http.js
CHANGED
|
@@ -20,9 +20,11 @@ export async function request(options) {
|
|
|
20
20
|
headers.authorization = options.token;
|
|
21
21
|
let response;
|
|
22
22
|
try {
|
|
23
|
-
response = await options.fetchImpl(options.url, { method: options.method, headers, body: options.body });
|
|
23
|
+
response = await options.fetchImpl(options.url, { method: options.method, headers, body: options.body, signal: options.signal });
|
|
24
24
|
}
|
|
25
25
|
catch (error) {
|
|
26
|
+
if (error?.name === 'TimeoutError' || error?.name === 'AbortError')
|
|
27
|
+
throw error;
|
|
26
28
|
throw new ViaSocketError(`Request to ${options.url} failed: ${error?.message || 'network error'}`, { status: null });
|
|
27
29
|
}
|
|
28
30
|
const text = await response.text();
|
package/dist/esm/index.d.ts
CHANGED
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
* const user = viasocket.user(uniqueIdentifier)
|
|
8
8
|
*
|
|
9
9
|
* const token = await user.token() // for the connect popup
|
|
10
|
+
* const serviceId = await viasocket.catalog.serviceIdFor('slack') // an app, by its slug
|
|
11
|
+
* const actionVersionId = await viasocket.catalog.versionIdFor(serviceId, 'row1jrbor', 'V107')
|
|
10
12
|
* const { scriptId } = await user.enable(serviceId, authId) // once, for actions
|
|
11
13
|
* const { options } = await user.listOptions(actionVersionId, {...}) // fill a dropdown
|
|
12
14
|
* const result = await viasocket.runAction(scriptId, actionVersionId, inputData)
|
|
@@ -15,8 +17,8 @@
|
|
|
15
17
|
*
|
|
16
18
|
* The browser half — opening the connect popup — lives at 'viasocket-apps/browser'.
|
|
17
19
|
*/
|
|
18
|
-
export { ViaSocket, UserScope } from './client.js';
|
|
20
|
+
export { ViaSocket, UserScope, BUILT_IN_PLUG_CATEGORIES } from './client.js';
|
|
19
21
|
export { ViaSocketError } from './errors.js';
|
|
20
22
|
export { signEmbedToken } from './token.js';
|
|
21
23
|
export type { SignEmbedTokenInput } from './token.js';
|
|
22
|
-
export type { AiAccepted, AiHistoryPage, AiMessage, AiReply, AiRtLayerToken, AiSend, AiSendParams, AiThread, AiTool, CatalogApp, CatalogAction, CatalogService, CatalogTrigger, CatalogVersion, CatalogVersions, EnableResult, Envelope, Flow, FlowStatus, ListAppsParams, ListOptionsParams, ListOptionsResult, Option, SubscribeParams, SubscribeResult, UpdateSubscriptionParams, ViaSocketOptions } from './types.js';
|
|
24
|
+
export type { AiAccepted, AiHistoryPage, AiMessage, AiReply, AiRtLayerToken, AiSend, AiSendParams, AiThread, AiTool, BuiltInApp, CatalogApp, CatalogAction, CatalogService, CatalogTrigger, CatalogVersion, CatalogVersions, EnableResult, Envelope, Flow, FlowStatus, ListAppsParams, ListOptionsParams, ListOptionsResult, Option, RenameFlowParams, SubscribeParams, SubscribeResult, UpdateSubscriptionParams, ViaSocketOptions } from './types.js';
|
package/dist/esm/index.js
CHANGED
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
* const user = viasocket.user(uniqueIdentifier)
|
|
8
8
|
*
|
|
9
9
|
* const token = await user.token() // for the connect popup
|
|
10
|
+
* const serviceId = await viasocket.catalog.serviceIdFor('slack') // an app, by its slug
|
|
11
|
+
* const actionVersionId = await viasocket.catalog.versionIdFor(serviceId, 'row1jrbor', 'V107')
|
|
10
12
|
* const { scriptId } = await user.enable(serviceId, authId) // once, for actions
|
|
11
13
|
* const { options } = await user.listOptions(actionVersionId, {...}) // fill a dropdown
|
|
12
14
|
* const result = await viasocket.runAction(scriptId, actionVersionId, inputData)
|
|
@@ -15,6 +17,6 @@
|
|
|
15
17
|
*
|
|
16
18
|
* The browser half — opening the connect popup — lives at 'viasocket-apps/browser'.
|
|
17
19
|
*/
|
|
18
|
-
export { ViaSocket, UserScope } from './client.js';
|
|
20
|
+
export { ViaSocket, UserScope, BUILT_IN_PLUG_CATEGORIES } from './client.js';
|
|
19
21
|
export { ViaSocketError } from './errors.js';
|
|
20
22
|
export { signEmbedToken } from './token.js';
|
package/dist/esm/types.d.ts
CHANGED
|
@@ -96,7 +96,14 @@ export interface Flow {
|
|
|
96
96
|
authId: string;
|
|
97
97
|
serviceId: string;
|
|
98
98
|
}
|
|
99
|
-
|
|
99
|
+
/** What `setFlowStatus` sets: `2` pauses, `1` resumes or restores, `0` deletes. */
|
|
100
|
+
export type FlowStatus = 0 | 1 | 2;
|
|
101
|
+
export interface RenameFlowParams {
|
|
102
|
+
title: string;
|
|
103
|
+
description?: string;
|
|
104
|
+
/** Anything of yours; comes back as `metadata` in the prebuilt UI's flow events. */
|
|
105
|
+
meta?: Record<string, unknown>;
|
|
106
|
+
}
|
|
100
107
|
/** The envelope every viaSocket endpoint answers with. */
|
|
101
108
|
/** One app of the catalog, from a search or the full list. */
|
|
102
109
|
export interface CatalogApp {
|
|
@@ -105,10 +112,24 @@ export interface CatalogApp {
|
|
|
105
112
|
name: string;
|
|
106
113
|
description: string;
|
|
107
114
|
iconUrl: string;
|
|
108
|
-
/**
|
|
115
|
+
/**
|
|
116
|
+
* The app's slug (`slack`, `google-sheets`): what code names a fixed app by, resolved with
|
|
117
|
+
* `catalog.serviceIdFor`. Case-sensitive. Only the full list carries it, and `category` and `domain`.
|
|
118
|
+
*/
|
|
119
|
+
slug?: string;
|
|
109
120
|
category?: string[];
|
|
110
121
|
domain?: string;
|
|
111
122
|
}
|
|
123
|
+
/** One built-in plug — `catalog.listBuiltIn()` — with its published actions. */
|
|
124
|
+
export interface BuiltInApp {
|
|
125
|
+
serviceId: string;
|
|
126
|
+
slug: string;
|
|
127
|
+
name: string;
|
|
128
|
+
iconUrl: string;
|
|
129
|
+
/** `false`: no Connect button, and `'NoAuth'` wherever a call takes an `authId`. */
|
|
130
|
+
requiresAuth: boolean;
|
|
131
|
+
actions: CatalogAction[];
|
|
132
|
+
}
|
|
112
133
|
export interface ListAppsParams {
|
|
113
134
|
/** Up to 200, the API's cap. */
|
|
114
135
|
limit?: number;
|
|
@@ -138,7 +159,9 @@ interface CatalogEventBase {
|
|
|
138
159
|
* schema carries no generator code.
|
|
139
160
|
*/
|
|
140
161
|
input_schema: Record<string, unknown>;
|
|
141
|
-
/**
|
|
162
|
+
/** The published version's name (`V107`): what `versionIdFor` checks the version in your code against. */
|
|
163
|
+
versionid?: string;
|
|
164
|
+
/** The connector author's example of the response or event, from another account: map from a real one. */
|
|
142
165
|
sample_output?: unknown;
|
|
143
166
|
updated_at?: string;
|
|
144
167
|
[key: string]: unknown;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "viasocket-apps",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "Server-side client for the viaSocket Apps API: let your users connect Slack, GitHub, Google Sheets or any of 2,300+ apps from your own product, then run actions and receive events through one API.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"viasocket",
|