@bambuser/n8n-nodes-livecommerce 0.1.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/LICENSE +21 -0
- package/README.md +188 -0
- package/dist/credentials/BambuserApi.credentials.js +55 -0
- package/dist/credentials/bambuser.svg +13 -0
- package/dist/lib/resolveOrigin.js +14 -0
- package/dist/nodes/BambuserCalls/BambuserCalls.node.js +295 -0
- package/dist/nodes/BambuserCalls/bambuser-live.svg +3 -0
- package/dist/nodes/BambuserProductCatalog/BambuserProductCatalog.node.js +236 -0
- package/dist/nodes/BambuserProductCatalog/bambuser-vod.svg +12 -0
- package/dist/nodes/BambuserShopperData/BambuserShopperData.node.js +170 -0
- package/dist/nodes/BambuserShopperData/bambuser-vod.svg +12 -0
- package/dist/nodes/BambuserShows/BambuserShows.node.js +1432 -0
- package/dist/nodes/BambuserShows/bambuser-live.svg +3 -0
- package/dist/nodes/BambuserVod/BambuserVod.node.js +704 -0
- package/dist/nodes/BambuserVod/bambuser-vod.svg +12 -0
- package/dist/nodes/BambuserWebhookTrigger/BambuserWebhookTrigger.node.js +114 -0
- package/dist/nodes/BambuserWebhookTrigger/bambuser-webhook.svg +8 -0
- package/package.json +77 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Bambuser AB
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
# n8n-nodes-livecommerce
|
|
2
|
+
|
|
3
|
+
[](https://github.com/bambuser/n8n-nodes-livecommerce/actions/workflows/ci.yml)
|
|
4
|
+
[](https://www.npmjs.com/package/@bambuser/n8n-nodes-livecommerce)
|
|
5
|
+
[](https://www.npmjs.com/package/@bambuser/n8n-nodes-livecommerce)
|
|
6
|
+
[](https://nodejs.org)
|
|
7
|
+
[](./LICENSE)
|
|
8
|
+
|
|
9
|
+
Community [n8n](https://n8n.io) nodes for the [Bambuser](https://bambuser.com) video-commerce platform. Connect **Bambuser Live**, **Bambuser Shoppable Videos**, **Bambuser Video Consultation**, the product catalog, shopper-data records, and webhook events into n8n's 400+ integrations without writing custom HTTP code.
|
|
10
|
+
|
|
11
|
+
## About
|
|
12
|
+
|
|
13
|
+
Bambuser powers live, on-demand, and one-to-one video commerce for retailers and brands. This package wraps the public Bambuser APIs as native n8n nodes so non-engineers can wire video-commerce events into CRMs, data warehouses, messaging platforms, and analytics tools by dragging nodes onto a canvas. The package is maintained by Bambuser and developed in the open.
|
|
14
|
+
|
|
15
|
+
## Available nodes
|
|
16
|
+
|
|
17
|
+
All nodes share a single `Bambuser API` credential. Each maps to a Bambuser product area and exposes resource/operation pairs in the standard n8n style.
|
|
18
|
+
|
|
19
|
+
**Bambuser Webhook Trigger** — Start a workflow when Bambuser fires an event. The node registers and tears down the subscription against the Bambuser API on workflow activation/deactivation, and re-registers automatically if the webhook URL changes.
|
|
20
|
+
- `show.*`, `broadcast.*`, `product.*`, `product-highlight.*`, `user.*` topics
|
|
21
|
+
- Requires the `WRITE_WEBHOOKS` API-key scope
|
|
22
|
+
|
|
23
|
+
**Bambuser Live** — Read and write live show data mid-workflow.
|
|
24
|
+
- List, create, update, publish, and schedule shows
|
|
25
|
+
- Pin and unpin chat messages
|
|
26
|
+
- Manage product highlights and pull viewer/sales metrics
|
|
27
|
+
|
|
28
|
+
**Bambuser Shoppable Videos** — Create and manage VOD assets.
|
|
29
|
+
- Clip a broadcast into a VOD
|
|
30
|
+
- Manage media assets, captions, and video playlists
|
|
31
|
+
- Query the video library
|
|
32
|
+
|
|
33
|
+
**Bambuser Product Catalog** — Search and manage the product catalog.
|
|
34
|
+
- Search and count products
|
|
35
|
+
- Create, update, delete
|
|
36
|
+
- Suitable for AI enrichment and stock-sync workflows
|
|
37
|
+
|
|
38
|
+
**Bambuser Video Consultation** — Automate one-to-one video calls and connect links.
|
|
39
|
+
- Create connect links
|
|
40
|
+
- Fetch call transcripts
|
|
41
|
+
- Manage agents, appointments, and availability
|
|
42
|
+
|
|
43
|
+
**Bambuser Shopper Data** — Read and erase shopper-data records.
|
|
44
|
+
- List and get records
|
|
45
|
+
- Drive GDPR erasure pipelines from a webhook
|
|
46
|
+
|
|
47
|
+
### Credential
|
|
48
|
+
|
|
49
|
+
The `Bambuser API` credential takes an API key, a region (`EU` or `US`), and an optional base-URL override. The key is sent as `Authorization: Token <key>`. Scopes are granted per-key in the Bambuser dashboard; relevant scopes include `VOD_MANAGE`, `VOD_READ`, `PRODUCT_CATALOG_READ`, `PRODUCT_CATALOG_MANAGE`, `WRITE_WEBHOOKS`, and `READ_SHOPPER_DATA`. Grant only the scopes the workflow needs.
|
|
50
|
+
|
|
51
|
+
## Prerequisites
|
|
52
|
+
|
|
53
|
+
- [n8n](https://n8n.io) 1.57.0 or later
|
|
54
|
+
- Node.js 24 or later (only required for local development of the nodes themselves)
|
|
55
|
+
|
|
56
|
+
## Installation
|
|
57
|
+
|
|
58
|
+
### n8n Cloud and self-hosted (Community Nodes UI)
|
|
59
|
+
|
|
60
|
+
1. Open n8n and go to **Settings → Community Nodes**.
|
|
61
|
+
2. Click **Install** and enter `@bambuser/n8n-nodes-livecommerce`.
|
|
62
|
+
3. Confirm the install. The Bambuser nodes appear in the node panel after a short refresh.
|
|
63
|
+
|
|
64
|
+
See the n8n [community nodes installation guide](https://docs.n8n.io/integrations/community-nodes/installation/) for the full UI walkthrough and the security caveats around third-party nodes.
|
|
65
|
+
|
|
66
|
+
### Self-hosted via npm
|
|
67
|
+
|
|
68
|
+
For self-hosted n8n instances that install community nodes from the file system:
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
cd ~/.n8n/custom
|
|
72
|
+
npm init -y # only if package.json does not already exist
|
|
73
|
+
npm install @bambuser/n8n-nodes-livecommerce
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Restart n8n. The nodes load from `~/.n8n/custom/node_modules`.
|
|
77
|
+
|
|
78
|
+
## Credentials
|
|
79
|
+
|
|
80
|
+
A Bambuser account is required. You can create one for free at [bambuser.com](https://bambuser.com).
|
|
81
|
+
|
|
82
|
+
1. Sign in to your Bambuser dashboard ([EU](https://lcx-eu.bambuser.com/) or [US](https://lcx.bambuser.com/), matching your org's data residency) and open **Settings → API Keys**.
|
|
83
|
+
2. Create a new key and grant the scopes your workflow needs (see the list under [Available nodes](#available-nodes)).
|
|
84
|
+
3. In n8n, create a new credential of type **Bambuser API**:
|
|
85
|
+
- **API Key** — the key from the dashboard.
|
|
86
|
+
- **Region** — `EU` or `US`, matching the org's data residency.
|
|
87
|
+
- **Base URL Override** — leave empty in production. Used for pointing at a non-default API host (staging, a tunnel, a local proxy).
|
|
88
|
+
|
|
89
|
+
The credential is reused across every node in this package.
|
|
90
|
+
|
|
91
|
+
## Compatibility
|
|
92
|
+
|
|
93
|
+
Built and tested against n8n 1.57 and later. Earlier versions are not supported.
|
|
94
|
+
|
|
95
|
+
## Quick start
|
|
96
|
+
|
|
97
|
+
A minimal workflow that reacts to a Bambuser show ending:
|
|
98
|
+
|
|
99
|
+
1. Add a **Bambuser Webhook Trigger** node. Select the `show` topic.
|
|
100
|
+
2. Activate the workflow. The node registers a webhook subscription against the Bambuser API automatically.
|
|
101
|
+
3. Add downstream nodes (Slack, HTTP Request, database, etc.) consuming the event payload.
|
|
102
|
+
|
|
103
|
+
End the show in the Bambuser dashboard. n8n receives the webhook and runs the workflow.
|
|
104
|
+
|
|
105
|
+
## What you can build
|
|
106
|
+
|
|
107
|
+
A few workflows that merchants commonly assemble from these nodes plus n8n's built-in integrations:
|
|
108
|
+
|
|
109
|
+
- **Go-live notification blast** — `show.started` → SMS via Twilio + email via Klaviyo + Instagram Story post.
|
|
110
|
+
- **Automatic show recap** — `show.ended` → pull stats → AI summary → post to Slack and save to Notion.
|
|
111
|
+
- **Live → VOD → CMS pipeline** — `broadcast.ended` → create VOD → AI writes description → push to Contentful or a Shopify product page.
|
|
112
|
+
- **Data warehouse sync** — `show.ended` → append views, carts, and revenue to BigQuery or Google Sheets.
|
|
113
|
+
- **Low-stock alerts** — daily catalog scan → Gemini drafts a restock recommendation → append to a shared Google Doc.
|
|
114
|
+
- **GDPR shopper data erasure** — erasure webhook fires → delete shopper record → log confirmation for the audit trail.
|
|
115
|
+
|
|
116
|
+
See [`examples/workflows/`](./examples/workflows/) for ready-to-import JSON workflows demonstrating each of these patterns.
|
|
117
|
+
|
|
118
|
+
## Examples
|
|
119
|
+
|
|
120
|
+
Ready-to-import workflow JSON files live under [`examples/workflows/`](./examples/workflows/). Import a file from the n8n UI via **Workflows → Import from File**, then attach your Bambuser API credential.
|
|
121
|
+
|
|
122
|
+
## Local development
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
git clone https://github.com/bambuser/n8n-nodes-livecommerce.git
|
|
126
|
+
cd n8n-nodes-livecommerce
|
|
127
|
+
nvm use # picks up .nvmrc → Node 24
|
|
128
|
+
npm install
|
|
129
|
+
npm run build
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
To link the local build into a local n8n install:
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
cd ~/.n8n/custom
|
|
136
|
+
npm link /absolute/path/to/n8n-nodes-livecommerce
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
Restart n8n and the nodes load from the local build directory.
|
|
140
|
+
|
|
141
|
+
### Docker development environment
|
|
142
|
+
|
|
143
|
+
The repo ships a Docker Compose setup that runs n8n with Postgres and bind-mounts the compiled nodes, so a rebuild + restart picks up changes without rebuilding the image.
|
|
144
|
+
|
|
145
|
+
```bash
|
|
146
|
+
cp .env.example .env
|
|
147
|
+
openssl rand -hex 32 # paste into N8N_ENCRYPTION_KEY in .env
|
|
148
|
+
docker compose up --build
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
n8n is available at `http://localhost:5678`.
|
|
152
|
+
|
|
153
|
+
After editing anything under `credentials/`, `lib/`, or `nodes/`:
|
|
154
|
+
|
|
155
|
+
```bash
|
|
156
|
+
npm run build
|
|
157
|
+
docker compose restart n8n
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### Webhook testing with a Cloudflare tunnel
|
|
161
|
+
|
|
162
|
+
Bambuser needs a public HTTPS URL to deliver webhook events. To run n8n behind a free `*.trycloudflare.com` tunnel:
|
|
163
|
+
|
|
164
|
+
```bash
|
|
165
|
+
docker compose -f docker-compose.yml -f docker-compose.tunnel.yml up --build
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
The tunnel URL changes on every restart. To push the new URL to Bambuser, deactivate and reactivate the workflow in the n8n UI — the Bambuser Webhook Trigger node re-registers the subscription with the new URL automatically.
|
|
169
|
+
|
|
170
|
+
## Publishing
|
|
171
|
+
|
|
172
|
+
Releases are tag-driven and run from GitHub Actions with npm provenance.
|
|
173
|
+
|
|
174
|
+
1. A maintainer runs `npm run release` from the repo root. [release-it](https://github.com/release-it/release-it) prompts for a version bump, updates `package.json`, commits, tags, and pushes the tag.
|
|
175
|
+
2. The tag push triggers `.github/workflows/publish.yml`, which builds the package and publishes it to npm under the `@bambuser` scope with an [npm provenance statement](https://docs.npmjs.com/generating-provenance-statements).
|
|
176
|
+
|
|
177
|
+
Both Trusted Publishers (OIDC) and a long-lived `NPM_TOKEN` secret are supported. See the comments in `.github/workflows/publish.yml` for setup.
|
|
178
|
+
|
|
179
|
+
## Resources
|
|
180
|
+
|
|
181
|
+
- [n8n community nodes documentation](https://docs.n8n.io/integrations/community-nodes/)
|
|
182
|
+
- [n8n node-development reference](https://docs.n8n.io/integrations/creating-nodes/)
|
|
183
|
+
- [Bambuser developer documentation](https://bambuser.com/docs)
|
|
184
|
+
- [Sign up for a Bambuser account](https://bambuser.com)
|
|
185
|
+
|
|
186
|
+
## License
|
|
187
|
+
|
|
188
|
+
[MIT](./LICENSE)
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.BambuserApi = void 0;
|
|
4
|
+
const resolveOrigin_1 = require("../lib/resolveOrigin");
|
|
5
|
+
class BambuserApi {
|
|
6
|
+
name = 'bambuserApi';
|
|
7
|
+
displayName = 'Bambuser API';
|
|
8
|
+
icon = 'file:bambuser.svg';
|
|
9
|
+
documentationUrl = 'https://bambuser.com/docs';
|
|
10
|
+
properties = [
|
|
11
|
+
{
|
|
12
|
+
displayName: 'API Key',
|
|
13
|
+
name: 'apiKey',
|
|
14
|
+
type: 'string',
|
|
15
|
+
typeOptions: { password: true },
|
|
16
|
+
default: '',
|
|
17
|
+
required: true,
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
displayName: 'Region',
|
|
21
|
+
name: 'region',
|
|
22
|
+
type: 'options',
|
|
23
|
+
options: [
|
|
24
|
+
{ name: 'EU', value: 'eu' },
|
|
25
|
+
{ name: 'US', value: 'us' },
|
|
26
|
+
],
|
|
27
|
+
default: 'eu',
|
|
28
|
+
required: true,
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
displayName: 'Base URL Override',
|
|
32
|
+
name: 'baseUrl',
|
|
33
|
+
type: 'string',
|
|
34
|
+
default: '',
|
|
35
|
+
placeholder: 'https://liveshopping-api.bambuser.com',
|
|
36
|
+
description: 'Leave empty to use the production URL for the selected region. Set to a stage or local mock URL to override.',
|
|
37
|
+
},
|
|
38
|
+
];
|
|
39
|
+
authenticate = {
|
|
40
|
+
type: 'generic',
|
|
41
|
+
properties: {
|
|
42
|
+
headers: {
|
|
43
|
+
Authorization: '=Token {{$credentials.apiKey}}',
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
test = {
|
|
48
|
+
request: {
|
|
49
|
+
baseURL: resolveOrigin_1.RESOLVE_ORIGIN_EXPRESSION,
|
|
50
|
+
url: '/v1/shows',
|
|
51
|
+
qs: { limit: 1 },
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
exports.BambuserApi = BambuserApi;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 80 80">
|
|
2
|
+
<rect width="80" height="80" fill="#000000"/>
|
|
3
|
+
<g fill="#ffffff" transform="translate(4.5, 19.5)">
|
|
4
|
+
<path d="M4.08921,20.0677 L4.02518,20.0677 L4.02518,22.0405 L0,22.0405 L0,0 L4.18069,0 L4.18069,8.13904 L4.27218,8.13904 C5.1321,6.8177 6.48602,5.79918 8.66328,5.79918 C12.6244,5.79918 15.4512,9.22179 15.4512,14.1493 C15.4512,19.2694 12.7433,22.4993 8.7822,22.4993 C6.5775,22.4993 5.01317,21.545 4.08921,20.0677 Z M11.2156,14.1493 C11.2156,11.2221 10.0446,9.18509 7.71187,9.18509 C5.06806,9.18509 4.08921,11.3139 4.08921,14.241 C4.08921,17.1406 5.53462,18.985 7.86739,18.985 C10.0172,18.985 11.2156,17.1957 11.2156,14.1493 Z"/>
|
|
5
|
+
<path d="M25.8814,20.2802 L25.8174,20.2802 C25.0215,21.4822 23.878,22.409 21.1793,22.409 C17.95,22.409 15.6813,20.7115 15.6813,17.5733 C15.6813,14.0864 18.508,12.9762 22.0118,12.4898 C24.6281,12.1228 25.8265,11.9026 25.8265,10.7005 C25.8265,9.56272 24.9392,8.81947 23.1827,8.81947 C21.2159,8.81947 20.2645,9.52602 20.1364,11.04 L16.4223,11.04 C16.5412,8.26892 18.6087,5.82812 23.2102,5.82812 C27.9397,5.82812 29.8517,7.95694 29.8517,11.6548 L29.8517,19.7021 C29.8517,20.9042 30.0347,21.6107 30.4006,21.8952 L30.4006,22.0511 L26.3754,22.0511 C26.1193,21.73 25.9729,20.9959 25.8814,20.2802 Z M25.9089,16.463 L25.9089,14.0864 C25.1679,14.5177 24.0335,14.7655 22.9906,15.0132 C20.8042,15.5087 19.7339,15.995 19.7339,17.4815 C19.7339,18.9589 20.7127,19.4819 22.1947,19.4819 C24.5915,19.4819 25.9089,18.0046 25.9089,16.463 Z"/>
|
|
6
|
+
<path d="M31.3501,6.22951 L35.3753,6.22951 L35.3753,8.34914 L35.4668,8.34914 C36.3267,6.89935 37.8636,5.78906 40.05,5.78906 C42.0443,5.78906 43.6452,6.89935 44.4136,8.56019 L44.4777,8.56019 C45.5572,6.83512 47.213,5.78906 49.2073,5.78906 C52.528,5.78906 54.4034,7.9454 54.4034,11.3955 L54.4034,22.0304 L50.2227,22.0304 L50.2227,12.1113 C50.2227,10.322 49.3353,9.3952 47.7985,9.3952 C46.0512,9.3952 44.9717,10.7532 44.9717,12.8453 L44.9717,22.0304 L40.791,22.0304 L40.791,12.1113 C40.791,10.322 39.9036,9.3952 38.3667,9.3952 C36.6743,9.3952 35.54,10.7532 35.54,12.8453 L35.54,22.0304 L31.3593,22.0304 L31.3593,6.22951 L31.3501,6.22951 Z"/>
|
|
7
|
+
<path d="M4.08921,37.8041 L4.02518,37.8041 L4.02518,39.7769 L0,39.7769 L0,17.7363 L4.18069,17.7363 L4.18069,25.8754 L4.27218,25.8754 C5.1321,24.554 6.48602,23.5355 8.66328,23.5355 C12.6244,23.5355 15.4512,26.9581 15.4512,31.8856 C15.4512,37.0058 12.7433,40.2357 8.7822,40.2357 C6.5775,40.2357 5.01317,39.2814 4.08921,37.8041 Z M11.2156,31.8856 C11.2156,28.9585 10.0446,26.9214 7.71187,26.9214 C5.06806,26.9214 4.08921,29.0502 4.08921,31.9774 C4.08921,34.8769 5.53462,36.7213 7.86739,36.7213 C10.0172,36.7213 11.2156,34.932 11.2156,31.8856 Z"/>
|
|
8
|
+
<path d="M26.4004,39.7789 L26.4004,37.9253 L26.3089,37.9253 C25.2295,39.3751 24.0036,40.2101 21.7898,40.2101 C18.286,40.2101 16.3192,37.962 16.3192,34.5119 L16.3192,23.9688 L20.4633,23.9688 L20.4633,33.8053 C20.4633,35.6589 21.2958,36.6682 23.0796,36.6682 C25.0465,36.6682 26.2449,35.1909 26.2449,33.0896 L26.2449,23.9688 L30.4256,23.9688 L30.4256,39.7789 L26.4004,39.7789 Z"/>
|
|
9
|
+
<path d="M31.1063,34.6564 L34.9759,34.6564 C35.2504,36.4732 36.5403,37.2807 38.4156,37.2807 C40.2635,37.2807 41.3064,36.5741 41.3064,35.4638 C41.3064,33.9498 39.3121,33.803 37.1623,33.3717 C34.3356,32.8212 31.5728,32.0504 31.5728,28.5911 C31.5728,25.1685 34.3996,23.5352 37.9948,23.5352 C42.1389,23.5352 44.4442,25.5997 44.7553,28.7746 L40.9771,28.7746 C40.7941,27.0771 39.7147,26.4623 37.9308,26.4623 C36.3024,26.4623 35.2229,27.0771 35.2229,28.2149 C35.2229,29.5729 37.3087,29.6922 39.5591,30.1602 C42.2029,30.7107 45.1212,31.454 45.1212,35.1519 C45.1212,38.3267 42.4134,40.2353 38.4797,40.2353 C33.9056,40.2353 31.3533,38.0148 31.1063,34.6564 Z"/>
|
|
10
|
+
<path d="M45.3332,31.8479 C45.3332,27.1591 48.4984,23.5254 53.1091,23.5254 C58.1772,23.5254 60.885,27.4068 60.885,33.05 L49.4499,33.05 C49.7609,35.4816 51.1057,36.9956 53.475,36.9956 C55.1034,36.9956 56.0548,36.2524 56.5214,35.0503 L60.638,35.0503 C60.0525,37.8582 57.5642,40.2256 53.5116,40.2256 C48.288,40.2347 45.3332,36.5644 45.3332,31.8479 Z M49.5139,30.1596 L56.5214,30.1596 C56.4024,28.1592 55.076,26.7645 53.1731,26.7645 C50.9593,26.7645 49.8798,28.095 49.5139,30.1596 Z"/>
|
|
11
|
+
<path d="M65.6228,26.4895 L65.7142,26.4895 C66.6656,24.7002 67.7451,23.7734 69.5839,23.7734 C70.0413,23.7734 70.3249,23.801 70.5719,23.8927 L70.5719,27.5631 L70.4804,27.5631 C67.7451,27.2878 65.7783,28.7376 65.7783,32.0593 L65.7783,39.7671 L61.5976,39.7671 L61.5976,23.9661 L65.6228,23.9661 L65.6228,26.4895 Z"/>
|
|
12
|
+
</g>
|
|
13
|
+
</svg>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.RESOLVE_ORIGIN_EXPRESSION = exports.resolveOrigin = void 0;
|
|
4
|
+
const ORIGIN_BY_REGION = {
|
|
5
|
+
eu: 'https://liveshopping-api-eu.bambuser.com',
|
|
6
|
+
us: 'https://liveshopping-api-us.bambuser.com',
|
|
7
|
+
};
|
|
8
|
+
const resolveOrigin = (baseUrl, region) => (baseUrl || ORIGIN_BY_REGION[region] || ORIGIN_BY_REGION.eu).replace(/\/$/, '');
|
|
9
|
+
exports.resolveOrigin = resolveOrigin;
|
|
10
|
+
// n8n expression form of resolveOrigin, evaluated at request-build time by n8n
|
|
11
|
+
// against `$credentials`. Shared with the BambuserApi credential's baseUrl
|
|
12
|
+
// binding so runtime helper calls and credential URL resolution always agree
|
|
13
|
+
// on the same per-region origins.
|
|
14
|
+
exports.RESOLVE_ORIGIN_EXPRESSION = `={{ ($credentials.baseUrl || "").replace(/\\/$/, "") || ($credentials.region === "us" ? "${ORIGIN_BY_REGION.us}" : "${ORIGIN_BY_REGION.eu}") }}`;
|
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.BambuserCalls = void 0;
|
|
4
|
+
const n8n_workflow_1 = require("n8n-workflow");
|
|
5
|
+
const resolveOrigin_1 = require("../../lib/resolveOrigin");
|
|
6
|
+
const filterEmpty = (obj) => Object.fromEntries(Object.entries(obj).filter(([, v]) => v !== '' && v !== undefined && v !== null));
|
|
7
|
+
const buildOperationHandlers = (ctx, baseUrl) => ({
|
|
8
|
+
// ─── Call ─────────────────────────────────────────────────────────────────
|
|
9
|
+
'call:get': async (i) => ({
|
|
10
|
+
method: 'GET',
|
|
11
|
+
url: `${baseUrl}/calls/${ctx.getNodeParameter('callId', i)}`,
|
|
12
|
+
}),
|
|
13
|
+
'call:getTranscriptions': async (i) => ({
|
|
14
|
+
method: 'GET',
|
|
15
|
+
url: `${baseUrl}/calls/${ctx.getNodeParameter('callId', i)}/transcriptions`,
|
|
16
|
+
qs: filterEmpty({ cursor: ctx.getNodeParameter('cursor', i, '') }),
|
|
17
|
+
}),
|
|
18
|
+
// ─── Connect Link ─────────────────────────────────────────────────────────
|
|
19
|
+
'connectLink:get': async (i) => ({
|
|
20
|
+
method: 'GET',
|
|
21
|
+
url: `${baseUrl}/connect-links/${ctx.getNodeParameter('connectLinkId', i)}`,
|
|
22
|
+
}),
|
|
23
|
+
'connectLink:getByExternalId': async (i) => ({
|
|
24
|
+
method: 'GET',
|
|
25
|
+
url: `${baseUrl}/connect-links`,
|
|
26
|
+
qs: { externalId: ctx.getNodeParameter('externalId', i) },
|
|
27
|
+
}),
|
|
28
|
+
'connectLink:create': async (i) => ({
|
|
29
|
+
method: 'POST',
|
|
30
|
+
url: `${baseUrl}/connect-links`,
|
|
31
|
+
headers: { 'Content-Type': 'application/json' },
|
|
32
|
+
body: JSON.parse(ctx.getNodeParameter('connectLinkBody', i, '{}')),
|
|
33
|
+
}),
|
|
34
|
+
'connectLink:update': async (i) => ({
|
|
35
|
+
method: 'PATCH',
|
|
36
|
+
url: `${baseUrl}/connect-links/${ctx.getNodeParameter('connectLinkId', i)}`,
|
|
37
|
+
headers: { 'Content-Type': 'application/json' },
|
|
38
|
+
body: JSON.parse(ctx.getNodeParameter('connectLinkBody', i, '{}')),
|
|
39
|
+
}),
|
|
40
|
+
'connectLink:updateByExternalId': async (i) => ({
|
|
41
|
+
method: 'PATCH',
|
|
42
|
+
url: `${baseUrl}/connect-links`,
|
|
43
|
+
qs: { externalId: ctx.getNodeParameter('externalId', i) },
|
|
44
|
+
headers: { 'Content-Type': 'application/json' },
|
|
45
|
+
body: JSON.parse(ctx.getNodeParameter('connectLinkBody', i, '{}')),
|
|
46
|
+
}),
|
|
47
|
+
'connectLink:delete': async (i) => ({
|
|
48
|
+
method: 'DELETE',
|
|
49
|
+
url: `${baseUrl}/connect-links/${ctx.getNodeParameter('connectLinkId', i)}`,
|
|
50
|
+
}),
|
|
51
|
+
'connectLink:deleteByExternalId': async (i) => ({
|
|
52
|
+
method: 'DELETE',
|
|
53
|
+
url: `${baseUrl}/connect-links`,
|
|
54
|
+
qs: { externalId: ctx.getNodeParameter('externalId', i) },
|
|
55
|
+
}),
|
|
56
|
+
// ─── Stats ────────────────────────────────────────────────────────────────
|
|
57
|
+
'stats:getAccepted': async (i) => ({
|
|
58
|
+
method: 'GET',
|
|
59
|
+
url: `${baseUrl}/stats/calls/accepted`,
|
|
60
|
+
qs: filterEmpty({
|
|
61
|
+
from: ctx.getNodeParameter('from', i),
|
|
62
|
+
to: ctx.getNodeParameter('to', i, ''),
|
|
63
|
+
excludeShortCalls: ctx.getNodeParameter('excludeShortCalls', i, ''),
|
|
64
|
+
excludeTestCalls: ctx.getNodeParameter('excludeTestCalls', i, ''),
|
|
65
|
+
}),
|
|
66
|
+
}),
|
|
67
|
+
'stats:getMissed': async (i) => ({
|
|
68
|
+
method: 'GET',
|
|
69
|
+
url: `${baseUrl}/stats/calls/missed`,
|
|
70
|
+
qs: filterEmpty({
|
|
71
|
+
from: ctx.getNodeParameter('from', i),
|
|
72
|
+
to: ctx.getNodeParameter('to', i, ''),
|
|
73
|
+
excludeShortQueueTime: ctx.getNodeParameter('excludeShortQueueTime', i, ''),
|
|
74
|
+
}),
|
|
75
|
+
}),
|
|
76
|
+
});
|
|
77
|
+
class BambuserCalls {
|
|
78
|
+
description = {
|
|
79
|
+
// `displayName` is the UI label and may follow public product branding freely.
|
|
80
|
+
// `name` is the persisted node type (`<package>.<name>`) baked into every saved
|
|
81
|
+
// workflow — changing it breaks existing flows, so it is frozen to the internal
|
|
82
|
+
// concept (calls) and must NOT track marketing renames.
|
|
83
|
+
displayName: 'Bambuser Video Consultation',
|
|
84
|
+
name: 'bambuserCalls',
|
|
85
|
+
icon: 'file:bambuser-live.svg',
|
|
86
|
+
group: ['transform'],
|
|
87
|
+
version: 1,
|
|
88
|
+
subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
|
|
89
|
+
description: 'Interact with the Bambuser Video Consultation API',
|
|
90
|
+
defaults: { name: 'Bambuser Video Consultation' },
|
|
91
|
+
inputs: ['main'],
|
|
92
|
+
outputs: ['main'],
|
|
93
|
+
credentials: [{ name: 'bambuserApi', required: true }],
|
|
94
|
+
properties: [
|
|
95
|
+
// ── Resource ───────────────────────────────────────────────────────────
|
|
96
|
+
{
|
|
97
|
+
displayName: 'Resource',
|
|
98
|
+
name: 'resource',
|
|
99
|
+
type: 'options',
|
|
100
|
+
noDataExpression: true,
|
|
101
|
+
options: [
|
|
102
|
+
{ name: 'Call', value: 'call' },
|
|
103
|
+
{ name: 'Connect Link', value: 'connectLink' },
|
|
104
|
+
{ name: 'Stat', value: 'stats' },
|
|
105
|
+
],
|
|
106
|
+
default: 'call',
|
|
107
|
+
},
|
|
108
|
+
// ── Operation: Call ────────────────────────────────────────────────────
|
|
109
|
+
{
|
|
110
|
+
displayName: 'Operation',
|
|
111
|
+
name: 'operation',
|
|
112
|
+
type: 'options',
|
|
113
|
+
noDataExpression: true,
|
|
114
|
+
displayOptions: { show: { resource: ['call'] } },
|
|
115
|
+
options: [
|
|
116
|
+
{ name: 'Get', value: 'get', action: 'Get a call by ID' },
|
|
117
|
+
{ name: 'Get Transcriptions', value: 'getTranscriptions', action: 'Get transcriptions for a call' },
|
|
118
|
+
],
|
|
119
|
+
default: 'get',
|
|
120
|
+
},
|
|
121
|
+
// ── Operation: Connect Link ────────────────────────────────────────────
|
|
122
|
+
{
|
|
123
|
+
displayName: 'Operation',
|
|
124
|
+
name: 'operation',
|
|
125
|
+
type: 'options',
|
|
126
|
+
noDataExpression: true,
|
|
127
|
+
displayOptions: { show: { resource: ['connectLink'] } },
|
|
128
|
+
options: [
|
|
129
|
+
{ name: 'Create', value: 'create', action: 'Create a connect link' },
|
|
130
|
+
{ name: 'Delete', value: 'delete', action: 'Delete a connect link by ID' },
|
|
131
|
+
{ name: 'Delete by External ID', value: 'deleteByExternalId', action: 'Delete a connect link by external ID' },
|
|
132
|
+
{ name: 'Get', value: 'get', action: 'Get a connect link by ID' },
|
|
133
|
+
{ name: 'Get by External ID', value: 'getByExternalId', action: 'Get a connect link by external ID' },
|
|
134
|
+
{ name: 'Update', value: 'update', action: 'Update a connect link by ID' },
|
|
135
|
+
{ name: 'Update by External ID', value: 'updateByExternalId', action: 'Update a connect link by external ID' },
|
|
136
|
+
],
|
|
137
|
+
default: 'get',
|
|
138
|
+
},
|
|
139
|
+
// ── Operation: Stats ───────────────────────────────────────────────────
|
|
140
|
+
{
|
|
141
|
+
displayName: 'Operation',
|
|
142
|
+
name: 'operation',
|
|
143
|
+
type: 'options',
|
|
144
|
+
noDataExpression: true,
|
|
145
|
+
displayOptions: { show: { resource: ['stats'] } },
|
|
146
|
+
options: [
|
|
147
|
+
{ name: 'Get Accepted Calls', value: 'getAccepted', action: 'Get statistics for accepted calls' },
|
|
148
|
+
{ name: 'Get Missed Calls', value: 'getMissed', action: 'Get statistics for missed calls' },
|
|
149
|
+
],
|
|
150
|
+
default: 'getAccepted',
|
|
151
|
+
},
|
|
152
|
+
// ── callId ─────────────────────────────────────────────────────────────
|
|
153
|
+
{
|
|
154
|
+
displayName: 'Call ID',
|
|
155
|
+
name: 'callId',
|
|
156
|
+
type: 'string',
|
|
157
|
+
required: true,
|
|
158
|
+
default: '',
|
|
159
|
+
displayOptions: { show: { resource: ['call'], operation: ['get', 'getTranscriptions'] } },
|
|
160
|
+
},
|
|
161
|
+
// ── connectLinkId ──────────────────────────────────────────────────────
|
|
162
|
+
{
|
|
163
|
+
displayName: 'Connect Link ID',
|
|
164
|
+
name: 'connectLinkId',
|
|
165
|
+
type: 'string',
|
|
166
|
+
required: true,
|
|
167
|
+
default: '',
|
|
168
|
+
displayOptions: {
|
|
169
|
+
show: { resource: ['connectLink'], operation: ['get', 'update', 'delete'] },
|
|
170
|
+
},
|
|
171
|
+
},
|
|
172
|
+
// ── externalId ─────────────────────────────────────────────────────────
|
|
173
|
+
{
|
|
174
|
+
displayName: 'External ID',
|
|
175
|
+
name: 'externalId',
|
|
176
|
+
type: 'string',
|
|
177
|
+
required: true,
|
|
178
|
+
default: '',
|
|
179
|
+
description: 'Caller-supplied external ID for the connect link',
|
|
180
|
+
displayOptions: {
|
|
181
|
+
show: { resource: ['connectLink'], operation: ['getByExternalId', 'updateByExternalId', 'deleteByExternalId'] },
|
|
182
|
+
},
|
|
183
|
+
},
|
|
184
|
+
// ── connectLinkBody (create) ───────────────────────────────────────────
|
|
185
|
+
{
|
|
186
|
+
displayName: 'Connect Link Data (JSON)',
|
|
187
|
+
name: 'connectLinkBody',
|
|
188
|
+
type: 'string',
|
|
189
|
+
required: true,
|
|
190
|
+
default: '{}',
|
|
191
|
+
placeholder: '{"externalId": "my-ID", "title": "My Link", "validTo": "2024-12-31T23:59:59Z"}',
|
|
192
|
+
description: 'JSON body. Supported fields: externalId, title, validFrom, validTo, firstName, lastName, email, queue.',
|
|
193
|
+
typeOptions: { rows: 3 },
|
|
194
|
+
displayOptions: { show: { resource: ['connectLink'], operation: ['create'] } },
|
|
195
|
+
},
|
|
196
|
+
// ── connectLinkBody (update) ───────────────────────────────────────────
|
|
197
|
+
{
|
|
198
|
+
displayName: 'Fields to Update (JSON)',
|
|
199
|
+
name: 'connectLinkBody',
|
|
200
|
+
type: 'string',
|
|
201
|
+
required: true,
|
|
202
|
+
default: '{}',
|
|
203
|
+
placeholder: '{"title": "New Title", "validTo": "2024-12-31T23:59:59Z"}',
|
|
204
|
+
description: 'JSON object with fields to update. Supported fields: title, validFrom, validTo, firstName, lastName, email, queue.',
|
|
205
|
+
typeOptions: { rows: 3 },
|
|
206
|
+
displayOptions: { show: { resource: ['connectLink'], operation: ['update', 'updateByExternalId'] } },
|
|
207
|
+
},
|
|
208
|
+
// ── cursor (call:getTranscriptions) ────────────────────────────────────
|
|
209
|
+
{
|
|
210
|
+
displayName: 'Cursor',
|
|
211
|
+
name: 'cursor',
|
|
212
|
+
type: 'string',
|
|
213
|
+
default: '',
|
|
214
|
+
description: 'Pagination cursor from the previous response',
|
|
215
|
+
displayOptions: { show: { resource: ['call'], operation: ['getTranscriptions'] } },
|
|
216
|
+
},
|
|
217
|
+
// ── from / to (stats) ─────────────────────────────────────────────────
|
|
218
|
+
{
|
|
219
|
+
displayName: 'From',
|
|
220
|
+
name: 'from',
|
|
221
|
+
type: 'string',
|
|
222
|
+
required: true,
|
|
223
|
+
default: '',
|
|
224
|
+
placeholder: '2024-01-01T00:00:00Z',
|
|
225
|
+
description: 'ISO date string — start of range (required)',
|
|
226
|
+
displayOptions: { show: { resource: ['stats'], operation: ['getAccepted', 'getMissed'] } },
|
|
227
|
+
},
|
|
228
|
+
{
|
|
229
|
+
displayName: 'To',
|
|
230
|
+
name: 'to',
|
|
231
|
+
type: 'string',
|
|
232
|
+
default: '',
|
|
233
|
+
placeholder: '2024-12-31T23:59:59Z',
|
|
234
|
+
description: 'ISO date string — end of range (defaults to now)',
|
|
235
|
+
displayOptions: { show: { resource: ['stats'], operation: ['getAccepted', 'getMissed'] } },
|
|
236
|
+
},
|
|
237
|
+
// ── excludeShortCalls / excludeTestCalls (stats:getAccepted) ──────────
|
|
238
|
+
{
|
|
239
|
+
displayName: 'Exclude Short Calls',
|
|
240
|
+
name: 'excludeShortCalls',
|
|
241
|
+
type: 'options',
|
|
242
|
+
options: [
|
|
243
|
+
{ name: 'No', value: '' },
|
|
244
|
+
{ name: 'Yes', value: 'true' },
|
|
245
|
+
],
|
|
246
|
+
default: '',
|
|
247
|
+
displayOptions: { show: { resource: ['stats'], operation: ['getAccepted'] } },
|
|
248
|
+
},
|
|
249
|
+
{
|
|
250
|
+
displayName: 'Exclude Test Calls',
|
|
251
|
+
name: 'excludeTestCalls',
|
|
252
|
+
type: 'options',
|
|
253
|
+
options: [
|
|
254
|
+
{ name: 'No', value: '' },
|
|
255
|
+
{ name: 'Yes', value: 'true' },
|
|
256
|
+
],
|
|
257
|
+
default: '',
|
|
258
|
+
displayOptions: { show: { resource: ['stats'], operation: ['getAccepted'] } },
|
|
259
|
+
},
|
|
260
|
+
// ── excludeShortQueueTime (stats:getMissed) ────────────────────────────
|
|
261
|
+
{
|
|
262
|
+
displayName: 'Exclude Short Queue Time',
|
|
263
|
+
name: 'excludeShortQueueTime',
|
|
264
|
+
type: 'options',
|
|
265
|
+
options: [
|
|
266
|
+
{ name: 'No', value: '' },
|
|
267
|
+
{ name: 'Yes', value: 'true' },
|
|
268
|
+
],
|
|
269
|
+
default: '',
|
|
270
|
+
displayOptions: { show: { resource: ['stats'], operation: ['getMissed'] } },
|
|
271
|
+
},
|
|
272
|
+
],
|
|
273
|
+
usableAsTool: true,
|
|
274
|
+
};
|
|
275
|
+
async execute() {
|
|
276
|
+
const items = this.getInputData();
|
|
277
|
+
const credentials = await this.getCredentials('bambuserApi');
|
|
278
|
+
const origin = (0, resolveOrigin_1.resolveOrigin)(credentials.baseUrl, credentials.region);
|
|
279
|
+
const handlers = buildOperationHandlers(this, `${origin}/v1`);
|
|
280
|
+
const results = await Promise.all(items.map(async (_, i) => {
|
|
281
|
+
const resource = this.getNodeParameter('resource', i);
|
|
282
|
+
const operation = this.getNodeParameter('operation', i);
|
|
283
|
+
const key = `${resource}:${operation}`;
|
|
284
|
+
const handler = handlers[key];
|
|
285
|
+
if (!handler) {
|
|
286
|
+
throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Unknown operation "${operation}" for resource "${resource}"`, { itemIndex: i });
|
|
287
|
+
}
|
|
288
|
+
const requestOptions = await handler(i);
|
|
289
|
+
const responseData = await this.helpers.httpRequestWithAuthentication.call(this, 'bambuserApi', requestOptions) ?? { success: true };
|
|
290
|
+
return { json: responseData };
|
|
291
|
+
}));
|
|
292
|
+
return [results];
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
exports.BambuserCalls = BambuserCalls;
|