@cocreate/api 1.22.4 → 1.23.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.
@@ -1,8 +1,10 @@
1
1
  name: Automated Workflow
2
+
2
3
  on:
3
4
  push:
4
5
  branches:
5
6
  - master
7
+
6
8
  jobs:
7
9
  about:
8
10
  runs-on: ubuntu-latest
@@ -18,27 +20,37 @@ jobs:
18
20
  with:
19
21
  direction: overwrite-github
20
22
  githubToken: "${{ secrets.GITHUB }}"
23
+
21
24
  release:
22
25
  runs-on: ubuntu-latest
23
26
  steps:
24
27
  - name: Checkout
25
- uses: actions/checkout@v3
26
- - name: Setup Node.js
27
- uses: actions/setup-node@v3
28
+ uses: actions/checkout@v4
28
29
  with:
29
- node-version: 14
30
- - name: Semantic Release
31
- uses: cycjimmy/semantic-release-action@v3
32
- id: semantic
30
+ fetch-depth: 0 # Required so semantic-release can trace git tags/history
31
+
32
+ - name: Setup Node.js
33
+ uses: actions/setup-node@v4
33
34
  with:
34
- extra_plugins: |
35
- @semantic-release/changelog
35
+ node-version: 22
36
+
37
+ - name: Install Semantic Release & Plugins
38
+ # Installs semantic-release and its plugins on the runner
39
+ run: |
40
+ npm install -g semantic-release \
41
+ @semantic-release/changelog \
42
+ @semantic-release/npm \
43
+ @semantic-release/github \
36
44
  @semantic-release/git
37
- @semantic-release/github
45
+
46
+ - name: Run Semantic Release (Native)
47
+ id: semantic
48
+ # This will automatically pick up your export default config file in the repository root
49
+ run: npx semantic-release
38
50
  env:
39
- GITHUB_TOKEN: "${{ secrets.GITHUB }}"
51
+ GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
40
52
  NPM_TOKEN: "${{ secrets.NPM_TOKEN }}"
53
+
41
54
  outputs:
42
55
  new_release_published: "${{ steps.semantic.outputs.new_release_published }}"
43
- new_release_version: "${{ steps.semantic.outputs.new_release_version }}"
44
-
56
+ new_release_version: "${{ steps.semantic.outputs.new_release_version }}"
@@ -14,8 +14,7 @@ jobs:
14
14
  env:
15
15
  DRY_RUN: ${{ github.event.inputs.dry_run }}
16
16
  GITHUB_TOKEN: "${{ secrets.GITHUB }}"
17
- NPM_TOKEN: "${{ secrets.NPM_TOKEN }}"
18
-
17
+
19
18
  steps:
20
19
  - name: Checkout
21
20
  uses: actions/checkout@v3
package/CHANGELOG.md CHANGED
@@ -1,3 +1,28 @@
1
+ # [1.23.0](https://github.com/CoCreate-app/CoCreate-api/compare/v1.22.5...v1.23.0) (2026-07-17)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * bump package versions ([c00d388](https://github.com/CoCreate-app/CoCreate-api/commit/c00d388116a8580d7fba19d32e4f963c9399961a))
7
+ * removed post install and add add packages to apprved list ([ae349df](https://github.com/CoCreate-app/CoCreate-api/commit/ae349df51e715e4de1ec26df0a3d0f0cf4e947bb))
8
+ * semantic version handling ([09356af](https://github.com/CoCreate-app/CoCreate-api/commit/09356af9dc8096ba59687cc8edd415da960ef176))
9
+ * update module export syntax in release configuration ([0f7d0c1](https://github.com/CoCreate-app/CoCreate-api/commit/0f7d0c180a143ecb2d035febc8cfdb07f27a46f6))
10
+
11
+
12
+ ### Features
13
+
14
+ * add CoCreate API client and server modules ([9eb47fd](https://github.com/CoCreate-app/CoCreate-api/commit/9eb47fd5506b69678a52afed78c3c7f700947a55))
15
+ * add export mappings for server and client modules in package.json ([146c96b](https://github.com/CoCreate-app/CoCreate-api/commit/146c96b07b51efcceb87ba11a8ca77465caf32f6))
16
+ * enhance API execution flow and improve configuration handling ([f81a7ac](https://github.com/CoCreate-app/CoCreate-api/commit/f81a7acb459d7f7a24ce4de9b9613016af297bdd))
17
+ * update automated workflow and release configuration for improved semantic release handling ([d7caf5d](https://github.com/CoCreate-app/CoCreate-api/commit/d7caf5db97dcc53ffecee43beb5842dcb903466b))
18
+
19
+ ## [1.22.5](https://github.com/CoCreate-app/CoCreate-api/compare/v1.22.4...v1.22.5) (2025-12-26)
20
+
21
+
22
+ ### Bug Fixes
23
+
24
+ * update worklow ([45cb3bc](https://github.com/CoCreate-app/CoCreate-api/commit/45cb3bc8b3aa8328a7dbce98a2b908eb2ba14a2e))
25
+
1
26
  ## [1.22.4](https://github.com/CoCreate-app/CoCreate-api/compare/v1.22.3...v1.22.4) (2025-05-01)
2
27
 
3
28
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cocreate/api",
3
- "version": "1.22.4",
3
+ "version": "1.23.0",
4
4
  "description": "A simple api helper component in vanilla javascript used by JavaScript developers to create thirdparty api intergrations. CoCreate-api includes the client component and server side for api processing. Thirdparty apis can be accessible using HTML5 attributes and/or JavaScript API. ",
5
5
  "keywords": [
6
6
  "thirdparty-api-intergration",
@@ -13,7 +13,6 @@
13
13
  "cocreate-framework",
14
14
  "no-code",
15
15
  "low-code",
16
- "collaborative-framework",
17
16
  "realtime",
18
17
  "realtime-framework",
19
18
  "collaboration",
@@ -27,8 +26,7 @@
27
26
  "scripts": {
28
27
  "start": "npx webpack --config webpack.config.js",
29
28
  "build": "npx webpack --mode=production --config webpack.config.js",
30
- "dev": "npx webpack --config webpack.config.js --watch",
31
- "postinstall": "node -e \"const { execSync } = require('child_process'); try { execSync('coc --version', { stdio: 'ignore' }); } catch (error) { try { execSync('npm install -g @cocreate/cli', { stdio: 'inherit' }); console.log('Installed \"@cocreate/cli\" globally.'); } catch (error) { console.error('Failed to install \"@cocreate/cli\" globally:', error); } }\""
29
+ "dev": "npx webpack --config webpack.config.js --watch"
32
30
  },
33
31
  "repository": {
34
32
  "type": "git",
@@ -44,22 +42,44 @@
44
42
  "type": "GitHub Sponsors ❤",
45
43
  "url": "https://github.com/sponsors/CoCreate-app"
46
44
  },
47
- "main": "./src/index.js",
48
- "devDependencies": {
49
- "css-loader": "^5.1.3",
50
- "esbuild": "^0.25.2",
51
- "esbuild-loader": "^4.3.0",
52
- "mini-css-extract-plugin": "^1.5.0",
53
- "webpack": "^5.24.4",
54
- "webpack-cli": "^4.5.0",
55
- "webpack-log": "^3.0.1"
45
+ "type": "module",
46
+ "main": "./src/server.js",
47
+ "browser": "./src/client.js",
48
+ "exports": {
49
+ ".": {
50
+ "node": "./src/server.js",
51
+ "deno": "./src/server.js",
52
+ "browser": "./src/client.js",
53
+ "default": "./src/server.js"
54
+ }
56
55
  },
57
56
  "dependencies": {
58
- "@cocreate/actions": "^1.21.1",
59
- "@cocreate/crud-client": "^1.34.2",
60
- "@cocreate/element-prototype": "^1.29.1",
61
- "@cocreate/render": "^1.45.2",
62
- "@cocreate/socket-client": "^1.40.2",
63
- "@cocreate/utils": "^1.39.0"
57
+ "@cocreate/actions": "^1.21.4",
58
+ "@cocreate/crud-client": "^1.34.6",
59
+ "@cocreate/element-prototype": "^1.31.4",
60
+ "@cocreate/render": "^1.46.1",
61
+ "@cocreate/socket-client": "^1.40.5",
62
+ "@cocreate/utils": "^1.42.2"
63
+ },
64
+ "devDependencies": {
65
+ "@cocreate/webpack": "^1.4.3"
66
+ },
67
+ "allowScripts": {
68
+ "@cocreate/actions@1.21.4": true,
69
+ "@cocreate/api@1.22.5": true,
70
+ "@cocreate/config@1.13.4": true,
71
+ "@cocreate/crud-client@1.34.6": true,
72
+ "@cocreate/element-prototype@1.31.4": true,
73
+ "@cocreate/elements@1.42.9": true,
74
+ "@cocreate/filter@1.33.6": true,
75
+ "@cocreate/indexeddb@1.23.4": true,
76
+ "@cocreate/local-storage@1.16.5": true,
77
+ "@cocreate/observer@1.19.0": true,
78
+ "@cocreate/organizations@1.30.1": true,
79
+ "@cocreate/render@1.46.1": true,
80
+ "@cocreate/socket-client@1.40.5": true,
81
+ "@cocreate/utils@1.42.2": true,
82
+ "@cocreate/uuid@1.12.4": true,
83
+ "@cocreate/webpack@1.4.3": true
64
84
  }
65
85
  }
package/release.config.js CHANGED
@@ -1,4 +1,4 @@
1
- module.exports = {
1
+ export default {
2
2
  dryRun: false,
3
3
  branches: ["master"],
4
4
  plugins: [
@@ -10,12 +10,20 @@ module.exports = {
10
10
  changelogFile: "CHANGELOG.md",
11
11
  },
12
12
  ],
13
- "@semantic-release/npm",
14
- "@semantic-release/github",
13
+ "@semantic-release/npm",
15
14
  [
16
- "@semantic-release/git",
15
+ "@semantic-release/github",
17
16
  {
17
+ successComment: false,
18
+ failTitle: false,
19
+ },
20
+ ],
21
+ [
22
+ "@semantic-release/git",
23
+ {
24
+ // Only stage and commit the changelog and package.json
18
25
  assets: ["CHANGELOG.md", "package.json"],
26
+ message: "chore(release): ${nextRelease.version} [skip ci]",
19
27
  },
20
28
  ],
21
29
  ],
package/src/server.js ADDED
@@ -0,0 +1,651 @@
1
+ /********************************************************************************
2
+ * Copyright (C) 2023 CoCreate and Contributors.
3
+ *
4
+ * This program is free software: you can redistribute it and/or modify
5
+ * it under the terms of the GNU Affero General Public License as published
6
+ * by the Free Software Foundation, either version 3 of the License, or
7
+ * (at your option) any later version.
8
+ *
9
+ * This program is distributed in the hope that it will be useful,
10
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ * GNU Affero General Public License for more details.
13
+ *
14
+ * You should have received a copy of the GNU Affero General Public License
15
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
16
+ *
17
+ ********************************************************************************/
18
+
19
+ // Commercial Licensing Information:
20
+ // For commercial use of this software without the copyleft provisions of the AGPLv3,
21
+ // you must obtain a commercial license from CoCreate LLC.
22
+ // For details, visit <https://cocreate.app/licenses/> or contact us at sales@cocreate.app.
23
+
24
+ import crypto from 'node:crypto';
25
+ import { URL, URLSearchParams } from 'node:url';
26
+ import Config from '@cocreate/config';
27
+ import { getValueFromObject, objectToSearchParams } from '@cocreate/utils';
28
+
29
+ // Core module references bound dynamically upon initialization
30
+ let server = null;
31
+ let wsManager = null;
32
+ let crud = null;
33
+
34
+ const NativeSandbox = {
35
+ Math,
36
+ Number,
37
+ String,
38
+ Boolean,
39
+ Date,
40
+ JSON,
41
+ Array,
42
+ Object,
43
+ parseInt,
44
+ parseFloat,
45
+ encodeURIComponent,
46
+ decodeURIComponent,
47
+ btoa: (str) => Buffer.from(String(str)).toString("base64"),
48
+ atob: (b64) => Buffer.from(String(b64), "base64").toString("utf-8"),
49
+ concat: (...args) => args.flat().map(arg => typeof arg === 'object' ? JSON.stringify(arg) : arg).join(''),
50
+ formUrlencode: (obj) => {
51
+ if (!obj || typeof obj !== 'object') return String(obj || '');
52
+ const params = new URLSearchParams();
53
+ for (const [key, val] of Object.entries(obj)) {
54
+ params.append(key, typeof val === 'object' ? JSON.stringify(val) : String(val));
55
+ }
56
+ return params.toString();
57
+ }
58
+ };
59
+
60
+ const API = {
61
+ init,
62
+ executeApi,
63
+ request,
64
+ generateJWT,
65
+ verifySignature,
66
+ makeHttpRequest,
67
+ processOperators,
68
+ processOperator,
69
+ getApiConfig,
70
+ normalizeConfig
71
+ };
72
+
73
+ export async function init(Server) {
74
+ server = Server;
75
+ wsManager = server.wsManager;
76
+ crud = server.crud;
77
+
78
+ wsManager.on("api", (data) => {
79
+ executeApi(data);
80
+ });
81
+
82
+ wsManager.on("endpoint", (data) => {
83
+ executeApi(data);
84
+ });
85
+
86
+ return API;
87
+ }
88
+
89
+ export function normalizeConfig(apiConfig, name) {
90
+ let key = null;
91
+ let url = null;
92
+ let headers = {};
93
+
94
+ if (typeof apiConfig === 'string') {
95
+ key = apiConfig;
96
+ } else if (apiConfig && typeof apiConfig === 'object') {
97
+ url = apiConfig.url;
98
+ headers = apiConfig.headers || {};
99
+
100
+ if (apiConfig.key) {
101
+ if (typeof apiConfig.key === 'string') {
102
+ key = apiConfig.key;
103
+ } else if (typeof apiConfig.key === 'object') {
104
+ key = apiConfig.key.apiKey || apiConfig.key.key || JSON.stringify(apiConfig.key);
105
+ }
106
+ } else if (apiConfig.apiKey) {
107
+ key = apiConfig.apiKey;
108
+ }
109
+
110
+ if (apiConfig.url) url = apiConfig.url;
111
+ if (apiConfig.headers) headers = { ...headers, ...apiConfig.headers };
112
+ }
113
+
114
+ return { key, url, headers };
115
+ }
116
+
117
+ export async function executeApi(data, parentContext = null) {
118
+ try {
119
+ // SECURITY FIREWALL: Parent contexts and authenticated socket boundaries take absolute priority
120
+ // over raw payload elements to prevent config-driven cross-tenant data leaks or privilege escalations.
121
+ const orgId = parentContext?.organization_id || data.socket?.organization_id || data.organization_id;
122
+ const host = parentContext?.host || data.socket?.host || data.host;
123
+
124
+ // PATH A: Native Execution of a Pre-prepared HTTP Request Object
125
+ if (data.url && data.method) {
126
+ const timeout = data.timeout || 15000;
127
+ const options = {
128
+ method: data.method.toUpperCase(),
129
+ headers: data.headers || {},
130
+ timeout
131
+ };
132
+
133
+ if (data.body) {
134
+ options.body = typeof data.body === 'object' ? JSON.stringify(data.body) : data.body;
135
+ }
136
+
137
+ const response = await makeHttpRequest(data.url, options);
138
+ const responsePayload = await response.json();
139
+
140
+ if (data.socket && wsManager) {
141
+ data.response = responsePayload;
142
+ wsManager.send(data);
143
+ }
144
+ return responsePayload;
145
+ }
146
+
147
+ // PATH B: Standard Dynamic Configuration Resolve & Execute
148
+ if (!data.method || !data.endpoint) {
149
+ throw new Error("API execution request is missing 'method' or 'endpoint' directives.");
150
+ }
151
+
152
+ const providerName = data.method.split(/[./]/)[0];
153
+ const httpMethod = data.endpoint.split(" ")[0].toUpperCase();
154
+
155
+ const endpointQuery = {
156
+ method: 'object.read',
157
+ array: 'endpoints',
158
+ organization_id: orgId,
159
+ host: host, // Carry host forward
160
+ $filter: {
161
+ query: { endpoint: data.endpoint, organization_id: orgId },
162
+ limit: 1
163
+ }
164
+ };
165
+ const endpointResponse = await crud.send(endpointQuery);
166
+ const endpointConfig = endpointResponse?.object?.[0] || {};
167
+
168
+ // Fetch provider credentials with full environmental scope intact
169
+ const rawApiConfig = await getApiConfig({ organization_id: orgId, host }, providerName);
170
+ if (!rawApiConfig) {
171
+ throw new Error(`Connection setting not registered for provider: '${providerName}'.`);
172
+ }
173
+ const apiConfig = normalizeConfig(rawApiConfig, providerName);
174
+
175
+ // Share parent context if executing within a nested macro frame
176
+ const apiContext = parentContext || {
177
+ data: data,
178
+ event: {},
179
+ actions: [],
180
+ organization_id: orgId,
181
+ host: host, // Lock host context into AST evaluation loop
182
+ crud,
183
+ wsManager,
184
+ key: apiConfig.key,
185
+ config: apiConfig
186
+ };
187
+
188
+ const actions = endpointConfig.actions || endpointConfig.action;
189
+ if (actions) {
190
+ if (Array.isArray(actions)) {
191
+ for (let i = 0; i < actions.length; i++) {
192
+ const result = await processOperators(apiContext, actions[i]);
193
+ apiContext.actions.push(result); // Saved flattened memory profile
194
+ }
195
+ const finalMacroResult = apiContext.actions[apiContext.actions.length - 1];
196
+ if (finalMacroResult !== undefined) {
197
+ data[providerName] = finalMacroResult;
198
+ }
199
+ } else {
200
+ const result = await processOperators(apiContext, actions);
201
+ apiContext.actions.push(result);
202
+ if (result !== undefined) {
203
+ data[providerName] = result;
204
+ }
205
+ }
206
+
207
+ if (data.socket && wsManager) {
208
+ wsManager.send(data);
209
+ }
210
+ return;
211
+ }
212
+
213
+ // --- Standard Outbound Construction Path ---
214
+ if (!apiConfig.url) {
215
+ throw new Error(`Configuration missing 'url' base for provider '${providerName}'.`);
216
+ }
217
+
218
+ let url = apiConfig.url.endsWith("/") ? apiConfig.url.slice(0, -1) : apiConfig.url;
219
+ let endpointPath = endpointConfig.path || data.endpoint.split(" ")[1];
220
+ url += endpointPath.startsWith("/") ? endpointPath : `/${endpointPath}`;
221
+
222
+ if (data[providerName] && data[providerName].$searchParams) {
223
+ url += objectToSearchParams(data[providerName].$searchParams);
224
+ delete data[providerName].$searchParams;
225
+ }
226
+
227
+ let headers = { ...(apiConfig.headers || {}), ...(endpointConfig.headers || {}) };
228
+ headers = await processOperators(apiContext, headers);
229
+
230
+ const timeout = 15000;
231
+ let options = { method: httpMethod, headers, timeout };
232
+
233
+ if (!["GET", "HEAD"].includes(httpMethod) && data[providerName]) {
234
+ const bodyPayload = await processOperators(apiContext, data[providerName]);
235
+
236
+ if (typeof bodyPayload === 'object' && bodyPayload !== null) {
237
+ options.body = JSON.stringify(bodyPayload);
238
+ if (!headers["Content-Type"]) {
239
+ options.headers["Content-Type"] = "application/json; charset=utf-8";
240
+ }
241
+ } else {
242
+ options.body = String(bodyPayload);
243
+ }
244
+ }
245
+
246
+ const response = await makeHttpRequest(url, options);
247
+ const responsePayload = await response.json();
248
+ data[providerName] = responsePayload;
249
+
250
+ if (data.socket && wsManager) {
251
+ wsManager.send(data);
252
+ }
253
+ return responsePayload;
254
+ } catch (error) {
255
+ data.error = error.message;
256
+ if (error.response) {
257
+ data.status = error.response.status;
258
+ data.statusText = error.response.statusText;
259
+ data.responseData = error.response.data;
260
+ }
261
+ if (data.socket && wsManager) {
262
+ wsManager.send(data);
263
+ }
264
+ throw error;
265
+ }
266
+ }
267
+
268
+ export async function request(req, res) {
269
+ try {
270
+ const urlObject = new URL(`http://${req.headers.host}${req.url}`);
271
+ const hostname = urlObject.hostname;
272
+
273
+ let organization;
274
+ try {
275
+ organization = await crud.getOrganization({ host: hostname });
276
+ } catch (error) {
277
+ res.writeHead(404, { "Content-Type": "application/json" });
278
+ return res.end(JSON.stringify({ error: "Organization mapping not found" }));
279
+ }
280
+
281
+ const webhookPath = urlObject.pathname.replace(/^\/webhooks\//, "");
282
+
283
+ const webhookQuery = {
284
+ method: 'object.read',
285
+ array: 'webhooks',
286
+ organization_id: organization._id,
287
+ host: hostname, // Maintain active host environment
288
+ $filter: {
289
+ query: {
290
+ path: webhookPath,
291
+ organization_id: organization._id
292
+ },
293
+ limit: 1
294
+ }
295
+ };
296
+ const webhookResponse = await crud.send(webhookQuery);
297
+ const webhookConfig = webhookResponse?.object?.[0];
298
+
299
+ if (!webhookConfig) {
300
+ res.writeHead(404, { "Content-Type": "application/json" });
301
+ return res.end(JSON.stringify({ error: `Webhook configuration for path '${webhookPath}' not found.` }));
302
+ }
303
+
304
+ let rawBody = "";
305
+ await new Promise((resolve, reject) => {
306
+ req.on("data", (chunk) => { rawBody += chunk.toString(); });
307
+ req.on("end", resolve);
308
+ req.on("error", reject);
309
+ });
310
+
311
+ const queryParams = Object.fromEntries(urlObject.searchParams.entries());
312
+
313
+ let parsedEvent = {};
314
+ if (rawBody) {
315
+ try {
316
+ parsedEvent = JSON.parse(rawBody);
317
+ } catch {
318
+ parsedEvent = { rawBody };
319
+ }
320
+ }
321
+
322
+ const context = {
323
+ data: {},
324
+ event: parsedEvent,
325
+ actions: [],
326
+ request: req,
327
+ response: res,
328
+ rawBody: rawBody,
329
+ host: hostname, // Propagate verified edge hostname
330
+ organization: organization,
331
+ organization_id: organization._id,
332
+ crud: crud,
333
+ wsManager: wsManager
334
+ };
335
+
336
+ context.request.query = queryParams;
337
+
338
+ if (webhookConfig.authenticate) {
339
+ const auth = webhookConfig.authenticate;
340
+ if (auth.method === "webhooks.constructEvent" && Array.isArray(auth.parameters)) {
341
+ const resolvedParams = await processOperators(context, [...auth.parameters]);
342
+ const payload = resolvedParams[0];
343
+ const signature = resolvedParams[1];
344
+ const secret = resolvedParams[2];
345
+
346
+ if (!verifySignature(payload, signature, secret, "sha256")) {
347
+ res.writeHead(401, { "Content-Type": "application/json" });
348
+ return res.end(JSON.stringify({ error: "Unauthorized access: signature verification failed." }));
349
+ }
350
+ }
351
+ }
352
+
353
+ let executeOperations = null;
354
+ const actionConfig = webhookConfig.action;
355
+
356
+ if (actionConfig) {
357
+ if (typeof actionConfig === 'string') {
358
+ const actionName = await processOperators(context, actionConfig);
359
+ if (actionName && webhookConfig.actions?.[actionName]) {
360
+ executeOperations = webhookConfig.actions[actionName];
361
+ }
362
+ } else {
363
+ executeOperations = actionConfig;
364
+ }
365
+ }
366
+
367
+ if (executeOperations) {
368
+ if (Array.isArray(executeOperations)) {
369
+ for (let i = 0; i < executeOperations.length; i++) {
370
+ const stepResult = await processOperators(context, executeOperations[i]);
371
+ context.actions.push(stepResult);
372
+ }
373
+ } else if (typeof executeOperations === 'object' && executeOperations !== null) {
374
+ const stepResult = await processOperators(context, [executeOperations]);
375
+ context.actions.push(stepResult);
376
+ }
377
+ }
378
+
379
+ // Native Stream Inspection: Fall back to default 200 OK only if actions did not write or end the stream
380
+ if (res && !res.writableEnded) {
381
+ res.writeHead(200, { "Content-Type": "application/json" });
382
+ res.end(JSON.stringify({
383
+ message: "Webhook processed successfully",
384
+ payload: context.actions[context.actions.length - 1] || {}
385
+ }));
386
+ }
387
+ } catch (error) {
388
+ if (res && !res.writableEnded) {
389
+ res.writeHead(400, { "Content-Type": "application/json" });
390
+ res.end(JSON.stringify({ error: "Invalid webhook request format", message: error.message }));
391
+ }
392
+ }
393
+ }
394
+
395
+ export async function processOperators(context, execute) {
396
+ if (Array.isArray(execute)) {
397
+ let evaluatedArray = [];
398
+ for (let index = 0; index < execute.length; index++) {
399
+ evaluatedArray[index] = await processOperators(context, execute[index]);
400
+ }
401
+ return evaluatedArray;
402
+ } else if (typeof execute === "object" && execute !== null) {
403
+ const keys = Object.keys(execute);
404
+ const operatorKey = keys.find(k => k.startsWith("$") && !["$storage", "$database", "$array", "$filter"].includes(k));
405
+
406
+ if (operatorKey && keys.length === 1) {
407
+ return await processOperator(context, operatorKey, execute[operatorKey]);
408
+ }
409
+
410
+ let evaluatedObject = {};
411
+ for (let key of keys) {
412
+ let finalKey = key;
413
+
414
+ if (key.startsWith("$") && !["$storage", "$database", "$array", "$filter"].includes(key)) {
415
+ let resolvedKey = await processOperator(context, key);
416
+ if (typeof resolvedKey === "string" || typeof resolvedKey === "number") {
417
+ finalKey = String(resolvedKey);
418
+ }
419
+ }
420
+
421
+ let finalValue = execute[key];
422
+ if (typeof finalValue === "string" && finalValue.startsWith("$") && !["$storage", "$database", "$array", "$filter"].includes(finalValue)) {
423
+ finalValue = await processOperator(context, finalValue);
424
+ } else if (Array.isArray(finalValue) || (typeof finalValue === "object" && finalValue !== null)) {
425
+ finalValue = await processOperators(context, finalValue);
426
+ }
427
+
428
+ evaluatedObject[finalKey] = finalValue;
429
+ }
430
+
431
+ return evaluatedObject;
432
+ } else if (typeof execute === "string" && execute.startsWith("$") && !["$storage", "$database", "$array", "$filter"].includes(execute)) {
433
+ return await processOperator(context, execute);
434
+ }
435
+
436
+ return execute;
437
+ }
438
+
439
+ export async function processOperator(context, operator, args) {
440
+ if (args === undefined) {
441
+ if (operator.startsWith("$data.")) {
442
+ return getValueFromObject(context.data, operator.substring(6));
443
+ } else if (operator === "$data") {
444
+ return context.data;
445
+ } else if (operator.startsWith("$event.")) {
446
+ const result = getValueFromObject(context.event, operator.substring(7));
447
+ return await processOperators(context, result);
448
+ } else if (operator === "$event") {
449
+ return await processOperators(context, context.event);
450
+ } else if (operator.startsWith("$actions.")) {
451
+ return getValueFromObject(context.actions, operator.substring(9));
452
+ } else if (operator === "$actions") {
453
+ return context.actions;
454
+ } else if (operator.startsWith("$request.")) {
455
+ return getValueFromObject(context.request, operator.substring(9));
456
+ } else if (operator === "$request") {
457
+ return context.request;
458
+ } else if (operator.startsWith("$header.")) {
459
+ const headerKey = operator.substring(8).toLowerCase();
460
+ return context.request?.headers?.[headerKey];
461
+ } else if (operator === "$rawBody") {
462
+ return context.rawBody;
463
+ }
464
+ }
465
+
466
+ if (operator === "$jwt") {
467
+ const evaluatedContext = args !== undefined ? await processOperators(context, args) : {};
468
+ const privateKey = evaluatedContext.privateKey;
469
+ if (!privateKey) throw new Error("Missing cryptographic signing key inside $jwt context parameters");
470
+ return generateJWT(evaluatedContext, privateKey);
471
+ }
472
+
473
+ const cleanPath = operator.startsWith("$") ? operator.substring(1) : operator;
474
+ const parts = cleanPath.split('.');
475
+
476
+ const Sandbox = {
477
+ ...NativeSandbox,
478
+ crud: context.crud,
479
+ socket: context.wsManager,
480
+ wsManager: context.wsManager,
481
+ request: context.request,
482
+ response: context.response,
483
+ key: context.key,
484
+ config: context.config,
485
+ organization_id: context.organization_id,
486
+ host: context.host, // Seal active host into AST sandbox environment
487
+ api: {
488
+ send: async (apiData) => {
489
+ return await executeApi(apiData, context);
490
+ }
491
+ }
492
+ };
493
+
494
+ let target = Sandbox[parts[0]];
495
+ let parentContext = Sandbox;
496
+
497
+ if (target !== undefined) {
498
+ for (let i = 1; i < parts.length; i++) {
499
+ parentContext = target;
500
+ target = target[parts[i]];
501
+ if (target === undefined) break;
502
+ }
503
+
504
+ if (typeof target === 'function') {
505
+ let argsArray = [];
506
+ if (args !== undefined) {
507
+ // Check if the argument is explicitly formatted using our human-friendly parameter operators
508
+ if (typeof args === 'object' && args !== null && (args.$args !== undefined || args.$arguments !== undefined)) {
509
+ const explicitArgs = args.$args !== undefined ? args.$args : args.$arguments;
510
+
511
+ if (Array.isArray(explicitArgs)) {
512
+ // Multi-parameter routing: Evaluate each index independently
513
+ argsArray = [];
514
+ for (let i = 0; i < explicitArgs.length; i++) {
515
+ argsArray.push(await processOperators(context, explicitArgs[i]));
516
+ }
517
+ } else {
518
+ // Single-parameter fallback: Spelled out explicitly but contains a single expression
519
+ const evaluatedSingle = await processOperators(context, explicitArgs);
520
+ argsArray = [evaluatedSingle];
521
+ }
522
+ } else {
523
+ // Standard / Implicit routing: Treat the entire value as a single, isolated argument (arrays behave like arrays!)
524
+ const evaluatedSingle = await processOperators(context, args);
525
+ argsArray = [evaluatedSingle];
526
+ }
527
+ }
528
+
529
+ // SECURITY FIREWALL: Forcefully overwrite parameters for local system calls
530
+ // to align with the sealed context of the authenticated user.
531
+ if (parentContext === context.crud || parentContext === context.wsManager) {
532
+ if (argsArray.length === 0) {
533
+ argsArray.push({
534
+ organization_id: context.organization_id,
535
+ host: context.host
536
+ });
537
+ } else if (argsArray.length > 0 && typeof argsArray[0] === 'object' && argsArray[0] !== null) {
538
+ argsArray[0].organization_id = context.organization_id;
539
+ argsArray[0].host = context.host; // Lock environment isolation parameters down
540
+ }
541
+ }
542
+
543
+ return await target.apply(parentContext, argsArray);
544
+ } else if (target !== undefined) {
545
+ return target;
546
+ }
547
+ }
548
+
549
+ return operator;
550
+ }
551
+
552
+ export function generateJWT(config, secret) {
553
+ try {
554
+ const header = config.header || { alg: 'HS256', typ: 'JWT' };
555
+ const payload = config.payload || {};
556
+
557
+ const encodedHeader = Buffer.from(JSON.stringify(header)).toString('base64url');
558
+ const encodedPayload = Buffer.from(JSON.stringify(payload)).toString('base64url');
559
+
560
+ const signatureInput = `${encodedHeader}.${encodedPayload}`;
561
+
562
+ const hmac = crypto.createHmac('sha256', secret);
563
+ hmac.update(signatureInput);
564
+ const signature = hmac.digest('base64url');
565
+
566
+ return `${signatureInput}.${signature}`;
567
+ } catch (err) {
568
+ console.error('[@cocreate/api] Failed to generate JWT:', err);
569
+ throw err;
570
+ }
571
+ }
572
+
573
+ export function verifySignature(payload, signature, secret, algorithm = 'sha256') {
574
+ try {
575
+ const hmac = crypto.createHmac(algorithm, secret);
576
+ hmac.update(payload);
577
+ const expectedSignature = hmac.digest('hex');
578
+
579
+ return crypto.timingSafeEqual(
580
+ Buffer.from(expectedSignature),
581
+ Buffer.from(signature)
582
+ );
583
+ } catch (err) {
584
+ console.error('[@cocreate/api] Failed signature verification:', err);
585
+ return false;
586
+ }
587
+ }
588
+
589
+ export async function makeHttpRequest(url, options) {
590
+ let controller, timeoutId;
591
+ if (global.AbortController) {
592
+ controller = new global.AbortController();
593
+ timeoutId = setTimeout(() => controller.abort(), options.timeout || 15000);
594
+ options.signal = controller.signal;
595
+ }
596
+
597
+ const fetchFn = global.fetch;
598
+ if (typeof fetchFn !== "function") {
599
+ throw new Error("System runtime environment is missing a valid HTTP fetch implementation.");
600
+ }
601
+
602
+ try {
603
+ const response = await fetchFn(url, options);
604
+ if (timeoutId) clearTimeout(timeoutId);
605
+
606
+ if (!response.ok) {
607
+ const text = await response.text();
608
+ const error = new Error(`HTTP error! Status: ${response.status} ${response.statusText}`);
609
+ error.response = {
610
+ status: response.status,
611
+ statusText: response.statusText,
612
+ data: text
613
+ };
614
+ throw error;
615
+ }
616
+ return response;
617
+ } catch (error) {
618
+ if (timeoutId) clearTimeout(timeoutId);
619
+ throw error;
620
+ }
621
+ }
622
+
623
+ export async function getApiConfig(data, name) {
624
+ let orgId = data.organization_id;
625
+ let host = data.host;
626
+
627
+ if (!orgId) {
628
+ const organization = await crud.getOrganization(data); // uses host inside data to lookup org
629
+ if (organization.error) throw new Error(organization.error);
630
+ orgId = organization._id;
631
+ }
632
+
633
+ const query = {
634
+ method: 'object.read',
635
+ array: 'apis',
636
+ organization_id: orgId,
637
+ host: host, // Keep environment host isolation contact
638
+ $filter: {
639
+ query: { name: name, organization_id: orgId },
640
+ limit: 1
641
+ }
642
+ };
643
+
644
+ const response = await crud.send(query);
645
+ const apiConfig = response?.object?.[0];
646
+
647
+ if (!apiConfig) throw new Error(`Integration settings are missing for provider: '${name}'`);
648
+ return apiConfig;
649
+ }
650
+
651
+ export default API;
File without changes