n8n-nodes-sudomock 0.4.0 → 0.5.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
@@ -7,9 +7,11 @@ n8n community node for the SudoMock API. Integrate mockup rendering into your n8
7
7
  ## Features
8
8
 
9
9
  ### Account Operations
10
+
10
11
  - **Get Account Info**: Retrieve account details, subscription plan, and usage statistics
11
12
 
12
13
  ### Mockup Management
14
+
13
15
  - **Upload PSD**: Upload PSD templates from a URL
14
16
  - **List Mockups**: List all your uploaded mockup templates with filtering and pagination
15
17
  - **Get Mockup**: Get detailed information about a specific mockup template
@@ -17,6 +19,7 @@ n8n community node for the SudoMock API. Integrate mockup rendering into your n8
17
19
  - **Delete Mockup**: Delete a specific mockup template
18
20
 
19
21
  ### 2D Mockups
22
+
20
23
  - **2D: Create Mockup**: Create a reusable 2D mockup from an image
21
24
  - **2D: Get Mockup**: Check preparation status and get ready print areas
22
25
  - **2D: List Mockups**: List your 2D mockups
@@ -25,18 +28,27 @@ n8n community node for the SudoMock API. Integrate mockup rendering into your n8
25
28
  - **2D: Delete Mockup**: Delete a 2D mockup
26
29
 
27
30
  ### Rendering
28
- - **Render Mockup**: Generate mockups by combining templates with your designs. Optionally run asynchronously with the **Run Asynchronously** toggle, which returns a `job_id` to track with **Get Job**.
29
- - **Render Video**: Turn a mockup (or an existing image URL) into a short product video (always asynchronous). Pick a duration valid for the model, optional audio/motion, an optional one-off webhook, and optionally **Wait for Completion** to return the finished clip.
31
+
32
+ - **Render Mockup**: Generate mockups with artwork, personalized text, or both. Text overrides support styled segments, fonts, size, color, stroke color, and overflow, shrink, or clip fitting. Optionally run asynchronously with the **Run Asynchronously** toggle, which returns a `job_id` to track with **Get Job**.
33
+ - **Render Video**: Turn a mockup (or an existing image URL) into a short product video (always asynchronous). Choose a supported duration, optional audio/motion, an optional one-off webhook, and optionally **Wait for Completion** to return the finished clip.
34
+
35
+ ### Fonts and Order Artwork
36
+
37
+ - **Fonts**: List available fonts, fetch one by UUID, upload a custom TTF or OTF from a public URL, and delete your custom fonts
38
+ - **Order Artwork**: Store customer artwork and previews for fulfillment, then delete them by URL or mockup UUID when needed
30
39
 
31
40
  ### Async Jobs
41
+
32
42
  - **Get Job**: Get the status and result of an async render, upload, video, or 2D mockup job by its `job_id`. Terminal statuses are `succeeded`, `failed`, and `cancelled` (pending jobs report `queued`). On success it surfaces `result_url` (render/video) or `mockup_uuid` (upload and 2D creation). Optionally **Wait for Completion** to poll until the job finishes.
33
- - **List Jobs**: List your async render, upload, and video jobs (keyset paginated, with optional `kind` and Mockup UUID filters).
43
+ - **List Jobs**: List your async render, upload, video, and 2D jobs (keyset paginated, with optional `kind` and Mockup UUID filters).
34
44
 
35
45
  ### Webhooks
36
- Manage webhook endpoints that receive a signed HTTPS request the moment a job finishes:
46
+
47
+ - **SudoMock Trigger**: Start a workflow from selected SudoMock events with automatic signature verification
48
+ - Manage webhook endpoints that receive a signed HTTPS request the moment a job finishes:
37
49
  - **Webhook: List Endpoints**
38
50
  - **Webhook: Get Endpoint**
39
- - **Webhook: Create Endpoint** (set an optional description and choose any of the six canonical events, or leave empty for all)
51
+ - **Webhook: Create Endpoint** (set an optional description and choose any current event, or leave empty for all)
40
52
  - **Webhook: Update Endpoint**
41
53
  - **Webhook: Delete Endpoint**
42
54
  - **Webhook: Rotate Secret**
@@ -46,7 +58,7 @@ Manage webhook endpoints that receive a signed HTTPS request the moment a job fi
46
58
  - **Webhook: Replay Failed Deliveries**
47
59
  - **Webhook: Events Feed** (recent deliveries across all of your endpoints)
48
60
 
49
- Canonical events: `render.succeeded`, `render.failed`, `upload.succeeded`, `video.succeeded`, `video.failed`, `webhook.test`. Deliveries are HMAC-signed; see https://sudomock.com/docs/api/webhooks for the current signature header and verification details.
61
+ Canonical events: `render.succeeded`, `render.failed`, `upload.succeeded`, `video.succeeded`, `video.failed`, `2d_mockup.ready`, `2d_mockup.rejected`, `2d_mockup.failed`, `2d_render.succeeded`, `2d_render.failed`, `webhook.test`. Deliveries are signed; see https://sudomock.com/docs/api/webhooks for verification details.
50
62
 
51
63
  ## Installation
52
64
 
@@ -89,12 +101,14 @@ Restart n8n after installation.
89
101
  Retrieve your account information, subscription details, and credit usage.
90
102
 
91
103
  **Output:**
104
+
92
105
  - Account: UUID, email, name, created date
93
106
  - Subscription: Plan name, status, billing period
94
107
  - Usage: Credits used/remaining, billing dates
95
108
  - API Key: Name, creation date, last used, total requests
96
109
 
97
110
  **Example Output:**
111
+
98
112
  ```json
99
113
  {
100
114
  "success": true,
@@ -124,16 +138,19 @@ Retrieve your account information, subscription details, and credit usage.
124
138
  Upload a PSD template from a public URL.
125
139
 
126
140
  **Parameters:**
141
+
127
142
  - **PSD File URL** (required): Public URL to your PSD file (up to Adobe's official PSD file size limit)
128
143
  - **Template Name** (optional): Custom name for the template (auto-generated if not provided)
129
144
 
130
145
  **Example:**
146
+
131
147
  ```
132
148
  PSD URL: https://storage.example.com/mockup.psd
133
149
  Name: T-Shirt Mockup Front
134
150
  ```
135
151
 
136
152
  **Output:**
153
+
137
154
  - Mockup UUID (use for rendering)
138
155
  - Smart object UUIDs and details
139
156
  - Thumbnail URLs
@@ -145,6 +162,7 @@ Name: T-Shirt Mockup Front
145
162
  List all your uploaded mockup templates with optional filtering and pagination.
146
163
 
147
164
  **Parameters:**
165
+
148
166
  - **Return All**: Fetch all mockups or limit results
149
167
  - **Limit**: Number of results (1-100, default: 20)
150
168
  - **Additional Options**:
@@ -155,6 +173,7 @@ List all your uploaded mockup templates with optional filtering and pagination.
155
173
  - Sort Order: `asc` or `desc`
156
174
 
157
175
  **Example:**
176
+
158
177
  ```
159
178
  Limit: 20
160
179
  Sort By: Created At
@@ -171,9 +190,11 @@ Each mockup as a separate item with full details (UUID, name, smart objects, thu
171
190
  Retrieve detailed information about a specific mockup template.
172
191
 
173
192
  **Parameters:**
193
+
174
194
  - **Mockup UUID** (required): UUID of the mockup to retrieve
175
195
 
176
196
  **Example:**
197
+
177
198
  ```
178
199
  Mockup UUID: c315f78f-d2c7-4541-b240-a9372842de94
179
200
  ```
@@ -188,10 +209,12 @@ Complete mockup details including all smart objects, thumbnails, and metadata.
188
209
  Update the name of a mockup template.
189
210
 
190
211
  **Parameters:**
212
+
191
213
  - **Mockup UUID** (required): UUID of the mockup to update
192
214
  - **New Name** (required): New name for the template
193
215
 
194
216
  **Example:**
217
+
195
218
  ```
196
219
  Mockup UUID: c315f78f-d2c7-4541-b240-a9372842de94
197
220
  New Name: Updated T-Shirt Mockup
@@ -207,6 +230,7 @@ Updated mockup details with the new name.
207
230
  Generate a mockup by filling smart objects with your designs.
208
231
 
209
232
  **Parameters:**
233
+
210
234
  - **Mockup UUID** (required): UUID from Upload PSD response
211
235
  - **Smart Objects** (required): One or more smart objects to fill
212
236
  - Smart Object UUID
@@ -227,6 +251,7 @@ Generate a mockup by filling smart objects with your designs.
227
251
  - Export Label: Custom label for file naming
228
252
 
229
253
  **Example:**
254
+
230
255
  ```
231
256
  Mockup UUID: abc123-def456
232
257
  Smart Object 1:
@@ -243,6 +268,7 @@ Export Options:
243
268
  ```
244
269
 
245
270
  **Output:**
271
+
246
272
  ```json
247
273
  {
248
274
  "success": true,
@@ -263,9 +289,11 @@ Export Options:
263
289
  Delete a specific mockup template.
264
290
 
265
291
  **Parameters:**
292
+
266
293
  - **Mockup UUID** (required): UUID of the mockup to delete
267
294
 
268
295
  **Example:**
296
+
269
297
  ```
270
298
  Mockup UUID: c315f78f-d2c7-4541-b240-a9372842de94
271
299
  ```
@@ -295,6 +323,7 @@ Create must reach `ready` through **2D: Get Mockup** before **2D: Render Mockup*
295
323
  Turn a mockup (or an existing image URL) into a short product video. Always asynchronous: returns a `job_id` you can track with **Get Job**.
296
324
 
297
325
  **Parameters:**
326
+
298
327
  - **Input Mode**: `Render Mockup` (render a mockup with your designs first) or `Animate Image URL` (animate a public image directly)
299
328
  - **Mockup UUID** (required in Render Mockup mode): UUID of the mockup to animate
300
329
  - **Smart Objects** (required in Render Mockup mode): one or more smart objects with a Design URL and Fit Mode
@@ -318,6 +347,7 @@ The 202 acknowledgement (`job_id`, `kind`, `status`, `status_url`, `estimated_cr
318
347
  Track an asynchronous render, upload, video, or 2D mockup job by its `job_id`.
319
348
 
320
349
  **Parameters:**
350
+
321
351
  - **Job ID** (required): the `job_id` from an async render, upload, video, or 2D mockup request
322
352
  - **Wait for Completion**: poll until a terminal status (`succeeded`, `failed`, `cancelled`)
323
353
  - **Poll Timeout (Seconds)**: max wait when Wait for Completion is on
@@ -332,6 +362,7 @@ The job object. On success it surfaces `result_url` / `resultUrl` (render/video)
332
362
  List your async render, upload, and video jobs (newest first, keyset paginated).
333
363
 
334
364
  **Parameters (all optional, under Filters):**
365
+
335
366
  - **Kind**: `render`, `upload`, or `video`
336
367
  - **Mockup UUID**: only jobs derived from this source mockup
337
368
  - **Limit**: 1-50 (default 20)
@@ -349,6 +380,7 @@ List your async render, upload, and video jobs (newest first, keyset paginated).
349
380
  Manage webhook endpoints and their deliveries. See the [Webhooks](#webhooks) feature list above for the full operation set and the canonical event types.
350
381
 
351
382
  **Common parameters:**
383
+
352
384
  - **Webhook Endpoint ID** (required for get/update/delete/rotate/test/deliveries/replay/replay-failed)
353
385
  - **Endpoint URL** + **Description** (create) and **Events** (create/update): leave Events empty to subscribe to all
354
386
  - **Delivery ID** (replay)
@@ -361,9 +393,11 @@ Manage webhook endpoints and their deliveries. See the [Webhooks](#webhooks) fea
361
393
  Ready-to-use n8n workflows are available in the [`examples/`](./examples) folder:
362
394
 
363
395
  ### 1. Complete API Test Workflow
396
+
364
397
  **File**: [`examples/complete-test-workflow.json`](./examples/complete-test-workflow.json)
365
398
 
366
399
  Tests the core mockup operations in sequence:
400
+
367
401
  - Get Account Info → Upload PSD → Get Mockup → List Mockups
368
402
  - Render Mockup → Update Name → Verify Update → Delete Mockup
369
403
 
@@ -372,9 +406,11 @@ Perfect for verifying your setup and understanding the complete workflow.
372
406
  [View Details →](./examples/README.md#1-complete-api-test-complete-test-workflowjson)
373
407
 
374
408
  ### 2. Rate Limit Handling Workflow
409
+
375
410
  **File**: [`examples/rate-limit-test-workflow.json`](./examples/rate-limit-test-workflow.json)
376
411
 
377
412
  Demonstrates proper rate limit error handling with automatic retry logic:
413
+
378
414
  - Detects 429 errors
379
415
  - Extracts retry-after value
380
416
  - Waits and automatically retries
@@ -383,9 +419,11 @@ Demonstrates proper rate limit error handling with automatic retry logic:
383
419
  [View Details →](./examples/README.md#2-rate-limit-error-handling-test-rate-limit-test-workflowjson)
384
420
 
385
421
  ### 3. Sequential Batch Render Workflow
422
+
386
423
  **File**: [`examples/batch-render-workflow.json`](./examples/batch-render-workflow.json)
387
424
 
388
425
  Demonstrates the power of n8n workflows with sequential batch processing:
426
+
389
427
  - Renders 10 mockups one after another
390
428
  - Uses different designs for each render
391
429
  - 2-second delay between renders to avoid rate limits
@@ -398,11 +436,13 @@ Perfect for understanding batch processing and workflow automation.
398
436
  ### Quick Start with Examples
399
437
 
400
438
  1. Import a workflow:
439
+
401
440
  ```
402
441
  n8n UI → Workflows → Import from File → Select workflow JSON
403
442
  ```
404
443
 
405
444
  2. Configure credentials:
445
+
406
446
  ```
407
447
  Click any SudoMock node → Credentials → Select your API key
408
448
  ```
@@ -481,12 +521,12 @@ Unauthenticated requests (IP-based): **30 requests per minute**
481
521
 
482
522
  ### Concurrent Request Limits (Plan-Based)
483
523
 
484
- | Plan | Concurrent Renders | Concurrent Uploads |
485
- |------|-------------------|--------------------|
486
- | Free | 1 | 1 |
487
- | Starter | 3 | 2 |
488
- | Pro | 10 | 5 |
489
- | Scale | 25 | 10 |
524
+ | Plan | Concurrent Renders | Concurrent Uploads |
525
+ | ------- | ------------------ | ------------------ |
526
+ | Free | 1 | 1 |
527
+ | Starter | 3 | 2 |
528
+ | Pro | 10 | 5 |
529
+ | Scale | 25 | 10 |
490
530
 
491
531
  ### Rate Limit Error Handling
492
532
 
@@ -495,6 +535,7 @@ The node automatically detects and handles two types of rate limit errors:
495
535
  #### 1. Request Rate Limit Exceeded (1000 RPM)
496
536
 
497
537
  **Error Output:**
538
+
498
539
  ```json
499
540
  {
500
541
  "error": "Rate limit exceeded (1000 requests/minute). Please retry after 42 seconds.",
@@ -513,6 +554,7 @@ The node automatically detects and handles two types of rate limit errors:
513
554
  #### 2. Concurrent Limit Exceeded (Plan-Based)
514
555
 
515
556
  **Error Output:**
557
+
516
558
  ```json
517
559
  {
518
560
  "error": "Concurrent render limit reached (11/10). Please wait 5 seconds and try again.",
@@ -549,11 +591,13 @@ The node automatically detects and handles two types of rate limit errors:
549
591
  ## Error Handling
550
592
 
551
593
  All operations support the **Continue On Fail** option in n8n. When enabled:
594
+
552
595
  - Errors are captured as output items
553
596
  - Workflow continues to next item
554
597
  - Error details included in output
555
598
 
556
599
  **Example Error Output:**
600
+
557
601
  ```json
558
602
  {
559
603
  "error": "Rate limit exceeded. Retry after 42 seconds.",
@@ -2,7 +2,24 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.SudoMock = void 0;
4
4
  const n8n_workflow_1 = require("n8n-workflow");
5
+ const webhooks_1 = require("./webhooks");
5
6
  const TERMINAL_JOB_STATUSES = ['succeeded', 'failed', 'cancelled'];
7
+ function format2DPrintAreas(printAreas) {
8
+ return printAreas.map((area) => ({
9
+ points: [
10
+ [area.point1X, area.point1Y],
11
+ [area.point2X, area.point2Y],
12
+ [area.point3X, area.point3Y],
13
+ [area.point4X, area.point4Y],
14
+ ],
15
+ }));
16
+ }
17
+ function parseJsonArray(value, name) {
18
+ const parsed = typeof value === 'string' ? JSON.parse(value) : value;
19
+ if (!Array.isArray(parsed))
20
+ throw new Error(`${name} must be a JSON array`);
21
+ return parsed;
22
+ }
6
23
  /**
7
24
  * Poll GET /api/v1/jobs/{job_id} until the job reaches a terminal status
8
25
  * (succeeded, failed, cancelled) or the timeout elapses. Uses a capped backoff.
@@ -61,7 +78,7 @@ class SudoMock {
61
78
  {
62
79
  name: '2D: Create Mockup',
63
80
  value: 'create2DMockup',
64
- description: 'Create a 2D mockup asynchronously. Returns a job immediately; use 2D: Get Mockup until status is ready before rendering.',
81
+ description: 'Create a 2D mockup now or queue it for background processing',
65
82
  action: 'Create a 2D mockup',
66
83
  },
67
84
  {
@@ -94,12 +111,48 @@ class SudoMock {
94
111
  description: 'Delete a 2D mockup',
95
112
  action: 'Delete a 2D mockup',
96
113
  },
114
+ {
115
+ name: 'Artwork: Delete Stored Files',
116
+ value: 'deleteArtworks',
117
+ description: 'Delete stored order artwork by URL or mockup UUID',
118
+ action: 'Delete stored artwork',
119
+ },
120
+ {
121
+ name: 'Artwork: Store Files',
122
+ value: 'storeArtworks',
123
+ description: 'Store customer artwork and an optional preview for order fulfillment',
124
+ action: 'Store artwork',
125
+ },
97
126
  {
98
127
  name: 'Delete Mockup',
99
128
  value: 'deleteMockup',
100
129
  description: 'Delete a specific mockup template',
101
130
  action: 'Delete a mockup',
102
131
  },
132
+ {
133
+ name: 'Font: Delete Custom Font',
134
+ value: 'deleteFont',
135
+ description: 'Delete one of your uploaded fonts',
136
+ action: 'Delete a custom font',
137
+ },
138
+ {
139
+ name: 'Font: Get Font',
140
+ value: 'getFont',
141
+ description: 'Get one available font by UUID',
142
+ action: 'Get a font',
143
+ },
144
+ {
145
+ name: 'Font: List Fonts',
146
+ value: 'listFonts',
147
+ description: 'List system fonts and your uploaded fonts',
148
+ action: 'List fonts',
149
+ },
150
+ {
151
+ name: 'Font: Upload From URL',
152
+ value: 'uploadFont',
153
+ description: 'Upload a custom TTF or OTF font from a public URL',
154
+ action: 'Upload a custom font',
155
+ },
103
156
  {
104
157
  name: 'Get Account Info',
105
158
  value: 'getAccountInfo',
@@ -292,6 +345,14 @@ class SudoMock {
292
345
  placeholder: 'T-Shirt Front',
293
346
  description: 'Optional human-readable name for the new 2D mockup',
294
347
  },
348
+ {
349
+ displayName: 'Run Asynchronously',
350
+ name: 'twoDCreateIsAsync',
351
+ type: 'boolean',
352
+ displayOptions: { show: { operation: ['create2DMockup'] } },
353
+ default: false,
354
+ description: 'Whether to queue creation and return a job_id (kind: 2d_create) immediately',
355
+ },
295
356
  {
296
357
  displayName: 'Mockup UUID',
297
358
  name: 'twoDMockupUuid',
@@ -299,12 +360,7 @@ class SudoMock {
299
360
  required: true,
300
361
  displayOptions: {
301
362
  show: {
302
- operation: [
303
- 'get2DMockup',
304
- 'set2DPrintAreas',
305
- 'render2DMockup',
306
- 'delete2DMockup',
307
- ],
363
+ operation: ['get2DMockup', 'set2DPrintAreas', 'render2DMockup', 'delete2DMockup'],
308
364
  },
309
365
  },
310
366
  default: '',
@@ -318,15 +374,14 @@ class SudoMock {
318
374
  typeOptions: {
319
375
  multipleValues: true,
320
376
  },
321
- required: true,
322
377
  displayOptions: {
323
378
  show: {
324
- operation: ['set2DPrintAreas'],
379
+ operation: ['create2DMockup', 'set2DPrintAreas'],
325
380
  },
326
381
  },
327
382
  default: {},
328
383
  placeholder: 'Add Print Area',
329
- description: 'Four coordinate pairs that define each print area quad',
384
+ description: "Optional during creation; required when replacing a mockup's print areas",
330
385
  options: [
331
386
  {
332
387
  name: 'items',
@@ -710,6 +765,14 @@ class SudoMock {
710
765
  },
711
766
  ],
712
767
  },
768
+ {
769
+ displayName: 'Run Asynchronously',
770
+ name: 'twoDRenderIsAsync',
771
+ type: 'boolean',
772
+ displayOptions: { show: { operation: ['render2DMockup'] } },
773
+ default: false,
774
+ description: 'Whether to queue the render and return a job_id (kind: 2d_render) immediately',
775
+ },
713
776
  // ============================================
714
777
  // UPLOAD PSD PARAMETERS
715
778
  // ============================================
@@ -1001,6 +1064,14 @@ class SudoMock {
1001
1064
  },
1002
1065
  ],
1003
1066
  },
1067
+ {
1068
+ displayName: 'Text Layers',
1069
+ name: 'textLayers',
1070
+ type: 'json',
1071
+ displayOptions: { show: { operation: ['render'] } },
1072
+ default: '[]',
1073
+ description: 'Optional text layer override array. Each item needs uuid plus text or segments, and can include font, font_size, color, stroke_color, and fit (overflow, shrink, or clip).',
1074
+ },
1004
1075
  {
1005
1076
  displayName: 'Export Options',
1006
1077
  name: 'exportOptions',
@@ -1190,9 +1261,21 @@ class SudoMock {
1190
1261
  name: 'fit',
1191
1262
  type: 'options',
1192
1263
  options: [
1193
- { name: 'Fill', value: 'fill', description: 'Stretch to fill entire area' },
1194
- { name: 'Contain', value: 'contain', description: 'Fit inside, may leave space' },
1195
- { name: 'Cover', value: 'cover', description: 'Fill area, may crop edges (recommended)' },
1264
+ {
1265
+ name: 'Fill',
1266
+ value: 'fill',
1267
+ description: 'Stretch to fill entire area',
1268
+ },
1269
+ {
1270
+ name: 'Contain',
1271
+ value: 'contain',
1272
+ description: 'Fit inside, may leave space',
1273
+ },
1274
+ {
1275
+ name: 'Cover',
1276
+ value: 'cover',
1277
+ description: 'Fill area, may crop edges (recommended)',
1278
+ },
1196
1279
  ],
1197
1280
  default: 'cover',
1198
1281
  description: 'How to fit the design in the smart object bounds',
@@ -1249,7 +1332,11 @@ class SudoMock {
1249
1332
  name: 'motion',
1250
1333
  type: 'options',
1251
1334
  options: [
1252
- { name: 'Ambient', value: 'ambient', description: 'Subtle ambient motion (default)' },
1335
+ {
1336
+ name: 'Ambient',
1337
+ value: 'ambient',
1338
+ description: 'Subtle ambient motion (default)',
1339
+ },
1253
1340
  {
1254
1341
  name: 'Showcase',
1255
1342
  value: 'showcase',
@@ -1365,6 +1452,8 @@ class SudoMock {
1365
1452
  { name: 'Render', value: 'render' },
1366
1453
  { name: 'Upload', value: 'upload' },
1367
1454
  { name: 'Video', value: 'video' },
1455
+ { name: '2D Create', value: '2d_create' },
1456
+ { name: '2D Render', value: '2d_render' },
1368
1457
  ],
1369
1458
  default: 'render',
1370
1459
  description: 'Only return jobs of this kind',
@@ -1397,6 +1486,173 @@ class SudoMock {
1397
1486
  ],
1398
1487
  },
1399
1488
  // ============================================
1489
+ // FONT PARAMETERS
1490
+ // ============================================
1491
+ {
1492
+ displayName: 'Font UUID',
1493
+ name: 'fontUuid',
1494
+ type: 'string',
1495
+ required: true,
1496
+ displayOptions: { show: { operation: ['getFont', 'deleteFont'] } },
1497
+ default: '',
1498
+ description: 'Font UUID from Font: List Fonts',
1499
+ },
1500
+ {
1501
+ displayName: 'Font URL',
1502
+ name: 'fontUrl',
1503
+ type: 'string',
1504
+ required: true,
1505
+ displayOptions: { show: { operation: ['uploadFont'] } },
1506
+ default: '',
1507
+ placeholder: 'https://example.com/fonts/Brand-Bold.ttf',
1508
+ description: 'Public URL of a TTF or OTF font file',
1509
+ },
1510
+ {
1511
+ displayName: 'License Confirmed',
1512
+ name: 'fontLicenseConfirmed',
1513
+ type: 'boolean',
1514
+ displayOptions: { show: { operation: ['uploadFont'] } },
1515
+ default: false,
1516
+ description: 'Whether you confirm you have the right to use and embed this font',
1517
+ },
1518
+ {
1519
+ displayName: 'Filters',
1520
+ name: 'fontFilters',
1521
+ type: 'collection',
1522
+ placeholder: 'Add Filter',
1523
+ displayOptions: { show: { operation: ['listFonts'] } },
1524
+ default: {},
1525
+ options: [
1526
+ {
1527
+ displayName: 'Page',
1528
+ name: 'page',
1529
+ type: 'number',
1530
+ typeOptions: { minValue: 1 },
1531
+ default: 1,
1532
+ },
1533
+ {
1534
+ displayName: 'Results Per Page',
1535
+ name: 'perPage',
1536
+ type: 'number',
1537
+ typeOptions: { minValue: 1, maxValue: 100 },
1538
+ default: 50,
1539
+ },
1540
+ {
1541
+ displayName: 'Category',
1542
+ name: 'category',
1543
+ type: 'string',
1544
+ default: '',
1545
+ },
1546
+ {
1547
+ displayName: 'Search',
1548
+ name: 'search',
1549
+ type: 'string',
1550
+ default: '',
1551
+ description: 'Filter by font family name',
1552
+ },
1553
+ {
1554
+ displayName: 'Scope',
1555
+ name: 'scope',
1556
+ type: 'options',
1557
+ options: [
1558
+ { name: 'All', value: 'all' },
1559
+ { name: 'System', value: 'system' },
1560
+ { name: 'Custom', value: 'custom' },
1561
+ ],
1562
+ default: 'all',
1563
+ },
1564
+ ],
1565
+ },
1566
+ // ============================================
1567
+ // ARTWORK PARAMETERS
1568
+ // ============================================
1569
+ {
1570
+ displayName: 'Mockup UUID',
1571
+ name: 'artworkMockupUuid',
1572
+ type: 'string',
1573
+ required: true,
1574
+ displayOptions: { show: { operation: ['storeArtworks'] } },
1575
+ default: '',
1576
+ description: 'Mockup UUID the stored artwork belongs to',
1577
+ },
1578
+ {
1579
+ displayName: 'Artwork Items',
1580
+ name: 'artworkItems',
1581
+ type: 'fixedCollection',
1582
+ typeOptions: { multipleValues: true },
1583
+ displayOptions: { show: { operation: ['storeArtworks'] } },
1584
+ default: {},
1585
+ placeholder: 'Add Artwork',
1586
+ description: 'Up to 10 artwork files to keep for order fulfillment',
1587
+ options: [
1588
+ {
1589
+ name: 'items',
1590
+ displayName: 'Artwork',
1591
+ values: [
1592
+ {
1593
+ displayName: 'Smart Object UUID',
1594
+ name: 'smartObjectUuid',
1595
+ type: 'string',
1596
+ required: true,
1597
+ default: '',
1598
+ },
1599
+ {
1600
+ displayName: 'Base64 Artwork',
1601
+ name: 'base64',
1602
+ type: 'string',
1603
+ required: true,
1604
+ default: '',
1605
+ description: 'Raw base64 image bytes without a data URL prefix',
1606
+ },
1607
+ ],
1608
+ },
1609
+ ],
1610
+ },
1611
+ {
1612
+ displayName: 'Preview URL',
1613
+ name: 'artworkPreviewUrl',
1614
+ type: 'string',
1615
+ displayOptions: { show: { operation: ['storeArtworks'] } },
1616
+ default: '',
1617
+ description: 'Optional render URL to keep with the order',
1618
+ },
1619
+ {
1620
+ displayName: 'Delete By',
1621
+ name: 'artworkDeleteMode',
1622
+ type: 'options',
1623
+ displayOptions: { show: { operation: ['deleteArtworks'] } },
1624
+ options: [
1625
+ { name: 'URLs', value: 'urls' },
1626
+ { name: 'Mockup UUID', value: 'mockup' },
1627
+ ],
1628
+ default: 'urls',
1629
+ },
1630
+ {
1631
+ displayName: 'URLs',
1632
+ name: 'artworkDeleteUrls',
1633
+ type: 'json',
1634
+ required: true,
1635
+ displayOptions: {
1636
+ show: { operation: ['deleteArtworks'], artworkDeleteMode: ['urls'] },
1637
+ },
1638
+ default: '[]',
1639
+ description: 'JSON array of stored artwork or preview URLs to delete',
1640
+ },
1641
+ {
1642
+ displayName: 'Mockup UUID',
1643
+ name: 'artworkDeleteMockupUuid',
1644
+ type: 'string',
1645
+ required: true,
1646
+ displayOptions: {
1647
+ show: {
1648
+ operation: ['deleteArtworks'],
1649
+ artworkDeleteMode: ['mockup'],
1650
+ },
1651
+ },
1652
+ default: '',
1653
+ description: 'Delete all stored artwork belonging to this mockup',
1654
+ },
1655
+ // ============================================
1400
1656
  // WEBHOOK ENDPOINT PARAMETERS
1401
1657
  // ============================================
1402
1658
  {
@@ -1434,14 +1690,7 @@ class SudoMock {
1434
1690
  operation: ['webhookCreate', 'webhookUpdate'],
1435
1691
  },
1436
1692
  },
1437
- options: [
1438
- { name: 'render.succeeded', value: 'render.succeeded' },
1439
- { name: 'render.failed', value: 'render.failed' },
1440
- { name: 'upload.succeeded', value: 'upload.succeeded' },
1441
- { name: 'video.succeeded', value: 'video.succeeded' },
1442
- { name: 'video.failed', value: 'video.failed' },
1443
- { name: 'webhook.test', value: 'webhook.test' },
1444
- ],
1693
+ options: webhooks_1.WEBHOOK_EVENT_OPTIONS,
1445
1694
  default: [],
1446
1695
  description: 'Events this endpoint receives. Leave empty to subscribe to all events, including ones added in the future.',
1447
1696
  },
@@ -1545,14 +1794,7 @@ class SudoMock {
1545
1794
  displayName: 'Event Type',
1546
1795
  name: 'eventType',
1547
1796
  type: 'options',
1548
- options: [
1549
- { name: 'render.succeeded', value: 'render.succeeded' },
1550
- { name: 'render.failed', value: 'render.failed' },
1551
- { name: 'upload.succeeded', value: 'upload.succeeded' },
1552
- { name: 'video.succeeded', value: 'video.succeeded' },
1553
- { name: 'video.failed', value: 'video.failed' },
1554
- { name: 'webhook.test', value: 'webhook.test' },
1555
- ],
1797
+ options: webhooks_1.WEBHOOK_EVENT_OPTIONS,
1556
1798
  default: 'render.succeeded',
1557
1799
  description: 'Only return deliveries for this event type',
1558
1800
  },
@@ -1598,14 +1840,7 @@ class SudoMock {
1598
1840
  displayName: 'Event Type',
1599
1841
  name: 'eventType',
1600
1842
  type: 'options',
1601
- options: [
1602
- { name: 'render.succeeded', value: 'render.succeeded' },
1603
- { name: 'render.failed', value: 'render.failed' },
1604
- { name: 'upload.succeeded', value: 'upload.succeeded' },
1605
- { name: 'video.succeeded', value: 'video.succeeded' },
1606
- { name: 'video.failed', value: 'video.failed' },
1607
- { name: 'webhook.test', value: 'webhook.test' },
1608
- ],
1843
+ options: webhooks_1.WEBHOOK_EVENT_OPTIONS,
1609
1844
  default: 'render.succeeded',
1610
1845
  description: 'Only return deliveries for this event type',
1611
1846
  },
@@ -1818,6 +2053,8 @@ class SudoMock {
1818
2053
  else if (operation === 'create2DMockup') {
1819
2054
  const sourceMode = this.getNodeParameter('twoDSourceMode', i);
1820
2055
  const name = this.getNodeParameter('twoDName', i, '');
2056
+ const isAsync = this.getNodeParameter('twoDCreateIsAsync', i, false);
2057
+ const printAreas = this.getNodeParameter('twoDSetPrintAreas.items', i, []);
1821
2058
  const body = {};
1822
2059
  if (sourceMode === 'base64') {
1823
2060
  body.source_base64 = this.getNodeParameter('twoDSourceBase64', i);
@@ -1828,13 +2065,22 @@ class SudoMock {
1828
2065
  if (name) {
1829
2066
  body.name = name;
1830
2067
  }
2068
+ if (isAsync) {
2069
+ body.is_async = true;
2070
+ }
2071
+ if (printAreas.length > 0) {
2072
+ body.print_areas = format2DPrintAreas(printAreas);
2073
+ }
1831
2074
  const response = await this.helpers.httpRequestWithAuthentication.call(this, 'sudoMockApi', {
1832
2075
  method: 'POST',
1833
2076
  url: 'https://api.sudomock.com/api/v1/sudoai/2d-mockups',
1834
2077
  body,
1835
2078
  json: true,
1836
2079
  });
1837
- returnData.push({ json: response, pairedItem: { item: i } });
2080
+ returnData.push({
2081
+ json: response,
2082
+ pairedItem: { item: i },
2083
+ });
1838
2084
  }
1839
2085
  // ========================================
1840
2086
  // 2D: GET MOCKUP
@@ -1843,10 +2089,13 @@ class SudoMock {
1843
2089
  const mockupUuid = this.getNodeParameter('twoDMockupUuid', i);
1844
2090
  const response = await this.helpers.httpRequestWithAuthentication.call(this, 'sudoMockApi', {
1845
2091
  method: 'GET',
1846
- url: `https://api.sudomock.com/api/v1/sudoai/2d-mockup/${mockupUuid}`,
2092
+ url: `https://api.sudomock.com/api/v1/sudoai/2d-mockups/${mockupUuid}`,
1847
2093
  json: true,
1848
2094
  });
1849
- returnData.push({ json: response, pairedItem: { item: i } });
2095
+ returnData.push({
2096
+ json: response,
2097
+ pairedItem: { item: i },
2098
+ });
1850
2099
  }
1851
2100
  // ========================================
1852
2101
  // 2D: LIST MOCKUPS
@@ -1857,7 +2106,10 @@ class SudoMock {
1857
2106
  url: 'https://api.sudomock.com/api/v1/sudoai/2d-mockups',
1858
2107
  json: true,
1859
2108
  });
1860
- returnData.push({ json: response, pairedItem: { item: i } });
2109
+ returnData.push({
2110
+ json: response,
2111
+ pairedItem: { item: i },
2112
+ });
1861
2113
  }
1862
2114
  // ========================================
1863
2115
  // 2D: SET PRINT AREAS
@@ -1865,23 +2117,20 @@ class SudoMock {
1865
2117
  else if (operation === 'set2DPrintAreas') {
1866
2118
  const mockupUuid = this.getNodeParameter('twoDMockupUuid', i);
1867
2119
  const printAreas = this.getNodeParameter('twoDSetPrintAreas.items', i, []);
1868
- const body = {
1869
- print_areas: printAreas.map((area) => ({
1870
- points: [
1871
- [area.point1X, area.point1Y],
1872
- [area.point2X, area.point2Y],
1873
- [area.point3X, area.point3Y],
1874
- [area.point4X, area.point4Y],
1875
- ],
1876
- })),
1877
- };
2120
+ if (printAreas.length === 0) {
2121
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Add at least one print area');
2122
+ }
2123
+ const body = { print_areas: format2DPrintAreas(printAreas) };
1878
2124
  const response = await this.helpers.httpRequestWithAuthentication.call(this, 'sudoMockApi', {
1879
2125
  method: 'PUT',
1880
- url: `https://api.sudomock.com/api/v1/sudoai/2d-mockup/${mockupUuid}/print-areas`,
2126
+ url: `https://api.sudomock.com/api/v1/sudoai/2d-mockups/${mockupUuid}/print-areas`,
1881
2127
  body,
1882
2128
  json: true,
1883
2129
  });
1884
- returnData.push({ json: response, pairedItem: { item: i } });
2130
+ returnData.push({
2131
+ json: response,
2132
+ pairedItem: { item: i },
2133
+ });
1885
2134
  }
1886
2135
  // ========================================
1887
2136
  // 2D: RENDER MOCKUP
@@ -1909,20 +2158,26 @@ class SudoMock {
1909
2158
  return printArea;
1910
2159
  });
1911
2160
  const exportOptions = this.getNodeParameter('twoDExportOptions', i, {});
2161
+ const isAsync = this.getNodeParameter('twoDRenderIsAsync', i, false);
1912
2162
  const body = {
1913
- mockup_uuid: mockupUuid,
1914
2163
  print_areas: printAreas,
1915
2164
  };
1916
2165
  if (Object.keys(exportOptions).length > 0) {
1917
2166
  body.export_options = exportOptions;
1918
2167
  }
2168
+ if (isAsync) {
2169
+ body.is_async = true;
2170
+ }
1919
2171
  const response = await this.helpers.httpRequestWithAuthentication.call(this, 'sudoMockApi', {
1920
2172
  method: 'POST',
1921
- url: 'https://api.sudomock.com/api/v1/sudoai/2d-mockup/render',
2173
+ url: `https://api.sudomock.com/api/v1/sudoai/2d-mockups/${mockupUuid}/render`,
1922
2174
  body,
1923
2175
  json: true,
1924
2176
  });
1925
- returnData.push({ json: response, pairedItem: { item: i } });
2177
+ returnData.push({
2178
+ json: response,
2179
+ pairedItem: { item: i },
2180
+ });
1926
2181
  }
1927
2182
  // ========================================
1928
2183
  // 2D: DELETE MOCKUP
@@ -1931,7 +2186,7 @@ class SudoMock {
1931
2186
  const mockupUuid = this.getNodeParameter('twoDMockupUuid', i);
1932
2187
  await this.helpers.httpRequestWithAuthentication.call(this, 'sudoMockApi', {
1933
2188
  method: 'DELETE',
1934
- url: `https://api.sudomock.com/api/v1/sudoai/2d-mockup/${mockupUuid}`,
2189
+ url: `https://api.sudomock.com/api/v1/sudoai/2d-mockups/${mockupUuid}`,
1935
2190
  });
1936
2191
  returnData.push({
1937
2192
  json: {
@@ -1944,11 +2199,138 @@ class SudoMock {
1944
2199
  });
1945
2200
  }
1946
2201
  // ========================================
2202
+ // FONTS
2203
+ // ========================================
2204
+ else if (operation === 'listFonts') {
2205
+ const filters = this.getNodeParameter('fontFilters', i, {});
2206
+ const qs = {};
2207
+ if (filters.page !== undefined)
2208
+ qs.page = filters.page;
2209
+ if (filters.perPage !== undefined)
2210
+ qs.per_page = filters.perPage;
2211
+ if (filters.category)
2212
+ qs.category = filters.category;
2213
+ if (filters.search)
2214
+ qs.search = filters.search;
2215
+ if (filters.scope)
2216
+ qs.scope = filters.scope;
2217
+ const response = await this.helpers.httpRequestWithAuthentication.call(this, 'sudoMockApi', {
2218
+ method: 'GET',
2219
+ url: 'https://api.sudomock.com/api/v1/fonts',
2220
+ qs,
2221
+ json: true,
2222
+ });
2223
+ returnData.push({
2224
+ json: response,
2225
+ pairedItem: { item: i },
2226
+ });
2227
+ }
2228
+ else if (operation === 'getFont') {
2229
+ const fontUuid = this.getNodeParameter('fontUuid', i);
2230
+ const response = await this.helpers.httpRequestWithAuthentication.call(this, 'sudoMockApi', {
2231
+ method: 'GET',
2232
+ url: `https://api.sudomock.com/api/v1/fonts/${fontUuid}`,
2233
+ json: true,
2234
+ });
2235
+ returnData.push({
2236
+ json: response,
2237
+ pairedItem: { item: i },
2238
+ });
2239
+ }
2240
+ else if (operation === 'uploadFont') {
2241
+ const licenseConfirmed = this.getNodeParameter('fontLicenseConfirmed', i, false);
2242
+ if (!licenseConfirmed) {
2243
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Confirm you have the right to use and embed this font');
2244
+ }
2245
+ const response = await this.helpers.httpRequestWithAuthentication.call(this, 'sudoMockApi', {
2246
+ method: 'POST',
2247
+ url: 'https://api.sudomock.com/api/v1/fonts',
2248
+ body: {
2249
+ url: this.getNodeParameter('fontUrl', i),
2250
+ license_confirmed: true,
2251
+ },
2252
+ json: true,
2253
+ });
2254
+ returnData.push({
2255
+ json: response,
2256
+ pairedItem: { item: i },
2257
+ });
2258
+ }
2259
+ else if (operation === 'deleteFont') {
2260
+ const fontUuid = this.getNodeParameter('fontUuid', i);
2261
+ const response = await this.helpers.httpRequestWithAuthentication.call(this, 'sudoMockApi', {
2262
+ method: 'DELETE',
2263
+ url: `https://api.sudomock.com/api/v1/fonts/${fontUuid}`,
2264
+ json: true,
2265
+ });
2266
+ returnData.push({
2267
+ json: response,
2268
+ pairedItem: { item: i },
2269
+ });
2270
+ }
2271
+ // ========================================
2272
+ // ARTWORKS
2273
+ // ========================================
2274
+ else if (operation === 'storeArtworks') {
2275
+ const items = this.getNodeParameter('artworkItems.items', i, []);
2276
+ const previewUrl = this.getNodeParameter('artworkPreviewUrl', i, '');
2277
+ if (items.length === 0 && !previewUrl) {
2278
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Add at least one artwork item or a preview URL');
2279
+ }
2280
+ if (items.length > 10) {
2281
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Artwork Items supports up to 10 files');
2282
+ }
2283
+ const body = {
2284
+ mockup_uuid: this.getNodeParameter('artworkMockupUuid', i),
2285
+ items: items.map((item) => ({
2286
+ smart_object_uuid: item.smartObjectUuid,
2287
+ base64: item.base64,
2288
+ })),
2289
+ };
2290
+ if (previewUrl)
2291
+ body.preview_url = previewUrl;
2292
+ const response = await this.helpers.httpRequestWithAuthentication.call(this, 'sudoMockApi', {
2293
+ method: 'POST',
2294
+ url: 'https://api.sudomock.com/api/v1/artworks',
2295
+ body,
2296
+ json: true,
2297
+ });
2298
+ returnData.push({
2299
+ json: response,
2300
+ pairedItem: { item: i },
2301
+ });
2302
+ }
2303
+ else if (operation === 'deleteArtworks') {
2304
+ const mode = this.getNodeParameter('artworkDeleteMode', i);
2305
+ const body = {};
2306
+ if (mode === 'mockup') {
2307
+ body.mockup_uuid = this.getNodeParameter('artworkDeleteMockupUuid', i);
2308
+ }
2309
+ else {
2310
+ const urls = parseJsonArray(this.getNodeParameter('artworkDeleteUrls', i, []), 'URLs');
2311
+ if (urls.length === 0 || !urls.every((url) => typeof url === 'string')) {
2312
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'URLs must contain at least one URL');
2313
+ }
2314
+ body.urls = urls;
2315
+ }
2316
+ const response = await this.helpers.httpRequestWithAuthentication.call(this, 'sudoMockApi', {
2317
+ method: 'POST',
2318
+ url: 'https://api.sudomock.com/api/v1/artworks/delete',
2319
+ body,
2320
+ json: true,
2321
+ });
2322
+ returnData.push({
2323
+ json: response,
2324
+ pairedItem: { item: i },
2325
+ });
2326
+ }
2327
+ // ========================================
1947
2328
  // RENDER MOCKUP
1948
2329
  // ========================================
1949
2330
  else if (operation === 'render') {
1950
2331
  const mockupUuid = this.getNodeParameter('mockupUuid', i);
1951
2332
  const smartObjectsData = this.getNodeParameter('smartObjects.items', i, []);
2333
+ const textLayers = parseJsonArray(this.getNodeParameter('textLayers', i, []), 'Text Layers');
1952
2334
  const exportOptions = this.getNodeParameter('exportOptions', i, {});
1953
2335
  // Convert smart objects array to API format
1954
2336
  const smartObjects = smartObjectsData.map((so) => {
@@ -2035,6 +2417,12 @@ class SudoMock {
2035
2417
  mockup_uuid: mockupUuid,
2036
2418
  smart_objects: smartObjects,
2037
2419
  };
2420
+ if (textLayers.length > 0) {
2421
+ body.text_layers = textLayers;
2422
+ }
2423
+ if (smartObjects.length === 0 && textLayers.length === 0) {
2424
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Add at least one smart object or text layer override');
2425
+ }
2038
2426
  if (isAsync) {
2039
2427
  body.is_async = true;
2040
2428
  }
@@ -2136,7 +2524,10 @@ class SudoMock {
2136
2524
  returnData.push({ json: out, pairedItem: { item: i } });
2137
2525
  }
2138
2526
  else {
2139
- returnData.push({ json: accepted, pairedItem: { item: i } });
2527
+ returnData.push({
2528
+ json: accepted,
2529
+ pairedItem: { item: i },
2530
+ });
2140
2531
  }
2141
2532
  }
2142
2533
  // ========================================
@@ -2191,7 +2582,10 @@ class SudoMock {
2191
2582
  qs,
2192
2583
  json: true,
2193
2584
  });
2194
- returnData.push({ json: response, pairedItem: { item: i } });
2585
+ returnData.push({
2586
+ json: response,
2587
+ pairedItem: { item: i },
2588
+ });
2195
2589
  }
2196
2590
  // ========================================
2197
2591
  // WEBHOOK: LIST ENDPOINTS
@@ -2202,7 +2596,10 @@ class SudoMock {
2202
2596
  url: 'https://api.sudomock.com/api/v1/webhook-endpoints',
2203
2597
  json: true,
2204
2598
  });
2205
- returnData.push({ json: response, pairedItem: { item: i } });
2599
+ returnData.push({
2600
+ json: response,
2601
+ pairedItem: { item: i },
2602
+ });
2206
2603
  }
2207
2604
  // ========================================
2208
2605
  // WEBHOOK: EVENTS FEED
@@ -2225,7 +2622,10 @@ class SudoMock {
2225
2622
  qs,
2226
2623
  json: true,
2227
2624
  });
2228
- returnData.push({ json: { deliveries: response }, pairedItem: { item: i } });
2625
+ returnData.push({
2626
+ json: { deliveries: response },
2627
+ pairedItem: { item: i },
2628
+ });
2229
2629
  }
2230
2630
  // ========================================
2231
2631
  // WEBHOOK: GET ENDPOINT
@@ -2237,7 +2637,10 @@ class SudoMock {
2237
2637
  url: `https://api.sudomock.com/api/v1/webhook-endpoints/${webhookId}`,
2238
2638
  json: true,
2239
2639
  });
2240
- returnData.push({ json: response, pairedItem: { item: i } });
2640
+ returnData.push({
2641
+ json: response,
2642
+ pairedItem: { item: i },
2643
+ });
2241
2644
  }
2242
2645
  // ========================================
2243
2646
  // WEBHOOK: CREATE ENDPOINT
@@ -2259,7 +2662,10 @@ class SudoMock {
2259
2662
  body,
2260
2663
  json: true,
2261
2664
  });
2262
- returnData.push({ json: response, pairedItem: { item: i } });
2665
+ returnData.push({
2666
+ json: response,
2667
+ pairedItem: { item: i },
2668
+ });
2263
2669
  }
2264
2670
  // ========================================
2265
2671
  // WEBHOOK: UPDATE ENDPOINT
@@ -2287,7 +2693,10 @@ class SudoMock {
2287
2693
  body,
2288
2694
  json: true,
2289
2695
  });
2290
- returnData.push({ json: response, pairedItem: { item: i } });
2696
+ returnData.push({
2697
+ json: response,
2698
+ pairedItem: { item: i },
2699
+ });
2291
2700
  }
2292
2701
  // ========================================
2293
2702
  // WEBHOOK: DELETE ENDPOINT
@@ -2318,7 +2727,10 @@ class SudoMock {
2318
2727
  url: `https://api.sudomock.com/api/v1/webhook-endpoints/${webhookId}/rotate-secret`,
2319
2728
  json: true,
2320
2729
  });
2321
- returnData.push({ json: response, pairedItem: { item: i } });
2730
+ returnData.push({
2731
+ json: response,
2732
+ pairedItem: { item: i },
2733
+ });
2322
2734
  }
2323
2735
  // ========================================
2324
2736
  // WEBHOOK: SEND TEST
@@ -2330,7 +2742,10 @@ class SudoMock {
2330
2742
  url: `https://api.sudomock.com/api/v1/webhook-endpoints/${webhookId}/test`,
2331
2743
  json: true,
2332
2744
  });
2333
- returnData.push({ json: response, pairedItem: { item: i } });
2745
+ returnData.push({
2746
+ json: response,
2747
+ pairedItem: { item: i },
2748
+ });
2334
2749
  }
2335
2750
  // ========================================
2336
2751
  // WEBHOOK: LIST DELIVERIES
@@ -2354,7 +2769,10 @@ class SudoMock {
2354
2769
  qs,
2355
2770
  json: true,
2356
2771
  });
2357
- returnData.push({ json: response, pairedItem: { item: i } });
2772
+ returnData.push({
2773
+ json: response,
2774
+ pairedItem: { item: i },
2775
+ });
2358
2776
  }
2359
2777
  // ========================================
2360
2778
  // WEBHOOK: REPLAY DELIVERY
@@ -2367,7 +2785,10 @@ class SudoMock {
2367
2785
  url: `https://api.sudomock.com/api/v1/webhook-endpoints/${webhookId}/deliveries/${deliveryId}/replay`,
2368
2786
  json: true,
2369
2787
  });
2370
- returnData.push({ json: response, pairedItem: { item: i } });
2788
+ returnData.push({
2789
+ json: response,
2790
+ pairedItem: { item: i },
2791
+ });
2371
2792
  }
2372
2793
  // ========================================
2373
2794
  // WEBHOOK: REPLAY FAILED DELIVERIES
@@ -2379,7 +2800,10 @@ class SudoMock {
2379
2800
  url: `https://api.sudomock.com/api/v1/webhook-endpoints/${webhookId}/deliveries/replay-failed`,
2380
2801
  json: true,
2381
2802
  });
2382
- returnData.push({ json: response, pairedItem: { item: i } });
2803
+ returnData.push({
2804
+ json: response,
2805
+ pairedItem: { item: i },
2806
+ });
2383
2807
  }
2384
2808
  // ========================================
2385
2809
  // GET ACCOUNT INFO
@@ -0,0 +1,12 @@
1
+ import { IHookFunctions, INodeType, INodeTypeDescription, IWebhookFunctions, IWebhookResponseData } from 'n8n-workflow';
2
+ export declare class SudoMockTrigger implements INodeType {
3
+ description: INodeTypeDescription;
4
+ webhookMethods: {
5
+ default: {
6
+ checkExists(this: IHookFunctions): Promise<boolean>;
7
+ create(this: IHookFunctions): Promise<boolean>;
8
+ delete(this: IHookFunctions): Promise<boolean>;
9
+ };
10
+ };
11
+ webhook(this: IWebhookFunctions): Promise<IWebhookResponseData>;
12
+ }
@@ -0,0 +1,118 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SudoMockTrigger = void 0;
4
+ const n8n_workflow_1 = require("n8n-workflow");
5
+ const webhooks_1 = require("./webhooks");
6
+ const API_URL = 'https://api.sudomock.com/api/v1/webhook-endpoints';
7
+ function firstHeader(value) {
8
+ return Array.isArray(value) ? (value[0] ?? '') : (value ?? '');
9
+ }
10
+ class SudoMockTrigger {
11
+ description = {
12
+ displayName: 'SudoMock Trigger',
13
+ name: 'sudoMockTrigger',
14
+ icon: 'file:sudomock.svg',
15
+ group: ['trigger'],
16
+ version: 1,
17
+ description: 'Start a workflow when a SudoMock job or test event finishes',
18
+ defaults: { name: 'SudoMock Trigger' },
19
+ inputs: [],
20
+ outputs: [n8n_workflow_1.NodeConnectionTypes.Main],
21
+ credentials: [{ name: 'sudoMockApi', required: true }],
22
+ webhooks: [
23
+ {
24
+ name: 'default',
25
+ httpMethod: 'POST',
26
+ responseMode: 'onReceived',
27
+ path: 'sudomock',
28
+ },
29
+ ],
30
+ properties: [
31
+ {
32
+ displayName: 'Events',
33
+ name: 'events',
34
+ type: 'multiOptions',
35
+ options: webhooks_1.WEBHOOK_EVENT_OPTIONS,
36
+ default: [],
37
+ description: 'Events that start this workflow. Leave empty to receive all events, including ones added in the future.',
38
+ },
39
+ ],
40
+ };
41
+ webhookMethods = {
42
+ default: {
43
+ async checkExists() {
44
+ const data = this.getWorkflowStaticData('node');
45
+ if (!data.webhookId || !data.webhookSecret)
46
+ return false;
47
+ try {
48
+ await this.helpers.httpRequestWithAuthentication.call(this, 'sudoMockApi', {
49
+ method: 'GET',
50
+ url: `${API_URL}/${data.webhookId}`,
51
+ json: true,
52
+ });
53
+ return true;
54
+ }
55
+ catch (error) {
56
+ if (Number(error.statusCode) !== 404)
57
+ throw error;
58
+ delete data.webhookId;
59
+ delete data.webhookSecret;
60
+ return false;
61
+ }
62
+ },
63
+ async create() {
64
+ const url = this.getNodeWebhookUrl('default');
65
+ if (!url)
66
+ return false;
67
+ const response = (await this.helpers.httpRequestWithAuthentication.call(this, 'sudoMockApi', {
68
+ method: 'POST',
69
+ url: API_URL,
70
+ body: {
71
+ url,
72
+ description: 'Workflow trigger',
73
+ event_types: this.getNodeParameter('events', []),
74
+ },
75
+ json: true,
76
+ }));
77
+ if (typeof response.id !== 'string' || typeof response.secret !== 'string')
78
+ return false;
79
+ const data = this.getWorkflowStaticData('node');
80
+ data.webhookId = response.id;
81
+ data.webhookSecret = response.secret;
82
+ return true;
83
+ },
84
+ async delete() {
85
+ const data = this.getWorkflowStaticData('node');
86
+ if (!data.webhookId)
87
+ return true;
88
+ try {
89
+ await this.helpers.httpRequestWithAuthentication.call(this, 'sudoMockApi', {
90
+ method: 'DELETE',
91
+ url: `${API_URL}/${data.webhookId}`,
92
+ });
93
+ }
94
+ catch {
95
+ return false;
96
+ }
97
+ delete data.webhookId;
98
+ delete data.webhookSecret;
99
+ return true;
100
+ },
101
+ },
102
+ };
103
+ async webhook() {
104
+ const request = this.getRequestObject();
105
+ if (!request.rawBody && request.readRawBody)
106
+ await request.readRawBody();
107
+ const rawBody = request.rawBody?.toString('utf8') ?? '';
108
+ const headers = this.getHeaderData();
109
+ const data = this.getWorkflowStaticData('node');
110
+ const valid = (0, webhooks_1.verifyWebhookSignature)(rawBody, firstHeader(headers['x-sudomock-signature']), firstHeader(headers['x-sudomock-timestamp']), typeof data.webhookSecret === 'string' ? data.webhookSecret : '');
111
+ if (!rawBody || !valid) {
112
+ this.getResponseObject().status(401).json({ message: 'Invalid webhook signature' });
113
+ return { noWebhookResponse: true };
114
+ }
115
+ return { workflowData: [[{ json: this.getBodyData() }]] };
116
+ }
117
+ }
118
+ exports.SudoMockTrigger = SudoMockTrigger;
@@ -0,0 +1,5 @@
1
+ export declare const WEBHOOK_EVENT_OPTIONS: {
2
+ name: string;
3
+ value: string;
4
+ }[];
5
+ export declare function verifyWebhookSignature(payload: string, signature: string, timestamp: string | number, secret: string, toleranceSeconds?: number, nowSeconds?: number): boolean;
@@ -0,0 +1,38 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.WEBHOOK_EVENT_OPTIONS = void 0;
4
+ exports.verifyWebhookSignature = verifyWebhookSignature;
5
+ const node_crypto_1 = require("node:crypto");
6
+ exports.WEBHOOK_EVENT_OPTIONS = [
7
+ { name: 'render.succeeded', value: 'render.succeeded' },
8
+ { name: 'render.failed', value: 'render.failed' },
9
+ { name: 'upload.succeeded', value: 'upload.succeeded' },
10
+ { name: 'video.succeeded', value: 'video.succeeded' },
11
+ { name: 'video.failed', value: 'video.failed' },
12
+ { name: '2d_mockup.ready', value: '2d_mockup.ready' },
13
+ { name: '2d_mockup.rejected', value: '2d_mockup.rejected' },
14
+ { name: '2d_mockup.failed', value: '2d_mockup.failed' },
15
+ { name: '2d_render.succeeded', value: '2d_render.succeeded' },
16
+ { name: '2d_render.failed', value: '2d_render.failed' },
17
+ { name: 'webhook.test', value: 'webhook.test' },
18
+ ];
19
+ function verifyWebhookSignature(payload, signature, timestamp, secret, toleranceSeconds = 300, nowSeconds = Math.floor(Date.now() / 1000)) {
20
+ if (!signature || !secret)
21
+ return false;
22
+ const parsedTimestamp = typeof timestamp === 'number' ? timestamp : Number.parseInt(timestamp, 10);
23
+ if (!Number.isFinite(parsedTimestamp) ||
24
+ Math.abs(nowSeconds - parsedTimestamp) > toleranceSeconds) {
25
+ return false;
26
+ }
27
+ const expected = (0, node_crypto_1.createHmac)('sha256', secret)
28
+ .update(`${parsedTimestamp}.${payload}`)
29
+ .digest('hex');
30
+ if (signature.length !== expected.length)
31
+ return false;
32
+ try {
33
+ return (0, node_crypto_1.timingSafeEqual)(Buffer.from(signature, 'hex'), Buffer.from(expected, 'hex'));
34
+ }
35
+ catch {
36
+ return false;
37
+ }
38
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "n8n-nodes-sudomock",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "n8n community node for SudoMock mockup rendering API - POD automation",
5
5
  "license": "MIT",
6
6
  "homepage": "https://sudomock.com",
@@ -40,7 +40,8 @@
40
40
  "dist/credentials/SudoMockApi.credentials.js"
41
41
  ],
42
42
  "nodes": [
43
- "dist/nodes/SudoMock/SudoMock.node.js"
43
+ "dist/nodes/SudoMock/SudoMock.node.js",
44
+ "dist/nodes/SudoMock/SudoMockTrigger.node.js"
44
45
  ]
45
46
  },
46
47
  "scripts": {