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 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 it asks for, and `VIASOCKET_EMBED_SECRET`, which
35
- goes in your server's `.env`. The skill is
36
- [skills/viasocket-integrations/SKILL.md](skills/viasocket-integrations/SKILL.md), generated by
37
- viaSocket and mirrored here; the same text filled with your ids is served at
38
- `https://flow.viasocket.com/documentation/skill.md?org=<org_id>&project=<project_id>`.
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
- // 2. After the popup posts back an auth_id: enable the app, once, to run actions.
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
- inputData: { channel_id: "${channelPickedAtSetup}", text: "New mail from " + event.from + ": " + event.subject }
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
- return await response.json()
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`.
@@ -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, or null. Call it before `enable()` —
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
- return flows.find((flow) => flow.serviceId === required('serviceId', serviceId) && flow.status === 'active')?.id ?? null;
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
- /** Turns a flow off (0) or back on (1): an enabled app, or a trigger subscription. */
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 { data } = await this.call('PUT', `/embed/updatestatus/${required('scriptId', scriptId)}?status=${status}`);
282
- // The response is the whole flow record; nothing else in it is anything a caller acts on.
283
- return { id: data.id, status: String(data.status) };
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, 0);
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");
@@ -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, or null. Call it before `enable()` —
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
- /** Turns a flow off (0) or back on (1): an enabled app, or a trigger subscription. */
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
@@ -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, or null. Call it before `enable()` —
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
- return flows.find((flow) => flow.serviceId === required('serviceId', serviceId) && flow.status === 'active')?.id ?? null;
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
- /** Turns a flow off (0) or back on (1): an enabled app, or a trigger subscription. */
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 { data } = await this.call('PUT', `/embed/updatestatus/${required('scriptId', scriptId)}?status=${status}`);
278
- // The response is the whole flow record; nothing else in it is anything a caller acts on.
279
- return { id: data.id, status: String(data.status) };
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, 0);
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
@@ -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();
@@ -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';
@@ -96,7 +96,14 @@ export interface Flow {
96
96
  authId: string;
97
97
  serviceId: string;
98
98
  }
99
- export type FlowStatus = 0 | 1;
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
- /** Only the full list carries these. */
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
- /** A real response of the action, or a real event payload of the trigger. */
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.6.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",