n8n-nodes-sudomock 0.2.0 → 0.3.1

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.
@@ -2,6 +2,31 @@
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 TERMINAL_JOB_STATUSES = ['succeeded', 'failed', 'cancelled'];
6
+ /**
7
+ * Poll GET /api/v1/jobs/{job_id} until the job reaches a terminal status
8
+ * (succeeded, failed, cancelled) or the timeout elapses. Uses a capped backoff.
9
+ */
10
+ async function pollJob(jobId, timeoutSeconds) {
11
+ const deadline = Date.now() + timeoutSeconds * 1000;
12
+ let delayMs = 1500;
13
+ // eslint-disable-next-line no-constant-condition
14
+ while (true) {
15
+ const job = (await this.helpers.httpRequestWithAuthentication.call(this, 'sudoMockApi', {
16
+ method: 'GET',
17
+ url: `https://api.sudomock.com/api/v1/jobs/${jobId}`,
18
+ json: true,
19
+ }));
20
+ if (typeof job.status === 'string' && TERMINAL_JOB_STATUSES.includes(job.status)) {
21
+ return job;
22
+ }
23
+ if (Date.now() + delayMs >= deadline) {
24
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Timed out after ${timeoutSeconds}s waiting for job ${jobId} (last status: ${job.status ?? 'unknown'})`);
25
+ }
26
+ await (0, n8n_workflow_1.sleep)(delayMs);
27
+ delayMs = Math.min(delayMs * 1.5, 8000);
28
+ }
29
+ }
5
30
  class SudoMock {
6
31
  description = {
7
32
  displayName: 'SudoMock',
@@ -10,7 +35,7 @@ class SudoMock {
10
35
  group: ['transform'],
11
36
  version: 1,
12
37
  subtitle: '={{$parameter["operation"]}}',
13
- description: 'Generate mockup images for Print-on-Demand automation. Upload PSDs, render with your designs.',
38
+ description: 'Generate mockups and product videos for Print-on-Demand automation. Upload PSDs, render with your designs, render videos, run renders asynchronously, track jobs, and manage webhooks.',
14
39
  defaults: {
15
40
  name: 'SudoMock',
16
41
  },
@@ -57,12 +82,30 @@ class SudoMock {
57
82
  description: 'List all your uploaded mockup templates',
58
83
  action: 'List mockups',
59
84
  },
85
+ {
86
+ name: 'Get Job',
87
+ value: 'getJob',
88
+ description: 'Poll an async job by its job_id (GET /jobs/{job_id}) to get its status and result',
89
+ action: 'Get a job',
90
+ },
91
+ {
92
+ name: 'List Jobs',
93
+ value: 'listJobs',
94
+ description: 'List your async render, upload, and video jobs',
95
+ action: 'List jobs',
96
+ },
60
97
  {
61
98
  name: 'Render Mockup',
62
99
  value: 'render',
63
100
  description: 'Render mockup with your design',
64
101
  action: 'Render a mockup',
65
102
  },
103
+ {
104
+ name: 'Render Video',
105
+ value: 'renderVideo',
106
+ description: 'Turn a mockup into a short product video (always async)',
107
+ action: 'Render a video',
108
+ },
66
109
  {
67
110
  name: 'Update Mockup',
68
111
  value: 'updateMockup',
@@ -75,6 +118,72 @@ class SudoMock {
75
118
  description: 'Upload a PSD template from URL',
76
119
  action: 'Upload a PSD template',
77
120
  },
121
+ {
122
+ name: 'Webhook: Create Endpoint',
123
+ value: 'webhookCreate',
124
+ description: 'Create a webhook endpoint',
125
+ action: 'Create a webhook endpoint',
126
+ },
127
+ {
128
+ name: 'Webhook: Delete Endpoint',
129
+ value: 'webhookDelete',
130
+ description: 'Delete a webhook endpoint',
131
+ action: 'Delete a webhook endpoint',
132
+ },
133
+ {
134
+ name: 'Webhook: Events Feed',
135
+ value: 'webhookEventsFeed',
136
+ description: 'Recent deliveries across all of your webhook endpoints',
137
+ action: 'Get the webhook events feed',
138
+ },
139
+ {
140
+ name: 'Webhook: Get Endpoint',
141
+ value: 'webhookGet',
142
+ description: 'Get a single webhook endpoint',
143
+ action: 'Get a webhook endpoint',
144
+ },
145
+ {
146
+ name: 'Webhook: List Deliveries',
147
+ value: 'webhookListDeliveries',
148
+ description: 'List delivery attempts for a webhook endpoint',
149
+ action: 'List webhook deliveries',
150
+ },
151
+ {
152
+ name: 'Webhook: List Endpoints',
153
+ value: 'webhookList',
154
+ description: 'List your webhook endpoints',
155
+ action: 'List webhook endpoints',
156
+ },
157
+ {
158
+ name: 'Webhook: Replay Delivery',
159
+ value: 'webhookReplayDelivery',
160
+ description: 'Replay a single webhook delivery',
161
+ action: 'Replay a webhook delivery',
162
+ },
163
+ {
164
+ name: 'Webhook: Replay Failed Deliveries',
165
+ value: 'webhookReplayFailed',
166
+ description: 'Replay all failed or dead deliveries for an endpoint',
167
+ action: 'Replay failed webhook deliveries',
168
+ },
169
+ {
170
+ name: 'Webhook: Rotate Secret',
171
+ value: 'webhookRotateSecret',
172
+ description: 'Rotate the signing secret of a webhook endpoint',
173
+ action: 'Rotate a webhook secret',
174
+ },
175
+ {
176
+ name: 'Webhook: Send Test',
177
+ value: 'webhookTest',
178
+ description: 'Send a test event to a webhook endpoint',
179
+ action: 'Send a webhook test',
180
+ },
181
+ {
182
+ name: 'Webhook: Update Endpoint',
183
+ value: 'webhookUpdate',
184
+ description: 'Update a webhook endpoint',
185
+ action: 'Update a webhook endpoint',
186
+ },
78
187
  ],
79
188
  default: 'render',
80
189
  },
@@ -108,6 +217,18 @@ class SudoMock {
108
217
  placeholder: 'T-Shirt Mockup Front',
109
218
  description: 'Human-readable name for the template. Auto-generated from filename if empty.',
110
219
  },
220
+ {
221
+ displayName: 'Run Asynchronously',
222
+ name: 'uploadIsAsync',
223
+ type: 'boolean',
224
+ displayOptions: {
225
+ show: {
226
+ operation: ['uploadPsd'],
227
+ },
228
+ },
229
+ default: false,
230
+ description: 'Whether to queue the upload in the background. Returns a job_id (kind: upload) immediately instead of the inline result. Track it with the Get Job operation; result_url carries the new mockup_uuid on success.',
231
+ },
111
232
  // ============================================
112
233
  // RENDER PARAMETERS
113
234
  // ============================================
@@ -170,7 +291,7 @@ class SudoMock {
170
291
  {
171
292
  name: 'Fill',
172
293
  value: 'fill',
173
- description: 'Stretch to fill entire area',
294
+ description: 'Stretch to fill entire area (default)',
174
295
  },
175
296
  {
176
297
  name: 'Contain',
@@ -180,10 +301,10 @@ class SudoMock {
180
301
  {
181
302
  name: 'Cover',
182
303
  value: 'cover',
183
- description: 'Fill area, may crop edges (recommended)',
304
+ description: 'Fill area, may crop edges',
184
305
  },
185
306
  ],
186
- default: 'cover',
307
+ default: 'fill',
187
308
  description: 'How to fit the design in the smart object bounds',
188
309
  },
189
310
  {
@@ -204,6 +325,60 @@ class SudoMock {
204
325
  default: 0,
205
326
  description: 'Rotation angle in degrees',
206
327
  },
328
+ {
329
+ displayName: 'Base64 Image',
330
+ name: 'base64',
331
+ type: 'string',
332
+ default: '',
333
+ description: 'Raw base64-encoded image bytes (no data: prefix). Alternative to Design URL; eliminates server-side download latency. If set, takes priority over the URL.',
334
+ },
335
+ {
336
+ displayName: 'Content Type',
337
+ name: 'contentType',
338
+ type: 'options',
339
+ options: [
340
+ { name: 'PNG', value: 'image/png' },
341
+ { name: 'JPEG', value: 'image/jpeg' },
342
+ { name: 'WebP', value: 'image/webp' },
343
+ { name: 'GIF', value: 'image/gif' },
344
+ ],
345
+ default: 'image/png',
346
+ description: 'MIME type of the Base64 Image. Defaults to image/png if omitted.',
347
+ },
348
+ {
349
+ displayName: 'Custom Width',
350
+ name: 'sizeWidth',
351
+ type: 'number',
352
+ typeOptions: {
353
+ minValue: 1,
354
+ },
355
+ default: 0,
356
+ description: 'Custom asset width override in pixels. 0 = use smart object bounds.',
357
+ },
358
+ {
359
+ displayName: 'Custom Height',
360
+ name: 'sizeHeight',
361
+ type: 'number',
362
+ typeOptions: {
363
+ minValue: 1,
364
+ },
365
+ default: 0,
366
+ description: 'Custom asset height override in pixels. 0 = use smart object bounds.',
367
+ },
368
+ {
369
+ displayName: 'Position Top',
370
+ name: 'positionTop',
371
+ type: 'number',
372
+ default: 0,
373
+ description: 'Custom position top offset in pixels',
374
+ },
375
+ {
376
+ displayName: 'Position Left',
377
+ name: 'positionLeft',
378
+ type: 'number',
379
+ default: 0,
380
+ description: 'Custom position left offset in pixels',
381
+ },
207
382
  {
208
383
  displayName: 'Color Overlay (Hex)',
209
384
  name: 'colorHex',
@@ -228,7 +403,7 @@ class SudoMock {
228
403
  { name: 'Hard Light', value: 'hard-light' },
229
404
  { name: 'Soft Light', value: 'soft-light' },
230
405
  ],
231
- default: 'multiply',
406
+ default: 'normal',
232
407
  description: 'Blend mode for color overlay',
233
408
  },
234
409
  {
@@ -264,6 +439,39 @@ class SudoMock {
264
439
  default: 100,
265
440
  description: 'Layer opacity (0-100)',
266
441
  },
442
+ {
443
+ displayName: 'Saturation',
444
+ name: 'saturation',
445
+ type: 'number',
446
+ typeOptions: {
447
+ minValue: -100,
448
+ maxValue: 100,
449
+ },
450
+ default: 0,
451
+ description: 'Saturation adjustment (-100 to 100). 0=no change, -100=grayscale.',
452
+ },
453
+ {
454
+ displayName: 'Vibrance',
455
+ name: 'vibrance',
456
+ type: 'number',
457
+ typeOptions: {
458
+ minValue: -100,
459
+ maxValue: 100,
460
+ },
461
+ default: 0,
462
+ description: 'Vibrance adjustment (-100 to 100). Similar to saturation but preserves skin tones.',
463
+ },
464
+ {
465
+ displayName: 'Blur',
466
+ name: 'blur',
467
+ type: 'number',
468
+ typeOptions: {
469
+ minValue: 0,
470
+ maxValue: 100,
471
+ },
472
+ default: 0,
473
+ description: 'Gaussian blur amount (0=sharp, 100=max blur)',
474
+ },
267
475
  ],
268
476
  },
269
477
  ],
@@ -313,8 +521,8 @@ class SudoMock {
313
521
  minValue: 100,
314
522
  maxValue: 10000,
315
523
  },
316
- default: 1920,
317
- description: 'Output width in pixels (100-10000). Height scales proportionally.',
524
+ default: 2048,
525
+ description: 'Output width in pixels (100-10000). Height scales proportionally. Powers of 2 (1024, 2048, 4096) recommended.',
318
526
  },
319
527
  {
320
528
  displayName: 'Quality',
@@ -324,8 +532,8 @@ class SudoMock {
324
532
  minValue: 1,
325
533
  maxValue: 100,
326
534
  },
327
- default: 95,
328
- description: 'Quality for JPG/WebP output (1-100)',
535
+ default: 90,
536
+ description: 'Quality for JPG/WebP output (1-100). Ignored for PNG (always lossless).',
329
537
  },
330
538
  {
331
539
  displayName: 'DPI',
@@ -347,180 +555,728 @@ class SudoMock {
347
555
  },
348
556
  ],
349
557
  },
350
- // ============================================
351
- // LIST MOCKUPS PARAMETERS
352
- // ============================================
353
558
  {
354
- displayName: 'Return All',
355
- name: 'returnAll',
559
+ displayName: 'Run Asynchronously',
560
+ name: 'isAsync',
356
561
  type: 'boolean',
357
562
  displayOptions: {
358
563
  show: {
359
- operation: ['listMockups'],
564
+ operation: ['render'],
360
565
  },
361
566
  },
362
567
  default: false,
363
- description: 'Whether to return all results or only up to a given limit',
364
- },
365
- {
366
- displayName: 'Limit',
367
- name: 'limit',
368
- type: 'number',
369
- displayOptions: {
370
- show: {
371
- operation: ['listMockups'],
372
- returnAll: [false],
373
- },
374
- },
375
- typeOptions: {
376
- minValue: 1,
377
- maxValue: 100,
378
- },
379
- default: 20,
380
- description: 'Max number of results to return (1-100)',
568
+ description: 'Whether to process the render in the background. Returns a job_id (kind: render) immediately instead of the inline result. Track it with the Get Job operation.',
381
569
  },
570
+ // ============================================
571
+ // RENDER VIDEO PARAMETERS
572
+ // ============================================
382
573
  {
383
- displayName: 'Additional Options',
384
- name: 'additionalOptions',
385
- type: 'collection',
386
- placeholder: 'Add Option',
574
+ displayName: 'Input Mode',
575
+ name: 'videoInputMode',
576
+ type: 'options',
387
577
  displayOptions: {
388
578
  show: {
389
- operation: ['listMockups'],
579
+ operation: ['renderVideo'],
390
580
  },
391
581
  },
392
- default: {},
393
582
  options: [
394
583
  {
395
- displayName: 'Filter by Name',
396
- name: 'name',
397
- type: 'string',
398
- default: '',
399
- description: 'Filter mockups by name (case-insensitive, partial match)',
400
- },
401
- {
402
- displayName: 'Created After',
403
- name: 'created_after',
404
- type: 'dateTime',
405
- default: '',
406
- description: 'Filter mockups created after this date',
407
- },
408
- {
409
- displayName: 'Created Before',
410
- name: 'created_before',
411
- type: 'dateTime',
412
- default: '',
413
- description: 'Filter mockups created before this date',
414
- },
415
- {
416
- displayName: 'Sort By',
417
- name: 'sort',
418
- type: 'options',
419
- options: [
420
- { name: 'Created At', value: 'created_at' },
421
- { name: 'Updated At', value: 'updated_at' },
422
- { name: 'Name', value: 'name' },
423
- ],
424
- default: 'created_at',
425
- description: 'Field to sort results by',
584
+ name: 'Render Mockup',
585
+ value: 'render',
586
+ description: 'Render a mockup with your designs, then animate it',
426
587
  },
427
588
  {
428
- displayName: 'Sort Order',
429
- name: 'order',
430
- type: 'options',
431
- options: [
432
- { name: 'Ascending', value: 'asc' },
433
- { name: 'Descending', value: 'desc' },
434
- ],
435
- default: 'desc',
436
- description: 'Sort order for results',
589
+ name: 'Animate Image URL',
590
+ value: 'image',
591
+ description: 'Animate an existing public image URL directly',
437
592
  },
438
593
  ],
594
+ default: 'render',
595
+ description: 'Whether to render a mockup first or animate an existing image URL',
439
596
  },
440
- // ============================================
441
- // GET MOCKUP PARAMETERS
442
- // ============================================
443
597
  {
444
598
  displayName: 'Mockup UUID',
445
- name: 'getMockupUuid',
599
+ name: 'videoMockupUuid',
446
600
  type: 'string',
447
601
  required: true,
448
602
  displayOptions: {
449
603
  show: {
450
- operation: ['getMockup'],
604
+ operation: ['renderVideo'],
605
+ videoInputMode: ['render'],
451
606
  },
452
607
  },
453
608
  default: '',
454
609
  placeholder: 'c315f78f-d2c7-4541-b240-a9372842de94',
455
- description: 'UUID of the mockup template to retrieve',
610
+ description: 'UUID of the uploaded mockup template to animate',
456
611
  },
457
- // ============================================
458
- // UPDATE MOCKUP PARAMETERS
459
- // ============================================
460
612
  {
461
- displayName: 'Mockup UUID',
462
- name: 'updateMockupUuid',
613
+ displayName: 'Image URL',
614
+ name: 'videoImageUrl',
463
615
  type: 'string',
464
616
  required: true,
465
617
  displayOptions: {
466
618
  show: {
467
- operation: ['updateMockup'],
619
+ operation: ['renderVideo'],
620
+ videoInputMode: ['image'],
468
621
  },
469
622
  },
470
623
  default: '',
471
- placeholder: 'c315f78f-d2c7-4541-b240-a9372842de94',
472
- description: 'UUID of the mockup template to update',
624
+ placeholder: 'https://cdn.example.com/product.png',
625
+ description: 'Public HTTPS image URL to animate directly (.png, .jpg, .jpeg, .webp, .gif, .avif)',
473
626
  },
474
627
  {
475
- displayName: 'New Name',
476
- name: 'newName',
477
- type: 'string',
478
- required: true,
628
+ displayName: 'Smart Objects',
629
+ name: 'videoSmartObjects',
630
+ type: 'fixedCollection',
631
+ typeOptions: {
632
+ multipleValues: true,
633
+ },
479
634
  displayOptions: {
480
635
  show: {
481
- operation: ['updateMockup'],
636
+ operation: ['renderVideo'],
637
+ videoInputMode: ['render'],
482
638
  },
483
639
  },
484
- default: '',
485
- placeholder: 'Updated Mockup Name',
486
- description: 'New name for the mockup template',
640
+ default: {},
641
+ placeholder: 'Add Smart Object',
642
+ description: 'Smart objects to fill with your designs, same format as Render Mockup',
643
+ options: [
644
+ {
645
+ name: 'items',
646
+ displayName: 'Smart Object',
647
+ values: [
648
+ {
649
+ displayName: 'Smart Object UUID',
650
+ name: 'uuid',
651
+ type: 'string',
652
+ default: '',
653
+ required: true,
654
+ description: 'UUID of the smart object (from Upload PSD response)',
655
+ },
656
+ {
657
+ displayName: 'Design URL',
658
+ name: 'assetUrl',
659
+ type: 'string',
660
+ default: '',
661
+ required: true,
662
+ placeholder: 'https://cdn.example.com/design.png',
663
+ description: 'URL to your design image (PNG, JPG, WebP)',
664
+ },
665
+ {
666
+ displayName: 'Fit Mode',
667
+ name: 'fit',
668
+ type: 'options',
669
+ options: [
670
+ { name: 'Fill', value: 'fill', description: 'Stretch to fill entire area' },
671
+ { name: 'Contain', value: 'contain', description: 'Fit inside, may leave space' },
672
+ { name: 'Cover', value: 'cover', description: 'Fill area, may crop edges (recommended)' },
673
+ ],
674
+ default: 'cover',
675
+ description: 'How to fit the design in the smart object bounds',
676
+ },
677
+ ],
678
+ },
679
+ ],
487
680
  },
488
- // ============================================
489
- // DELETE MOCKUP PARAMETERS
490
- // ============================================
491
681
  {
492
- displayName: 'Mockup UUID',
493
- name: 'deleteMockupUuid',
682
+ displayName: 'Webhook URL',
683
+ name: 'videoWebhookUrl',
494
684
  type: 'string',
495
- required: true,
496
685
  displayOptions: {
497
686
  show: {
498
- operation: ['deleteMockup'],
687
+ operation: ['renderVideo'],
499
688
  },
500
689
  },
501
690
  default: '',
502
- description: 'UUID of the mockup template to delete',
691
+ placeholder: 'https://your-app.com/webhooks/sudomock',
692
+ description: 'Optional one-off HTTPS URL to notify when this video job finishes. Independent of any saved webhook endpoints.',
503
693
  },
504
- ],
505
- };
506
- async execute() {
507
- const items = this.getInputData();
508
- const returnData = [];
509
- const operation = this.getNodeParameter('operation', 0);
510
- for (let i = 0; i < items.length; i++) {
511
- try {
512
- // ========================================
513
- // UPLOAD PSD
514
- // ========================================
515
- if (operation === 'uploadPsd') {
516
- const psdFileUrl = this.getNodeParameter('psdFileUrl', i);
694
+ {
695
+ displayName: 'Video Options',
696
+ name: 'videoOptions',
697
+ type: 'collection',
698
+ placeholder: 'Add Video Option',
699
+ displayOptions: {
700
+ show: {
701
+ operation: ['renderVideo'],
702
+ },
703
+ },
704
+ default: {},
705
+ options: [
706
+ {
707
+ displayName: 'Duration (Seconds)',
708
+ name: 'durationSeconds',
709
+ type: 'number',
710
+ typeOptions: {
711
+ minValue: 1,
712
+ maxValue: 15,
713
+ },
714
+ default: 4,
715
+ description: "Length of the clip in seconds. Must be one of the chosen model's allowed durations (the default model veo-3.1-fast allows 4, 6, or 8). Longer clips cost more credits.",
716
+ },
717
+ {
718
+ displayName: 'Audio',
719
+ name: 'audio',
720
+ type: 'boolean',
721
+ default: false,
722
+ description: 'Whether to include a generated audio track. Enabling audio can cost extra credits depending on the model.',
723
+ },
724
+ {
725
+ displayName: 'Motion',
726
+ name: 'motion',
727
+ type: 'options',
728
+ options: [
729
+ { name: 'Ambient', value: 'ambient', description: 'Subtle ambient motion (default)' },
730
+ {
731
+ name: 'Showcase',
732
+ value: 'showcase',
733
+ description: 'More pronounced product-showcase motion',
734
+ },
735
+ ],
736
+ default: 'ambient',
737
+ description: 'Animation style for the generated clip',
738
+ },
739
+ {
740
+ displayName: 'Advanced Model',
741
+ name: 'advancedModel',
742
+ type: 'string',
743
+ default: '',
744
+ placeholder: 'kling-v3-pro',
745
+ description: 'Optional override to pin a specific model from the roster (veo-3.1-fast, kling-v3-pro, kling-2.6-pro, seedance-2.0, wan-2.5). Leave empty to let SudoMock auto-pick by plan tier. An unknown id returns a 400 error.',
746
+ },
747
+ ],
748
+ },
749
+ {
750
+ displayName: 'Wait for Completion',
751
+ name: 'videoWaitForCompletion',
752
+ type: 'boolean',
753
+ displayOptions: {
754
+ show: {
755
+ operation: ['renderVideo'],
756
+ },
757
+ },
758
+ default: false,
759
+ description: 'Whether to poll the job until it finishes and return the result, instead of returning the queued job immediately',
760
+ },
761
+ {
762
+ displayName: 'Poll Timeout (Seconds)',
763
+ name: 'videoPollTimeout',
764
+ type: 'number',
765
+ typeOptions: {
766
+ minValue: 5,
767
+ },
768
+ displayOptions: {
769
+ show: {
770
+ operation: ['renderVideo'],
771
+ videoWaitForCompletion: [true],
772
+ },
773
+ },
774
+ default: 300,
775
+ description: 'Maximum time to wait for the video job to finish',
776
+ },
777
+ // ============================================
778
+ // GET JOB PARAMETERS
779
+ // ============================================
780
+ {
781
+ displayName: 'Job ID',
782
+ name: 'jobId',
783
+ type: 'string',
784
+ required: true,
785
+ displayOptions: {
786
+ show: {
787
+ operation: ['getJob'],
788
+ },
789
+ },
790
+ default: '',
791
+ placeholder: '9d4e2b51-0c7a-4f8e-bb1c-2a6f9e3d8c10',
792
+ description: 'The job_id returned by an async render, upload, or video request',
793
+ },
794
+ {
795
+ displayName: 'Wait for Completion',
796
+ name: 'jobWaitForCompletion',
797
+ type: 'boolean',
798
+ displayOptions: {
799
+ show: {
800
+ operation: ['getJob'],
801
+ },
802
+ },
803
+ default: false,
804
+ description: 'Whether to poll the job until it reaches a terminal status (succeeded, failed, or cancelled) instead of returning the current state once',
805
+ },
806
+ {
807
+ displayName: 'Poll Timeout (Seconds)',
808
+ name: 'jobPollTimeout',
809
+ type: 'number',
810
+ typeOptions: {
811
+ minValue: 5,
812
+ },
813
+ displayOptions: {
814
+ show: {
815
+ operation: ['getJob'],
816
+ jobWaitForCompletion: [true],
817
+ },
818
+ },
819
+ default: 300,
820
+ description: 'Maximum time to wait for the job to finish',
821
+ },
822
+ // ============================================
823
+ // LIST JOBS PARAMETERS
824
+ // ============================================
825
+ {
826
+ displayName: 'Filters',
827
+ name: 'listJobsFilters',
828
+ type: 'collection',
829
+ placeholder: 'Add Filter',
830
+ displayOptions: {
831
+ show: {
832
+ operation: ['listJobs'],
833
+ },
834
+ },
835
+ default: {},
836
+ options: [
837
+ {
838
+ displayName: 'Kind',
839
+ name: 'kind',
840
+ type: 'options',
841
+ options: [
842
+ { name: 'Render', value: 'render' },
843
+ { name: 'Upload', value: 'upload' },
844
+ { name: 'Video', value: 'video' },
845
+ ],
846
+ default: 'render',
847
+ description: 'Only return jobs of this kind',
848
+ },
849
+ {
850
+ displayName: 'Mockup UUID',
851
+ name: 'mockupUuid',
852
+ type: 'string',
853
+ default: '',
854
+ description: 'Only return jobs derived from this source mockup',
855
+ },
856
+ {
857
+ displayName: 'Limit',
858
+ name: 'limit',
859
+ type: 'number',
860
+ typeOptions: {
861
+ minValue: 1,
862
+ maxValue: 50,
863
+ },
864
+ default: 20,
865
+ description: 'Max number of results to return',
866
+ },
867
+ {
868
+ displayName: 'Cursor',
869
+ name: 'cursor',
870
+ type: 'string',
871
+ default: '',
872
+ description: 'Opaque keyset cursor from a previous response (next_cursor) for pagination',
873
+ },
874
+ ],
875
+ },
876
+ // ============================================
877
+ // WEBHOOK ENDPOINT PARAMETERS
878
+ // ============================================
879
+ {
880
+ displayName: 'Endpoint URL',
881
+ name: 'webhookEndpointUrl',
882
+ type: 'string',
883
+ required: true,
884
+ displayOptions: {
885
+ show: {
886
+ operation: ['webhookCreate'],
887
+ },
888
+ },
889
+ default: '',
890
+ placeholder: 'https://your-app.com/webhooks/sudomock',
891
+ description: 'HTTPS URL that SudoMock will POST signed events to',
892
+ },
893
+ {
894
+ displayName: 'Description',
895
+ name: 'webhookDescription',
896
+ type: 'string',
897
+ displayOptions: {
898
+ show: {
899
+ operation: ['webhookCreate'],
900
+ },
901
+ },
902
+ default: '',
903
+ description: 'Optional human-readable label for this endpoint',
904
+ },
905
+ {
906
+ displayName: 'Events',
907
+ name: 'webhookEvents',
908
+ type: 'multiOptions',
909
+ displayOptions: {
910
+ show: {
911
+ operation: ['webhookCreate', 'webhookUpdate'],
912
+ },
913
+ },
914
+ options: [
915
+ { name: 'render.succeeded', value: 'render.succeeded' },
916
+ { name: 'render.failed', value: 'render.failed' },
917
+ { name: 'upload.succeeded', value: 'upload.succeeded' },
918
+ { name: 'video.succeeded', value: 'video.succeeded' },
919
+ { name: 'video.failed', value: 'video.failed' },
920
+ { name: 'webhook.test', value: 'webhook.test' },
921
+ ],
922
+ default: [],
923
+ description: 'Events this endpoint receives. Leave empty to subscribe to all events, including ones added in the future.',
924
+ },
925
+ {
926
+ displayName: 'Webhook Endpoint ID',
927
+ name: 'webhookId',
928
+ type: 'string',
929
+ required: true,
930
+ displayOptions: {
931
+ show: {
932
+ operation: [
933
+ 'webhookGet',
934
+ 'webhookUpdate',
935
+ 'webhookDelete',
936
+ 'webhookRotateSecret',
937
+ 'webhookTest',
938
+ 'webhookListDeliveries',
939
+ 'webhookReplayDelivery',
940
+ 'webhookReplayFailed',
941
+ ],
942
+ },
943
+ },
944
+ default: '',
945
+ description: 'ID of the webhook endpoint (from Webhook: List Endpoints)',
946
+ },
947
+ {
948
+ displayName: 'Update Fields',
949
+ name: 'webhookUpdateFields',
950
+ type: 'collection',
951
+ placeholder: 'Add Field',
952
+ displayOptions: {
953
+ show: {
954
+ operation: ['webhookUpdate'],
955
+ },
956
+ },
957
+ default: {},
958
+ options: [
959
+ {
960
+ displayName: 'Description',
961
+ name: 'description',
962
+ type: 'string',
963
+ default: '',
964
+ description: 'New human-readable label for the endpoint',
965
+ },
966
+ {
967
+ displayName: 'Enabled',
968
+ name: 'enabled',
969
+ type: 'boolean',
970
+ default: true,
971
+ description: 'Whether the endpoint is active and receives deliveries',
972
+ },
973
+ {
974
+ displayName: 'Endpoint URL',
975
+ name: 'url',
976
+ type: 'string',
977
+ default: '',
978
+ placeholder: 'https://your-app.com/webhooks/sudomock',
979
+ description: 'New HTTPS URL for the endpoint',
980
+ },
981
+ ],
982
+ },
983
+ {
984
+ displayName: 'Delivery ID',
985
+ name: 'webhookDeliveryId',
986
+ type: 'string',
987
+ required: true,
988
+ displayOptions: {
989
+ show: {
990
+ operation: ['webhookReplayDelivery'],
991
+ },
992
+ },
993
+ default: '',
994
+ description: 'ID of the delivery to replay (from Webhook: List Deliveries)',
995
+ },
996
+ {
997
+ displayName: 'Filters',
998
+ name: 'webhookDeliveriesFilters',
999
+ type: 'collection',
1000
+ placeholder: 'Add Filter',
1001
+ displayOptions: {
1002
+ show: {
1003
+ operation: ['webhookListDeliveries'],
1004
+ },
1005
+ },
1006
+ default: {},
1007
+ options: [
1008
+ {
1009
+ displayName: 'Status',
1010
+ name: 'status',
1011
+ type: 'options',
1012
+ options: [
1013
+ { name: 'Pending', value: 'pending' },
1014
+ { name: 'Delivered', value: 'delivered' },
1015
+ { name: 'Failed', value: 'failed' },
1016
+ { name: 'Dead', value: 'dead' },
1017
+ ],
1018
+ default: 'delivered',
1019
+ description: 'Only return deliveries with this status',
1020
+ },
1021
+ {
1022
+ displayName: 'Event Type',
1023
+ name: 'eventType',
1024
+ type: 'options',
1025
+ options: [
1026
+ { name: 'render.succeeded', value: 'render.succeeded' },
1027
+ { name: 'render.failed', value: 'render.failed' },
1028
+ { name: 'upload.succeeded', value: 'upload.succeeded' },
1029
+ { name: 'video.succeeded', value: 'video.succeeded' },
1030
+ { name: 'video.failed', value: 'video.failed' },
1031
+ { name: 'webhook.test', value: 'webhook.test' },
1032
+ ],
1033
+ default: 'render.succeeded',
1034
+ description: 'Only return deliveries for this event type',
1035
+ },
1036
+ {
1037
+ displayName: 'Limit',
1038
+ name: 'limit',
1039
+ type: 'number',
1040
+ typeOptions: {
1041
+ minValue: 1,
1042
+ maxValue: 200,
1043
+ },
1044
+ default: 50,
1045
+ description: 'Max number of results to return (1-200)',
1046
+ },
1047
+ ],
1048
+ },
1049
+ {
1050
+ displayName: 'Filters',
1051
+ name: 'webhookEventsFilters',
1052
+ type: 'collection',
1053
+ placeholder: 'Add Filter',
1054
+ displayOptions: {
1055
+ show: {
1056
+ operation: ['webhookEventsFeed'],
1057
+ },
1058
+ },
1059
+ default: {},
1060
+ options: [
1061
+ {
1062
+ displayName: 'Status',
1063
+ name: 'status',
1064
+ type: 'options',
1065
+ options: [
1066
+ { name: 'Pending', value: 'pending' },
1067
+ { name: 'Delivered', value: 'delivered' },
1068
+ { name: 'Failed', value: 'failed' },
1069
+ { name: 'Dead', value: 'dead' },
1070
+ ],
1071
+ default: 'delivered',
1072
+ description: 'Only return deliveries with this status',
1073
+ },
1074
+ {
1075
+ displayName: 'Event Type',
1076
+ name: 'eventType',
1077
+ type: 'options',
1078
+ options: [
1079
+ { name: 'render.succeeded', value: 'render.succeeded' },
1080
+ { name: 'render.failed', value: 'render.failed' },
1081
+ { name: 'upload.succeeded', value: 'upload.succeeded' },
1082
+ { name: 'video.succeeded', value: 'video.succeeded' },
1083
+ { name: 'video.failed', value: 'video.failed' },
1084
+ { name: 'webhook.test', value: 'webhook.test' },
1085
+ ],
1086
+ default: 'render.succeeded',
1087
+ description: 'Only return deliveries for this event type',
1088
+ },
1089
+ {
1090
+ displayName: 'Limit',
1091
+ name: 'limit',
1092
+ type: 'number',
1093
+ typeOptions: {
1094
+ minValue: 1,
1095
+ maxValue: 200,
1096
+ },
1097
+ default: 100,
1098
+ description: 'Max number of results to return (1-200)',
1099
+ },
1100
+ ],
1101
+ },
1102
+ // ============================================
1103
+ // LIST MOCKUPS PARAMETERS
1104
+ // ============================================
1105
+ {
1106
+ displayName: 'Return All',
1107
+ name: 'returnAll',
1108
+ type: 'boolean',
1109
+ displayOptions: {
1110
+ show: {
1111
+ operation: ['listMockups'],
1112
+ },
1113
+ },
1114
+ default: false,
1115
+ description: 'Whether to return all results or only up to a given limit',
1116
+ },
1117
+ {
1118
+ displayName: 'Limit',
1119
+ name: 'limit',
1120
+ type: 'number',
1121
+ displayOptions: {
1122
+ show: {
1123
+ operation: ['listMockups'],
1124
+ returnAll: [false],
1125
+ },
1126
+ },
1127
+ typeOptions: {
1128
+ minValue: 1,
1129
+ maxValue: 100,
1130
+ },
1131
+ default: 20,
1132
+ description: 'Max number of results to return (1-100)',
1133
+ },
1134
+ {
1135
+ displayName: 'Additional Options',
1136
+ name: 'additionalOptions',
1137
+ type: 'collection',
1138
+ placeholder: 'Add Option',
1139
+ displayOptions: {
1140
+ show: {
1141
+ operation: ['listMockups'],
1142
+ },
1143
+ },
1144
+ default: {},
1145
+ options: [
1146
+ {
1147
+ displayName: 'Filter by Name',
1148
+ name: 'name',
1149
+ type: 'string',
1150
+ default: '',
1151
+ description: 'Filter mockups by name (case-insensitive, partial match)',
1152
+ },
1153
+ {
1154
+ displayName: 'Created After',
1155
+ name: 'created_after',
1156
+ type: 'dateTime',
1157
+ default: '',
1158
+ description: 'Filter mockups created after this date',
1159
+ },
1160
+ {
1161
+ displayName: 'Created Before',
1162
+ name: 'created_before',
1163
+ type: 'dateTime',
1164
+ default: '',
1165
+ description: 'Filter mockups created before this date',
1166
+ },
1167
+ {
1168
+ displayName: 'Sort By',
1169
+ name: 'sort',
1170
+ type: 'options',
1171
+ options: [
1172
+ { name: 'Created At', value: 'created_at' },
1173
+ { name: 'Updated At', value: 'updated_at' },
1174
+ { name: 'Name', value: 'name' },
1175
+ ],
1176
+ default: 'created_at',
1177
+ description: 'Field to sort results by',
1178
+ },
1179
+ {
1180
+ displayName: 'Sort Order',
1181
+ name: 'order',
1182
+ type: 'options',
1183
+ options: [
1184
+ { name: 'Ascending', value: 'asc' },
1185
+ { name: 'Descending', value: 'desc' },
1186
+ ],
1187
+ default: 'desc',
1188
+ description: 'Sort order for results',
1189
+ },
1190
+ ],
1191
+ },
1192
+ // ============================================
1193
+ // GET MOCKUP PARAMETERS
1194
+ // ============================================
1195
+ {
1196
+ displayName: 'Mockup UUID',
1197
+ name: 'getMockupUuid',
1198
+ type: 'string',
1199
+ required: true,
1200
+ displayOptions: {
1201
+ show: {
1202
+ operation: ['getMockup'],
1203
+ },
1204
+ },
1205
+ default: '',
1206
+ placeholder: 'c315f78f-d2c7-4541-b240-a9372842de94',
1207
+ description: 'UUID of the mockup template to retrieve',
1208
+ },
1209
+ // ============================================
1210
+ // UPDATE MOCKUP PARAMETERS
1211
+ // ============================================
1212
+ {
1213
+ displayName: 'Mockup UUID',
1214
+ name: 'updateMockupUuid',
1215
+ type: 'string',
1216
+ required: true,
1217
+ displayOptions: {
1218
+ show: {
1219
+ operation: ['updateMockup'],
1220
+ },
1221
+ },
1222
+ default: '',
1223
+ placeholder: 'c315f78f-d2c7-4541-b240-a9372842de94',
1224
+ description: 'UUID of the mockup template to update',
1225
+ },
1226
+ {
1227
+ displayName: 'New Name',
1228
+ name: 'newName',
1229
+ type: 'string',
1230
+ required: true,
1231
+ displayOptions: {
1232
+ show: {
1233
+ operation: ['updateMockup'],
1234
+ },
1235
+ },
1236
+ default: '',
1237
+ placeholder: 'Updated Mockup Name',
1238
+ description: 'New name for the mockup template',
1239
+ },
1240
+ // ============================================
1241
+ // DELETE MOCKUP PARAMETERS
1242
+ // ============================================
1243
+ {
1244
+ displayName: 'Mockup UUID',
1245
+ name: 'deleteMockupUuid',
1246
+ type: 'string',
1247
+ required: true,
1248
+ displayOptions: {
1249
+ show: {
1250
+ operation: ['deleteMockup'],
1251
+ },
1252
+ },
1253
+ default: '',
1254
+ description: 'UUID of the mockup template to delete',
1255
+ },
1256
+ ],
1257
+ };
1258
+ async execute() {
1259
+ const items = this.getInputData();
1260
+ const returnData = [];
1261
+ const operation = this.getNodeParameter('operation', 0);
1262
+ for (let i = 0; i < items.length; i++) {
1263
+ try {
1264
+ // ========================================
1265
+ // UPLOAD PSD
1266
+ // ========================================
1267
+ if (operation === 'uploadPsd') {
1268
+ const psdFileUrl = this.getNodeParameter('psdFileUrl', i);
517
1269
  const psdName = this.getNodeParameter('psdName', i);
1270
+ const uploadIsAsync = this.getNodeParameter('uploadIsAsync', i, false);
518
1271
  const body = {
519
1272
  psd_file_url: psdFileUrl,
520
1273
  };
521
1274
  if (psdName) {
522
1275
  body.psd_name = psdName;
523
1276
  }
1277
+ if (uploadIsAsync) {
1278
+ body.is_async = true;
1279
+ }
524
1280
  const response = await this.helpers.httpRequestWithAuthentication.call(this, 'sudoMockApi', {
525
1281
  method: 'POST',
526
1282
  url: 'https://api.sudomock.com/api/v1/psd/upload',
@@ -552,15 +1308,45 @@ class SudoMock {
552
1308
  // Add additional options if present
553
1309
  if (so.additionalOptions) {
554
1310
  const opts = so.additionalOptions;
1311
+ const asset = smartObject.asset;
555
1312
  // Rotation
556
1313
  if (opts.rotate !== undefined && opts.rotate !== 0) {
557
- smartObject.asset.rotate = opts.rotate;
1314
+ asset.rotate = opts.rotate;
1315
+ }
1316
+ // Base64 image source (alternative to URL, takes priority server-side)
1317
+ if (opts.base64) {
1318
+ asset.base64 = opts.base64;
1319
+ if (opts.contentType) {
1320
+ asset.content_type = opts.contentType;
1321
+ }
1322
+ }
1323
+ // Custom size override
1324
+ const size = {};
1325
+ if (opts.sizeWidth !== undefined && opts.sizeWidth > 0) {
1326
+ size.width = opts.sizeWidth;
1327
+ }
1328
+ if (opts.sizeHeight !== undefined && opts.sizeHeight > 0) {
1329
+ size.height = opts.sizeHeight;
1330
+ }
1331
+ if (Object.keys(size).length > 0) {
1332
+ asset.size = size;
1333
+ }
1334
+ // Custom position override (top/left, DynamicMockups compatible)
1335
+ const position = {};
1336
+ if (opts.positionTop !== undefined && opts.positionTop !== 0) {
1337
+ position.top = opts.positionTop;
1338
+ }
1339
+ if (opts.positionLeft !== undefined && opts.positionLeft !== 0) {
1340
+ position.left = opts.positionLeft;
1341
+ }
1342
+ if (Object.keys(position).length > 0) {
1343
+ asset.position = position;
558
1344
  }
559
1345
  // Color overlay
560
1346
  if (opts.colorHex) {
561
1347
  smartObject.color = {
562
1348
  hex: opts.colorHex,
563
- blending_mode: opts.colorBlendMode || 'multiply',
1349
+ blending_mode: opts.colorBlendMode || 'normal',
564
1350
  };
565
1351
  }
566
1352
  // Adjustment layers
@@ -574,6 +1360,15 @@ class SudoMock {
574
1360
  if (opts.opacity !== undefined && opts.opacity !== 100) {
575
1361
  adjustments.opacity = opts.opacity;
576
1362
  }
1363
+ if (opts.saturation !== undefined && opts.saturation !== 0) {
1364
+ adjustments.saturation = opts.saturation;
1365
+ }
1366
+ if (opts.vibrance !== undefined && opts.vibrance !== 0) {
1367
+ adjustments.vibrance = opts.vibrance;
1368
+ }
1369
+ if (opts.blur !== undefined && opts.blur !== 0) {
1370
+ adjustments.blur = opts.blur;
1371
+ }
577
1372
  if (Object.keys(adjustments).length > 0) {
578
1373
  smartObject.adjustment_layers = adjustments;
579
1374
  }
@@ -581,10 +1376,14 @@ class SudoMock {
581
1376
  return smartObject;
582
1377
  });
583
1378
  // Request body
1379
+ const isAsync = this.getNodeParameter('isAsync', i, false);
584
1380
  const body = {
585
1381
  mockup_uuid: mockupUuid,
586
1382
  smart_objects: smartObjects,
587
1383
  };
1384
+ if (isAsync) {
1385
+ body.is_async = true;
1386
+ }
588
1387
  // Export options
589
1388
  if (Object.keys(exportOptions).length > 0) {
590
1389
  const expOpts = {};
@@ -628,6 +1427,307 @@ class SudoMock {
628
1427
  });
629
1428
  }
630
1429
  // ========================================
1430
+ // RENDER VIDEO (always async)
1431
+ // ========================================
1432
+ else if (operation === 'renderVideo') {
1433
+ const inputMode = this.getNodeParameter('videoInputMode', i, 'render');
1434
+ const videoOptions = this.getNodeParameter('videoOptions', i, {});
1435
+ const videoWebhookUrl = this.getNodeParameter('videoWebhookUrl', i, '');
1436
+ const body = {};
1437
+ if (inputMode === 'image') {
1438
+ body.image_url = this.getNodeParameter('videoImageUrl', i);
1439
+ }
1440
+ else {
1441
+ const mockupUuid = this.getNodeParameter('videoMockupUuid', i);
1442
+ const smartObjectsData = this.getNodeParameter('videoSmartObjects.items', i, []);
1443
+ body.mockup_uuid = mockupUuid;
1444
+ body.smart_objects = smartObjectsData.map((so) => ({
1445
+ uuid: so.uuid,
1446
+ asset: { url: so.assetUrl, fit: so.fit },
1447
+ }));
1448
+ }
1449
+ const video = {};
1450
+ if (videoOptions.durationSeconds !== undefined) {
1451
+ video.duration_seconds = videoOptions.durationSeconds;
1452
+ }
1453
+ if (videoOptions.audio !== undefined) {
1454
+ video.audio = videoOptions.audio;
1455
+ }
1456
+ if (videoOptions.motion) {
1457
+ video.motion = videoOptions.motion;
1458
+ }
1459
+ if (videoOptions.advancedModel) {
1460
+ video.advanced_model = videoOptions.advancedModel;
1461
+ }
1462
+ if (Object.keys(video).length > 0) {
1463
+ body.video = video;
1464
+ }
1465
+ if (videoWebhookUrl) {
1466
+ body.webhook = { url: videoWebhookUrl };
1467
+ }
1468
+ const accepted = await this.helpers.httpRequestWithAuthentication.call(this, 'sudoMockApi', {
1469
+ method: 'POST',
1470
+ url: 'https://api.sudomock.com/api/v1/renders/video',
1471
+ body,
1472
+ json: true,
1473
+ });
1474
+ const waitForCompletion = this.getNodeParameter('videoWaitForCompletion', i, false);
1475
+ if (waitForCompletion && accepted?.job_id) {
1476
+ const timeout = this.getNodeParameter('videoPollTimeout', i, 300);
1477
+ const job = await pollJob.call(this, accepted.job_id, timeout);
1478
+ const out = { ...job };
1479
+ if (job?.result_url) {
1480
+ out.resultUrl = job.result_url;
1481
+ }
1482
+ returnData.push({ json: out, pairedItem: { item: i } });
1483
+ }
1484
+ else {
1485
+ returnData.push({ json: accepted, pairedItem: { item: i } });
1486
+ }
1487
+ }
1488
+ // ========================================
1489
+ // GET JOB
1490
+ // ========================================
1491
+ else if (operation === 'getJob') {
1492
+ const jobId = this.getNodeParameter('jobId', i);
1493
+ const waitForCompletion = this.getNodeParameter('jobWaitForCompletion', i, false);
1494
+ let job;
1495
+ if (waitForCompletion) {
1496
+ const timeout = this.getNodeParameter('jobPollTimeout', i, 300);
1497
+ job = (await pollJob.call(this, jobId, timeout));
1498
+ }
1499
+ else {
1500
+ job = (await this.helpers.httpRequestWithAuthentication.call(this, 'sudoMockApi', {
1501
+ method: 'GET',
1502
+ url: `https://api.sudomock.com/api/v1/jobs/${jobId}`,
1503
+ json: true,
1504
+ }));
1505
+ }
1506
+ // Surface the result for convenience: result_url (render/video) or mockup_uuid (upload).
1507
+ const out = { ...job };
1508
+ if (job.kind === 'upload' && job.mockup_uuid) {
1509
+ out.resultMockupUuid = job.mockup_uuid;
1510
+ }
1511
+ else if (job.result_url) {
1512
+ out.resultUrl = job.result_url;
1513
+ }
1514
+ returnData.push({ json: out, pairedItem: { item: i } });
1515
+ }
1516
+ // ========================================
1517
+ // LIST JOBS
1518
+ // ========================================
1519
+ else if (operation === 'listJobs') {
1520
+ const filters = this.getNodeParameter('listJobsFilters', i, {});
1521
+ const qs = {};
1522
+ if (filters.kind) {
1523
+ qs.kind = filters.kind;
1524
+ }
1525
+ if (filters.mockupUuid) {
1526
+ qs.mockup_uuid = filters.mockupUuid;
1527
+ }
1528
+ if (filters.limit !== undefined) {
1529
+ qs.limit = filters.limit;
1530
+ }
1531
+ if (filters.cursor) {
1532
+ qs.cursor = filters.cursor;
1533
+ }
1534
+ const response = await this.helpers.httpRequestWithAuthentication.call(this, 'sudoMockApi', {
1535
+ method: 'GET',
1536
+ url: 'https://api.sudomock.com/api/v1/jobs',
1537
+ qs,
1538
+ json: true,
1539
+ });
1540
+ returnData.push({ json: response, pairedItem: { item: i } });
1541
+ }
1542
+ // ========================================
1543
+ // WEBHOOK: LIST ENDPOINTS
1544
+ // ========================================
1545
+ else if (operation === 'webhookList') {
1546
+ const response = await this.helpers.httpRequestWithAuthentication.call(this, 'sudoMockApi', {
1547
+ method: 'GET',
1548
+ url: 'https://api.sudomock.com/api/v1/webhook-endpoints',
1549
+ json: true,
1550
+ });
1551
+ returnData.push({ json: response, pairedItem: { item: i } });
1552
+ }
1553
+ // ========================================
1554
+ // WEBHOOK: EVENTS FEED
1555
+ // ========================================
1556
+ else if (operation === 'webhookEventsFeed') {
1557
+ const filters = this.getNodeParameter('webhookEventsFilters', i, {});
1558
+ const qs = {};
1559
+ if (filters.status) {
1560
+ qs.status = filters.status;
1561
+ }
1562
+ if (filters.eventType) {
1563
+ qs.event_type = filters.eventType;
1564
+ }
1565
+ if (filters.limit !== undefined) {
1566
+ qs.limit = filters.limit;
1567
+ }
1568
+ const response = await this.helpers.httpRequestWithAuthentication.call(this, 'sudoMockApi', {
1569
+ method: 'GET',
1570
+ url: 'https://api.sudomock.com/api/v1/webhook-endpoints/events',
1571
+ qs,
1572
+ json: true,
1573
+ });
1574
+ returnData.push({ json: { deliveries: response }, pairedItem: { item: i } });
1575
+ }
1576
+ // ========================================
1577
+ // WEBHOOK: GET ENDPOINT
1578
+ // ========================================
1579
+ else if (operation === 'webhookGet') {
1580
+ const webhookId = this.getNodeParameter('webhookId', i);
1581
+ const response = await this.helpers.httpRequestWithAuthentication.call(this, 'sudoMockApi', {
1582
+ method: 'GET',
1583
+ url: `https://api.sudomock.com/api/v1/webhook-endpoints/${webhookId}`,
1584
+ json: true,
1585
+ });
1586
+ returnData.push({ json: response, pairedItem: { item: i } });
1587
+ }
1588
+ // ========================================
1589
+ // WEBHOOK: CREATE ENDPOINT
1590
+ // ========================================
1591
+ else if (operation === 'webhookCreate') {
1592
+ const url = this.getNodeParameter('webhookEndpointUrl', i);
1593
+ const events = this.getNodeParameter('webhookEvents', i, []);
1594
+ const description = this.getNodeParameter('webhookDescription', i, '');
1595
+ const body = { url };
1596
+ if (description) {
1597
+ body.description = description;
1598
+ }
1599
+ if (events.length > 0) {
1600
+ body.event_types = events;
1601
+ }
1602
+ const response = await this.helpers.httpRequestWithAuthentication.call(this, 'sudoMockApi', {
1603
+ method: 'POST',
1604
+ url: 'https://api.sudomock.com/api/v1/webhook-endpoints',
1605
+ body,
1606
+ json: true,
1607
+ });
1608
+ returnData.push({ json: response, pairedItem: { item: i } });
1609
+ }
1610
+ // ========================================
1611
+ // WEBHOOK: UPDATE ENDPOINT
1612
+ // ========================================
1613
+ else if (operation === 'webhookUpdate') {
1614
+ const webhookId = this.getNodeParameter('webhookId', i);
1615
+ const events = this.getNodeParameter('webhookEvents', i, []);
1616
+ const updateFields = this.getNodeParameter('webhookUpdateFields', i, {});
1617
+ const body = {};
1618
+ if (updateFields.url) {
1619
+ body.url = updateFields.url;
1620
+ }
1621
+ if (updateFields.description !== undefined) {
1622
+ body.description = updateFields.description;
1623
+ }
1624
+ if (updateFields.enabled !== undefined) {
1625
+ body.enabled = updateFields.enabled;
1626
+ }
1627
+ if (events.length > 0) {
1628
+ body.event_types = events;
1629
+ }
1630
+ const response = await this.helpers.httpRequestWithAuthentication.call(this, 'sudoMockApi', {
1631
+ method: 'PATCH',
1632
+ url: `https://api.sudomock.com/api/v1/webhook-endpoints/${webhookId}`,
1633
+ body,
1634
+ json: true,
1635
+ });
1636
+ returnData.push({ json: response, pairedItem: { item: i } });
1637
+ }
1638
+ // ========================================
1639
+ // WEBHOOK: DELETE ENDPOINT
1640
+ // ========================================
1641
+ else if (operation === 'webhookDelete') {
1642
+ const webhookId = this.getNodeParameter('webhookId', i);
1643
+ await this.helpers.httpRequestWithAuthentication.call(this, 'sudoMockApi', {
1644
+ method: 'DELETE',
1645
+ url: `https://api.sudomock.com/api/v1/webhook-endpoints/${webhookId}`,
1646
+ });
1647
+ returnData.push({
1648
+ json: {
1649
+ success: true,
1650
+ message: 'Webhook endpoint deleted successfully',
1651
+ webhookId,
1652
+ statusCode: 204,
1653
+ },
1654
+ pairedItem: { item: i },
1655
+ });
1656
+ }
1657
+ // ========================================
1658
+ // WEBHOOK: ROTATE SECRET
1659
+ // ========================================
1660
+ else if (operation === 'webhookRotateSecret') {
1661
+ const webhookId = this.getNodeParameter('webhookId', i);
1662
+ const response = await this.helpers.httpRequestWithAuthentication.call(this, 'sudoMockApi', {
1663
+ method: 'POST',
1664
+ url: `https://api.sudomock.com/api/v1/webhook-endpoints/${webhookId}/rotate-secret`,
1665
+ json: true,
1666
+ });
1667
+ returnData.push({ json: response, pairedItem: { item: i } });
1668
+ }
1669
+ // ========================================
1670
+ // WEBHOOK: SEND TEST
1671
+ // ========================================
1672
+ else if (operation === 'webhookTest') {
1673
+ const webhookId = this.getNodeParameter('webhookId', i);
1674
+ const response = await this.helpers.httpRequestWithAuthentication.call(this, 'sudoMockApi', {
1675
+ method: 'POST',
1676
+ url: `https://api.sudomock.com/api/v1/webhook-endpoints/${webhookId}/test`,
1677
+ json: true,
1678
+ });
1679
+ returnData.push({ json: response, pairedItem: { item: i } });
1680
+ }
1681
+ // ========================================
1682
+ // WEBHOOK: LIST DELIVERIES
1683
+ // ========================================
1684
+ else if (operation === 'webhookListDeliveries') {
1685
+ const webhookId = this.getNodeParameter('webhookId', i);
1686
+ const filters = this.getNodeParameter('webhookDeliveriesFilters', i, {});
1687
+ const qs = {};
1688
+ if (filters.status) {
1689
+ qs.status = filters.status;
1690
+ }
1691
+ if (filters.eventType) {
1692
+ qs.event_type = filters.eventType;
1693
+ }
1694
+ if (filters.limit !== undefined) {
1695
+ qs.limit = filters.limit;
1696
+ }
1697
+ const response = await this.helpers.httpRequestWithAuthentication.call(this, 'sudoMockApi', {
1698
+ method: 'GET',
1699
+ url: `https://api.sudomock.com/api/v1/webhook-endpoints/${webhookId}/deliveries`,
1700
+ qs,
1701
+ json: true,
1702
+ });
1703
+ returnData.push({ json: response, pairedItem: { item: i } });
1704
+ }
1705
+ // ========================================
1706
+ // WEBHOOK: REPLAY DELIVERY
1707
+ // ========================================
1708
+ else if (operation === 'webhookReplayDelivery') {
1709
+ const webhookId = this.getNodeParameter('webhookId', i);
1710
+ const deliveryId = this.getNodeParameter('webhookDeliveryId', i);
1711
+ const response = await this.helpers.httpRequestWithAuthentication.call(this, 'sudoMockApi', {
1712
+ method: 'POST',
1713
+ url: `https://api.sudomock.com/api/v1/webhook-endpoints/${webhookId}/deliveries/${deliveryId}/replay`,
1714
+ json: true,
1715
+ });
1716
+ returnData.push({ json: response, pairedItem: { item: i } });
1717
+ }
1718
+ // ========================================
1719
+ // WEBHOOK: REPLAY FAILED DELIVERIES
1720
+ // ========================================
1721
+ else if (operation === 'webhookReplayFailed') {
1722
+ const webhookId = this.getNodeParameter('webhookId', i);
1723
+ const response = await this.helpers.httpRequestWithAuthentication.call(this, 'sudoMockApi', {
1724
+ method: 'POST',
1725
+ url: `https://api.sudomock.com/api/v1/webhook-endpoints/${webhookId}/deliveries/replay-failed`,
1726
+ json: true,
1727
+ });
1728
+ returnData.push({ json: response, pairedItem: { item: i } });
1729
+ }
1730
+ // ========================================
631
1731
  // GET ACCOUNT INFO
632
1732
  // ========================================
633
1733
  else if (operation === 'getAccountInfo') {