viasocket-apps 0.1.0 → 0.1.2

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
@@ -15,6 +15,28 @@ The same calls are documented as plain HTTP in your viaSocket dashboard under
15
15
  **Integrations → your Apps API integration → API reference**. Use whichever suits your stack;
16
16
  this package is a thin, typed layer over those endpoints and adds nothing they don't have.
17
17
 
18
+ ## For coding agents
19
+
20
+ [![skills.sh](https://skills.sh/b/Walkover-Web-Solution/viasocket-apps)](https://skills.sh/Walkover-Web-Solution/viasocket-apps)
21
+
22
+ This repo also carries the **viaSocket integrations skill** — what a coding agent reads to
23
+ integrate any app into your product, with or without this package. Install it into Claude Code,
24
+ Cursor, Codex, Copilot, Gemini CLI, Windsurf, Cline and 20+ other agents:
25
+
26
+ ```sh
27
+ npx skills add Walkover-Web-Solution/viasocket-apps
28
+ ```
29
+
30
+ Add `-g` to install it for every project. No install? Paste this into your agent:
31
+ `Read https://flow.viasocket.com/documentation/skill.md and follow it.`
32
+
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>`.
39
+
18
40
  ## Server
19
41
 
20
42
  ```js
@@ -49,12 +71,14 @@ const result = await viasocket.runAction(scriptId, actionVersionId, {
49
71
  column_key: true
50
72
  })
51
73
 
52
- // 5. Or subscribe to an event. Needs only the connection, not enable().
74
+ // 5. Or subscribe to an event. Needs only the connection, not enable(). `code` is a handler we
75
+ // run on our servers when the event fires — see "Events" below. Save the scriptId it returns
76
+ // together with the inputData and handler you sent: the flows list never returns those.
53
77
  const subscription = await user.subscribe(triggerVersionId, {
54
78
  authId,
55
79
  inputData: { channel_id: ['C08SXCV3J85'] },
56
- webhook: 'https://your-app.com/webhooks/viasocket',
57
- meta: { userId: 'user_123' }
80
+ code: handler,
81
+ meta: { user_id: 'user_123' }
58
82
  })
59
83
  ```
60
84
 
@@ -79,10 +103,45 @@ const { authId } = await connect({ embedToken, serviceId: 'rowbu58rc' })
79
103
  | `user.token()` | Signed embed token for that user; hand it to the frontend. |
80
104
  | `user.enable(serviceId, authId)` | Turn a connection into a `scriptId` for running actions. Once per connection. |
81
105
  | `user.findEnabled(serviceId)` | The `scriptId` if this app is already enabled, else `null`. Call before `enable`. |
82
- | `user.listOptions(actionVersionId, { fieldKey, authId, existingFields })` | The values a field accepts, as `{ options, offset }`. Both response shapes normalised. |
106
+ | `user.listOptions(actionVersionId, { fieldKey, authId, existingFields })` | The values a field accepts, as `{ options, offset }`. Both response shapes normalised; an unreadable connection (revoked, another user's `auth_id`) throws a `ViaSocketError` with status 400 even though the API answers 200. |
83
107
  | `viasocket.runAction(scriptId, actionVersionId, inputData)` | Run one action. Returns the app's own response. |
84
- | `user.subscribe(triggerVersionId, { authId, inputData, webhook \| code, meta })` | Subscribe to an app event. Returns `{ scriptId, hookUrl, … }`. |
108
+ | `user.subscribe(triggerVersionId, { authId, inputData, code \| webhook, meta })` | Subscribe to an app event with a handler we run per event. Returns `{ scriptId, hookUrl, … }`. |
85
109
  | `user.updateSubscription(scriptId, { code, meta })` | Change a live subscription in place. |
110
+
111
+ ## Events
112
+
113
+ Subscribing to an event means telling us **what to do when it fires**. That is `code`: a handler we
114
+ run on viaSocket's servers per event, with the event in `context.req.body`. Your server is not in
115
+ the path. Most often the handler runs an action in another app the user connected — the run URL
116
+ takes no token, the `script_id` is the credential — or calls your own API with your own auth header
117
+ baked in. It is a string executed away from your process, so it must stand alone: no imports,
118
+ nothing from your codebase; `axios`, `fetch` and `context` are in scope.
119
+
120
+ ```js
121
+ // A string, not a function: it runs on viaSocket's servers each time the event fires.
122
+ // In scope there: axios, fetch, context. The event the app sent is context.req.body.
123
+ // ${…} are YOUR variables, baked in per user when you subscribe.
124
+ const handler = `
125
+ const event = context.req.body
126
+ const response = await fetch("https://flow.sokt.io/func/${slackScriptId}", {
127
+ method: "POST",
128
+ headers: { "Content-Type": "application/json" },
129
+ body: JSON.stringify({
130
+ action_version_id: "${slackSendMessageVersionId}",
131
+ inputData: { channel_id: "${channelPickedAtSetup}", text: "New mail from " + event.from + ": " + event.subject }
132
+ })
133
+ })
134
+ return await response.json()
135
+ `
136
+
137
+ await user.subscribe(gmailNewMailVersionId, { authId, inputData: {}, code: handler, meta: { user_id: uniqueIdentifier } })
138
+ ```
139
+
140
+ `webhook` is the edge case: a public, unauthenticated URL of yours that we POST every raw event to.
141
+ Use it only when that is what you want, and never both.
142
+
143
+ Some triggers are polled rather than pushed. For those, `inputData` may carry `scheduledTime`, the
144
+ minutes between checks, as a string: `"5"` or `"15"`.
86
145
  | `user.listFlows()` | Every enabled app and subscription this user has. |
87
146
  | `user.disableFlow(scriptId)` / `user.enableFlow(scriptId)` | Turn a flow off or back on. Disabling a subscription ends it. |
88
147
  | `user.listConnections()` | Every app this user has connected. |
@@ -124,15 +124,22 @@ class UserScope {
124
124
  });
125
125
  if (Array.isArray(data))
126
126
  return { options: data, offset: null };
127
+ // A connection that cannot be read (revoked, or another user's auth_id) is answered with 200 and
128
+ // success: true, the failure nested in data. It is an error, and the message is the explanation.
129
+ if (data?.response && Number(data.response.status) >= 400) {
130
+ throw new errors_js_1.ViaSocketError(data.response.data?.message || 'list-options failed', { status: Number(data.response.status), body: data });
131
+ }
127
132
  return { options: Array.isArray(data?.data) ? data.data : [], offset: data?.offset ?? null };
128
133
  }
129
134
  /**
130
- * Subscribes to an app event. Give `webhook` (a URL we POST each event to) or `code`
131
- * (JavaScript we run per event) — one or the other.
135
+ * Subscribes to an app event with a handler: `code`, a self-contained script we run on our
136
+ * servers each time the event fires, with the event at `context.req.body`. It runs an action in
137
+ * another app the user connected, or calls your own API. `webhook` (a public URL we POST raw
138
+ * events to) is the edge case. Exactly one of the two.
132
139
  */
133
140
  async subscribe(triggerVersionId, params) {
134
141
  if (!params?.webhook === !params?.code) {
135
- throw new errors_js_1.ViaSocketError('subscribe: pass exactly one of `webhook` or `code`', { status: null });
142
+ throw new errors_js_1.ViaSocketError('subscribe: pass exactly one of `code` (a handler we run per event) or `webhook`', { status: null });
136
143
  }
137
144
  const body = {
138
145
  auth_id: required('authId', params.authId),
package/dist/cjs/http.js CHANGED
@@ -2,10 +2,17 @@
2
2
  /**
3
3
  * The one place a request is made and a response judged.
4
4
  *
5
- * viaSocket answers every call with the same envelope, and it can say "no" two ways: a non-2xx
6
- * status, or a 2xx whose body carries `success: false`. Callers should never have to remember
7
- * that, so both become a ViaSocketError here with the API's own message attached — that message
8
- * is almost always the explanation.
5
+ * The embed endpoints answer with one envelope, `{ success, data }`, and can say "no" two ways:
6
+ * a non-2xx status, or a 2xx whose body carries `success: false`. Callers should never have to
7
+ * remember that, so both become a ViaSocketError here with the API's own message attached —
8
+ * that message is almost always the explanation.
9
+ *
10
+ * The action runner is the exception: some actions answer in that envelope, many hand back their
11
+ * own result at the top level — with no `success` or `data` key at all (Gmail's list-mails returns
12
+ * `{ emails, nextPageToken }`), or with a `success` key of their own and no `data` (Slack's "Find
13
+ * Public channel" returns `{ success, channels }`). So a body is the envelope only when it carries a
14
+ * boolean `success` AND a `data` key, or is a refusal (`success: false`); anything else is the
15
+ * result itself. Getting this wrong returned `undefined` for every such action.
9
16
  */
10
17
  Object.defineProperty(exports, "__esModule", { value: true });
11
18
  exports.request = request;
@@ -29,9 +36,16 @@ async function request(options) {
29
36
  catch {
30
37
  payload = text;
31
38
  }
32
- const envelope = payload && typeof payload === 'object' ? payload : null;
39
+ const shaped = payload !== null && typeof payload === 'object' ? payload : null;
40
+ const isEnvelope = shaped !== null && typeof shaped.success === 'boolean' && ('data' in shaped || shaped.success === false);
41
+ const envelope = isEnvelope ? payload : null;
33
42
  if (!response.ok || envelope?.success === false) {
34
- const message = envelope?.message || envelope?.error?.message || `viaSocket responded ${response.status} for ${options.method} ${options.url}`;
43
+ const raw = payload && typeof payload === 'object' ? payload : null;
44
+ const message = envelope?.message ||
45
+ envelope?.error?.message ||
46
+ raw?.message ||
47
+ raw?.error?.message ||
48
+ `viaSocket responded ${response.status} for ${options.method} ${options.url}`;
35
49
  throw new errors_js_1.ViaSocketError(message, { status: response.status, body: payload });
36
50
  }
37
51
  return (envelope ?? { success: true, data: payload });
package/dist/cjs/index.js CHANGED
@@ -5,13 +5,13 @@
5
5
  * import { ViaSocket } from 'viasocket-apps'
6
6
  *
7
7
  * const viasocket = new ViaSocket({ orgId, projectId, secret: process.env.VIASOCKET_EMBED_SECRET })
8
- * const user = viasocket.user(endUserId)
8
+ * const user = viasocket.user(uniqueIdentifier)
9
9
  *
10
10
  * const token = await user.token() // for the connect popup
11
11
  * const { scriptId } = await user.enable(serviceId, authId) // once, for actions
12
12
  * const { options } = await user.listOptions(actionVersionId, {...}) // fill a dropdown
13
13
  * const result = await viasocket.runAction(scriptId, actionVersionId, inputData)
14
- * const sub = await user.subscribe(triggerVersionId, { authId, inputData, webhook })
14
+ * const sub = await user.subscribe(triggerVersionId, { authId, inputData, code: handler })
15
15
  *
16
16
  * The browser half — opening the connect popup — lives at 'viasocket-apps/browser'.
17
17
  */
@@ -56,8 +56,10 @@ export declare class UserScope {
56
56
  */
57
57
  listOptions(actionVersionId: string, params: ListOptionsParams): Promise<ListOptionsResult>;
58
58
  /**
59
- * Subscribes to an app event. Give `webhook` (a URL we POST each event to) or `code`
60
- * (JavaScript we run per event) — one or the other.
59
+ * Subscribes to an app event with a handler: `code`, a self-contained script we run on our
60
+ * servers each time the event fires, with the event at `context.req.body`. It runs an action in
61
+ * another app the user connected, or calls your own API. `webhook` (a public URL we POST raw
62
+ * events to) is the edge case. Exactly one of the two.
61
63
  */
62
64
  subscribe(triggerVersionId: string, params: SubscribeParams): Promise<SubscribeResult>;
63
65
  /** Changes a live subscription in place. The script_id does not change. */
@@ -120,15 +120,22 @@ export class UserScope {
120
120
  });
121
121
  if (Array.isArray(data))
122
122
  return { options: data, offset: null };
123
+ // A connection that cannot be read (revoked, or another user's auth_id) is answered with 200 and
124
+ // success: true, the failure nested in data. It is an error, and the message is the explanation.
125
+ if (data?.response && Number(data.response.status) >= 400) {
126
+ throw new ViaSocketError(data.response.data?.message || 'list-options failed', { status: Number(data.response.status), body: data });
127
+ }
123
128
  return { options: Array.isArray(data?.data) ? data.data : [], offset: data?.offset ?? null };
124
129
  }
125
130
  /**
126
- * Subscribes to an app event. Give `webhook` (a URL we POST each event to) or `code`
127
- * (JavaScript we run per event) — one or the other.
131
+ * Subscribes to an app event with a handler: `code`, a self-contained script we run on our
132
+ * servers each time the event fires, with the event at `context.req.body`. It runs an action in
133
+ * another app the user connected, or calls your own API. `webhook` (a public URL we POST raw
134
+ * events to) is the edge case. Exactly one of the two.
128
135
  */
129
136
  async subscribe(triggerVersionId, params) {
130
137
  if (!params?.webhook === !params?.code) {
131
- throw new ViaSocketError('subscribe: pass exactly one of `webhook` or `code`', { status: null });
138
+ throw new ViaSocketError('subscribe: pass exactly one of `code` (a handler we run per event) or `webhook`', { status: null });
132
139
  }
133
140
  const body = {
134
141
  auth_id: required('authId', params.authId),
@@ -1,10 +1,17 @@
1
1
  /**
2
2
  * The one place a request is made and a response judged.
3
3
  *
4
- * viaSocket answers every call with the same envelope, and it can say "no" two ways: a non-2xx
5
- * status, or a 2xx whose body carries `success: false`. Callers should never have to remember
6
- * that, so both become a ViaSocketError here with the API's own message attached — that message
7
- * is almost always the explanation.
4
+ * The embed endpoints answer with one envelope, `{ success, data }`, and can say "no" two ways:
5
+ * a non-2xx status, or a 2xx whose body carries `success: false`. Callers should never have to
6
+ * remember that, so both become a ViaSocketError here with the API's own message attached —
7
+ * that message is almost always the explanation.
8
+ *
9
+ * The action runner is the exception: some actions answer in that envelope, many hand back their
10
+ * own result at the top level — with no `success` or `data` key at all (Gmail's list-mails returns
11
+ * `{ emails, nextPageToken }`), or with a `success` key of their own and no `data` (Slack's "Find
12
+ * Public channel" returns `{ success, channels }`). So a body is the envelope only when it carries a
13
+ * boolean `success` AND a `data` key, or is a refusal (`success: false`); anything else is the
14
+ * result itself. Getting this wrong returned `undefined` for every such action.
8
15
  */
9
16
  import type { Envelope } from './types.js';
10
17
  export interface RequestOptions {
package/dist/esm/http.js CHANGED
@@ -1,10 +1,17 @@
1
1
  /**
2
2
  * The one place a request is made and a response judged.
3
3
  *
4
- * viaSocket answers every call with the same envelope, and it can say "no" two ways: a non-2xx
5
- * status, or a 2xx whose body carries `success: false`. Callers should never have to remember
6
- * that, so both become a ViaSocketError here with the API's own message attached — that message
7
- * is almost always the explanation.
4
+ * The embed endpoints answer with one envelope, `{ success, data }`, and can say "no" two ways:
5
+ * a non-2xx status, or a 2xx whose body carries `success: false`. Callers should never have to
6
+ * remember that, so both become a ViaSocketError here with the API's own message attached —
7
+ * that message is almost always the explanation.
8
+ *
9
+ * The action runner is the exception: some actions answer in that envelope, many hand back their
10
+ * own result at the top level — with no `success` or `data` key at all (Gmail's list-mails returns
11
+ * `{ emails, nextPageToken }`), or with a `success` key of their own and no `data` (Slack's "Find
12
+ * Public channel" returns `{ success, channels }`). So a body is the envelope only when it carries a
13
+ * boolean `success` AND a `data` key, or is a refusal (`success: false`); anything else is the
14
+ * result itself. Getting this wrong returned `undefined` for every such action.
8
15
  */
9
16
  import { ViaSocketError } from './errors.js';
10
17
  export async function request(options) {
@@ -26,9 +33,16 @@ export async function request(options) {
26
33
  catch {
27
34
  payload = text;
28
35
  }
29
- const envelope = payload && typeof payload === 'object' ? payload : null;
36
+ const shaped = payload !== null && typeof payload === 'object' ? payload : null;
37
+ const isEnvelope = shaped !== null && typeof shaped.success === 'boolean' && ('data' in shaped || shaped.success === false);
38
+ const envelope = isEnvelope ? payload : null;
30
39
  if (!response.ok || envelope?.success === false) {
31
- const message = envelope?.message || envelope?.error?.message || `viaSocket responded ${response.status} for ${options.method} ${options.url}`;
40
+ const raw = payload && typeof payload === 'object' ? payload : null;
41
+ const message = envelope?.message ||
42
+ envelope?.error?.message ||
43
+ raw?.message ||
44
+ raw?.error?.message ||
45
+ `viaSocket responded ${response.status} for ${options.method} ${options.url}`;
32
46
  throw new ViaSocketError(message, { status: response.status, body: payload });
33
47
  }
34
48
  return (envelope ?? { success: true, data: payload });
@@ -4,13 +4,13 @@
4
4
  * import { ViaSocket } from 'viasocket-apps'
5
5
  *
6
6
  * const viasocket = new ViaSocket({ orgId, projectId, secret: process.env.VIASOCKET_EMBED_SECRET })
7
- * const user = viasocket.user(endUserId)
7
+ * const user = viasocket.user(uniqueIdentifier)
8
8
  *
9
9
  * const token = await user.token() // for the connect popup
10
10
  * const { scriptId } = await user.enable(serviceId, authId) // once, for actions
11
11
  * const { options } = await user.listOptions(actionVersionId, {...}) // fill a dropdown
12
12
  * const result = await viasocket.runAction(scriptId, actionVersionId, inputData)
13
- * const sub = await user.subscribe(triggerVersionId, { authId, inputData, webhook })
13
+ * const sub = await user.subscribe(triggerVersionId, { authId, inputData, code: handler })
14
14
  *
15
15
  * The browser half — opening the connect popup — lives at 'viasocket-apps/browser'.
16
16
  */
package/dist/esm/index.js CHANGED
@@ -4,13 +4,13 @@
4
4
  * import { ViaSocket } from 'viasocket-apps'
5
5
  *
6
6
  * const viasocket = new ViaSocket({ orgId, projectId, secret: process.env.VIASOCKET_EMBED_SECRET })
7
- * const user = viasocket.user(endUserId)
7
+ * const user = viasocket.user(uniqueIdentifier)
8
8
  *
9
9
  * const token = await user.token() // for the connect popup
10
10
  * const { scriptId } = await user.enable(serviceId, authId) // once, for actions
11
11
  * const { options } = await user.listOptions(actionVersionId, {...}) // fill a dropdown
12
12
  * const result = await viasocket.runAction(scriptId, actionVersionId, inputData)
13
- * const sub = await user.subscribe(triggerVersionId, { authId, inputData, webhook })
13
+ * const sub = await user.subscribe(triggerVersionId, { authId, inputData, code: handler })
14
14
  *
15
15
  * The browser half — opening the connect popup — lives at 'viasocket-apps/browser'.
16
16
  */
@@ -50,12 +50,23 @@ export interface EnableResult {
50
50
  }
51
51
  export interface SubscribeParams {
52
52
  authId: string;
53
- /** The event's own configuration — which channel, which sheet. */
53
+ /**
54
+ * The event's own configuration — which channel, which sheet. For polled triggers it may also
55
+ * carry `scheduledTime`, the minutes between checks as a string: "5" or "15".
56
+ */
54
57
  inputData: Record<string, unknown>;
55
- /** Your endpoint. We POST every event to it. Use this or `code`, not both. */
56
- webhook?: string;
57
- /** JavaScript run on each event instead of a webhook. */
58
+ /**
59
+ * What to do when the event fires: a script we run on viaSocket's servers per event. The
60
+ * default. It runs an action in another app the user connected, or calls your own API.
61
+ *
62
+ * It is a string of JavaScript executed away from your process, so it must stand alone — no
63
+ * imports and no reference to anything in your codebase. In scope there: `axios`, `fetch` and
64
+ * `context`; the event the app sent is `context.req.body`. Bake in any value of yours (a
65
+ * script_id, a picked id, an API key) when you build the string. Exactly one of `code` or `webhook`.
66
+ */
58
67
  code?: string;
68
+ /** Edge case: a public, unauthenticated URL of yours. We POST every raw event to it. */
69
+ webhook?: string;
59
70
  /** Anything of yours; handed back with every delivery. */
60
71
  meta?: Record<string, unknown>;
61
72
  }
@@ -70,6 +81,7 @@ export interface SubscribeResult {
70
81
  meta: Record<string, unknown>;
71
82
  }
72
83
  export interface UpdateSubscriptionParams {
84
+ /** A new script to run per event. Same rules as `SubscribeParams.code`. */
73
85
  code?: string;
74
86
  meta?: Record<string, unknown>;
75
87
  }
package/package.json CHANGED
@@ -1,8 +1,17 @@
1
1
  {
2
2
  "name": "viasocket-apps",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
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
- "keywords": ["viasocket", "integrations", "oauth", "api", "webhooks", "slack", "github", "google-sheets"],
5
+ "keywords": [
6
+ "viasocket",
7
+ "integrations",
8
+ "oauth",
9
+ "api",
10
+ "webhooks",
11
+ "slack",
12
+ "github",
13
+ "google-sheets"
14
+ ],
6
15
  "license": "MIT",
7
16
  "author": "Walkover Web Solution",
8
17
  "homepage": "https://github.com/Walkover-Web-Solution/viasocket-apps#readme",
@@ -31,7 +40,10 @@
31
40
  "require": "./dist/cjs/browser.js"
32
41
  }
33
42
  },
34
- "files": ["dist", "README.md"],
43
+ "files": [
44
+ "dist",
45
+ "README.md"
46
+ ],
35
47
  "sideEffects": false,
36
48
  "scripts": {
37
49
  "build": "tsc -p tsconfig.esm.json && tsc -p tsconfig.cjs.json && node scripts/finish-build.mjs",