@canveletedotcom/sdk 2.0.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.
@@ -0,0 +1,126 @@
1
+ /**
2
+ * Element validation utilities
3
+ */
4
+ import { ValidationError } from '../errors';
5
+ const VALID_ELEMENT_TYPES = [
6
+ 'rectangle',
7
+ 'circle',
8
+ 'text',
9
+ 'image',
10
+ 'line',
11
+ 'polygon',
12
+ 'star',
13
+ 'svg',
14
+ 'bezier',
15
+ 'container',
16
+ 'table',
17
+ 'qr',
18
+ 'barcode',
19
+ ];
20
+ /**
21
+ * Validate an element's basic properties
22
+ */
23
+ export function validateElement(element) {
24
+ const errors = [];
25
+ // Required fields
26
+ if (!element.type) {
27
+ errors.push('Element type is required');
28
+ }
29
+ else if (!VALID_ELEMENT_TYPES.includes(element.type)) {
30
+ errors.push(`Invalid element type: ${element.type}. Valid types: ${VALID_ELEMENT_TYPES.join(', ')}`);
31
+ }
32
+ if (typeof element.x !== 'number') {
33
+ errors.push('Element x position must be a number');
34
+ }
35
+ if (typeof element.y !== 'number') {
36
+ errors.push('Element y position must be a number');
37
+ }
38
+ // Type-specific validation
39
+ if (element.type === 'text' && !element.text) {
40
+ errors.push('Text element requires text content');
41
+ }
42
+ if (element.type === 'image' && !element.src) {
43
+ errors.push('Image element requires src URL');
44
+ }
45
+ // Numeric range validation
46
+ if (element.opacity !== undefined && (element.opacity < 0 || element.opacity > 1)) {
47
+ errors.push('Opacity must be between 0 and 1');
48
+ }
49
+ if (element.fontSize !== undefined && element.fontSize <= 0) {
50
+ errors.push('Font size must be positive');
51
+ }
52
+ if (element.strokeWidth !== undefined && element.strokeWidth < 0) {
53
+ errors.push('Stroke width cannot be negative');
54
+ }
55
+ if (element.borderRadius !== undefined && element.borderRadius < 0) {
56
+ errors.push('Border radius cannot be negative');
57
+ }
58
+ if (errors.length > 0) {
59
+ throw new ValidationError(`Element validation failed: ${errors.join('; ')}`);
60
+ }
61
+ }
62
+ /**
63
+ * Validate canvas dimensions
64
+ */
65
+ export function validateCanvasDimensions(width, height) {
66
+ const errors = [];
67
+ if (typeof width !== 'number' || width <= 0) {
68
+ errors.push('Width must be a positive number');
69
+ }
70
+ if (typeof height !== 'number' || height <= 0) {
71
+ errors.push('Height must be a positive number');
72
+ }
73
+ if (width > 10000) {
74
+ errors.push('Width cannot exceed 10000 pixels');
75
+ }
76
+ if (height > 10000) {
77
+ errors.push('Height cannot exceed 10000 pixels');
78
+ }
79
+ if (errors.length > 0) {
80
+ throw new ValidationError(`Canvas validation failed: ${errors.join('; ')}`);
81
+ }
82
+ }
83
+ /**
84
+ * Validate render options
85
+ */
86
+ export function validateRenderOptions(options) {
87
+ const errors = [];
88
+ if (!options.designId && !options.templateId) {
89
+ errors.push('Either designId or templateId is required');
90
+ }
91
+ if (options.format && !['png', 'jpg', 'jpeg', 'pdf', 'svg'].includes(options.format)) {
92
+ errors.push('Invalid format. Valid formats: png, jpg, jpeg, pdf, svg');
93
+ }
94
+ if (options.quality !== undefined && (options.quality < 1 || options.quality > 100)) {
95
+ errors.push('Quality must be between 1 and 100');
96
+ }
97
+ if (errors.length > 0) {
98
+ throw new ValidationError(`Render options validation failed: ${errors.join('; ')}`);
99
+ }
100
+ }
101
+ /**
102
+ * Validate color format
103
+ */
104
+ export function validateColor(color) {
105
+ // Hex color
106
+ if (/^#([0-9A-Fa-f]{3}|[0-9A-Fa-f]{6}|[0-9A-Fa-f]{8})$/.test(color)) {
107
+ return true;
108
+ }
109
+ // RGB/RGBA
110
+ if (/^rgba?\(\s*\d+\s*,\s*\d+\s*,\s*\d+(,\s*[\d.]+)?\s*\)$/.test(color)) {
111
+ return true;
112
+ }
113
+ // HSL/HSLA
114
+ if (/^hsla?\(\s*\d+\s*,\s*\d+%\s*,\s*\d+%(,\s*[\d.]+)?\s*\)$/.test(color)) {
115
+ return true;
116
+ }
117
+ // Named colors (basic check)
118
+ const namedColors = [
119
+ 'transparent', 'black', 'white', 'red', 'green', 'blue',
120
+ 'yellow', 'orange', 'purple', 'pink', 'gray', 'grey',
121
+ ];
122
+ if (namedColors.includes(color.toLowerCase())) {
123
+ return true;
124
+ }
125
+ return false;
126
+ }
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Webhook signature verification utilities
3
+ */
4
+ export interface WebhookEvent {
5
+ id: string;
6
+ type: string;
7
+ timestamp: string;
8
+ data: Record<string, any>;
9
+ }
10
+ export interface WebhookVerifyOptions {
11
+ payload: string | Buffer;
12
+ signature: string;
13
+ secret: string;
14
+ tolerance?: number;
15
+ }
16
+ /**
17
+ * Verify a webhook signature
18
+ */
19
+ export declare function verifyWebhookSignature(options: WebhookVerifyOptions): boolean;
20
+ /**
21
+ * Parse a webhook payload
22
+ */
23
+ export declare function parseWebhookPayload(payload: string | Buffer): WebhookEvent;
24
+ /**
25
+ * Construct a webhook event from verified payload
26
+ */
27
+ export declare function constructWebhookEvent(payload: string | Buffer, signature: string, secret: string): WebhookEvent;
28
+ /**
29
+ * Generate a webhook signature (for testing)
30
+ */
31
+ export declare function generateWebhookSignature(payload: string, secret: string): string;
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Webhook signature verification utilities
3
+ */
4
+ import { createHmac, timingSafeEqual } from 'crypto';
5
+ /**
6
+ * Verify a webhook signature
7
+ */
8
+ export function verifyWebhookSignature(options) {
9
+ const { payload, signature, secret, tolerance = 300 } = options;
10
+ // Parse the signature header (format: t=timestamp,v1=signature)
11
+ const parts = signature.split(',');
12
+ const signatureParts = {};
13
+ for (const part of parts) {
14
+ const [key, value] = part.split('=');
15
+ if (key && value) {
16
+ signatureParts[key] = value;
17
+ }
18
+ }
19
+ const timestamp = signatureParts['t'];
20
+ const v1Signature = signatureParts['v1'];
21
+ if (!timestamp || !v1Signature) {
22
+ return false;
23
+ }
24
+ // Check timestamp tolerance
25
+ const timestampNum = parseInt(timestamp, 10);
26
+ const now = Math.floor(Date.now() / 1000);
27
+ if (Math.abs(now - timestampNum) > tolerance) {
28
+ return false;
29
+ }
30
+ // Compute expected signature
31
+ const payloadStr = typeof payload === 'string' ? payload : payload.toString('utf8');
32
+ const signedPayload = `${timestamp}.${payloadStr}`;
33
+ const expectedSignature = createHmac('sha256', secret)
34
+ .update(signedPayload)
35
+ .digest('hex');
36
+ // Timing-safe comparison
37
+ try {
38
+ return timingSafeEqual(Buffer.from(v1Signature), Buffer.from(expectedSignature));
39
+ }
40
+ catch {
41
+ return false;
42
+ }
43
+ }
44
+ /**
45
+ * Parse a webhook payload
46
+ */
47
+ export function parseWebhookPayload(payload) {
48
+ const payloadStr = typeof payload === 'string' ? payload : payload.toString('utf8');
49
+ return JSON.parse(payloadStr);
50
+ }
51
+ /**
52
+ * Construct a webhook event from verified payload
53
+ */
54
+ export function constructWebhookEvent(payload, signature, secret) {
55
+ if (!verifyWebhookSignature({ payload, signature, secret })) {
56
+ throw new Error('Invalid webhook signature');
57
+ }
58
+ return parseWebhookPayload(payload);
59
+ }
60
+ /**
61
+ * Generate a webhook signature (for testing)
62
+ */
63
+ export function generateWebhookSignature(payload, secret) {
64
+ const timestamp = Math.floor(Date.now() / 1000);
65
+ const signedPayload = `${timestamp}.${payload}`;
66
+ const signature = createHmac('sha256', secret)
67
+ .update(signedPayload)
68
+ .digest('hex');
69
+ return `t=${timestamp},v1=${signature}`;
70
+ }
package/package.json ADDED
@@ -0,0 +1,57 @@
1
+ {
2
+ "name": "@canveletedotcom/sdk",
3
+ "version": "2.0.0",
4
+ "description": "Official TypeScript SDK for the Canvelete API",
5
+ "main": "dist/index.js",
6
+ "module": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "type": "module",
9
+ "exports": {
10
+ ".": {
11
+ "import": "./dist/index.js",
12
+ "types": "./dist/index.d.ts"
13
+ },
14
+ "./utils": {
15
+ "import": "./dist/utils/index.js",
16
+ "types": "./dist/utils/index.d.ts"
17
+ }
18
+ },
19
+ "files": [
20
+ "dist",
21
+ "README.md"
22
+ ],
23
+ "scripts": {
24
+ "build": "tsc",
25
+ "test": "vitest run",
26
+ "test:watch": "vitest",
27
+ "test:coverage": "vitest run --coverage",
28
+ "prepublishOnly": "npm run build"
29
+ },
30
+ "keywords": [
31
+ "canvelete",
32
+ "typescript",
33
+ "sdk",
34
+ "api",
35
+ "image",
36
+ "generation",
37
+ "pdf",
38
+ "design",
39
+ "automation"
40
+ ],
41
+ "author": "Canvelete",
42
+ "license": "MIT",
43
+ "repository": {
44
+ "type": "git",
45
+ "url": "https://github.com/canvelete/canvelete-sdk.git"
46
+ },
47
+ "homepage": "https://docs.canvelete.com",
48
+ "devDependencies": {
49
+ "typescript": "^5.0.0",
50
+ "@types/node": "^18.0.0",
51
+ "vitest": "^1.0.0",
52
+ "@vitest/coverage-v8": "^1.0.0"
53
+ },
54
+ "engines": {
55
+ "node": ">=18.0.0"
56
+ }
57
+ }