@mailhooks/sdk 1.0.5 → 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.
package/README.md CHANGED
@@ -211,6 +211,122 @@ try {
211
211
  - **404 Not Found**: Resource not found
212
212
  - **429 Too Many Requests**: Rate limit exceeded
213
213
 
214
+ ## Webhook Signature Verification
215
+
216
+ When you create a webhook in Mailhooks, you receive a secret that can be used to verify that incoming webhook requests are genuinely from Mailhooks. Each webhook request includes an `X-Webhook-Signature` header containing an HMAC-SHA256 signature of the request body.
217
+
218
+ ### Verifying Signatures
219
+
220
+ ```typescript
221
+ import { verifyWebhookSignature, parseWebhookPayload } from '@mailhooks/sdk';
222
+
223
+ // Express.js example - IMPORTANT: Use raw body parser for signature verification
224
+ app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
225
+ const signature = req.headers['x-webhook-signature'] as string;
226
+ const payload = req.body.toString();
227
+
228
+ // Verify the signature using your webhook secret
229
+ if (!verifyWebhookSignature(payload, signature, process.env.WEBHOOK_SECRET!)) {
230
+ console.error('Invalid webhook signature');
231
+ return res.status(401).send('Invalid signature');
232
+ }
233
+
234
+ // Parse the verified payload
235
+ const event = parseWebhookPayload(payload);
236
+
237
+ console.log(`Received email from ${event.from}: ${event.subject}`);
238
+
239
+ // Process the webhook...
240
+ res.status(200).send('OK');
241
+ });
242
+ ```
243
+
244
+ ### Webhook Payload
245
+
246
+ The webhook payload contains the following fields:
247
+
248
+ ```typescript
249
+ interface WebhookPayload {
250
+ id: string; // Unique email ID
251
+ from: string; // Sender email address
252
+ to: string[]; // Array of recipient addresses
253
+ subject: string; // Email subject
254
+ body: string; // Plain text body
255
+ html?: string; // HTML body (if available)
256
+ attachments: Array<{ // Attachment metadata
257
+ filename: string;
258
+ contentType: string;
259
+ size: number;
260
+ }>;
261
+ receivedAt: string; // ISO 8601 timestamp
262
+
263
+ // Email authentication results
264
+ spfResult?: 'pass' | 'fail' | 'softfail' | 'neutral' | 'none';
265
+ dkimResult?: 'pass' | 'fail' | 'none' | 'temperror' | 'permerror';
266
+ dmarcResult?: 'pass' | 'fail' | 'none' | 'temperror' | 'permerror';
267
+ authSummary?: 'pass' | 'fail' | 'partial';
268
+
269
+ // Spam detection
270
+ spamScore?: number; // 0.0 to 1.0
271
+ isSpam?: boolean;
272
+ }
273
+ ```
274
+
275
+ ### Security Best Practices
276
+
277
+ 1. **Always verify signatures** before processing webhook data
278
+ 2. **Use the raw request body** for signature verification (not parsed JSON)
279
+ 3. **Store secrets securely** in environment variables, not in code
280
+ 4. **Use HTTPS** for your webhook endpoint
281
+ 5. **Respond quickly** - return 200 OK before doing heavy processing
282
+
283
+ ### Framework Examples
284
+
285
+ #### Next.js API Route
286
+
287
+ ```typescript
288
+ import { verifyWebhookSignature, parseWebhookPayload } from '@mailhooks/sdk';
289
+ import { NextRequest, NextResponse } from 'next/server';
290
+
291
+ export async function POST(request: NextRequest) {
292
+ const payload = await request.text();
293
+ const signature = request.headers.get('x-webhook-signature') || '';
294
+
295
+ if (!verifyWebhookSignature(payload, signature, process.env.WEBHOOK_SECRET!)) {
296
+ return NextResponse.json({ error: 'Invalid signature' }, { status: 401 });
297
+ }
298
+
299
+ const event = parseWebhookPayload(payload);
300
+ // Process event...
301
+
302
+ return NextResponse.json({ received: true });
303
+ }
304
+ ```
305
+
306
+ #### Fastify
307
+
308
+ ```typescript
309
+ import { verifyWebhookSignature, parseWebhookPayload } from '@mailhooks/sdk';
310
+
311
+ fastify.post('/webhook', {
312
+ config: {
313
+ rawBody: true, // Enable raw body
314
+ },
315
+ }, async (request, reply) => {
316
+ const signature = request.headers['x-webhook-signature'] as string;
317
+ const payload = request.rawBody?.toString() || '';
318
+
319
+ if (!verifyWebhookSignature(payload, signature, process.env.WEBHOOK_SECRET!)) {
320
+ return reply.status(401).send({ error: 'Invalid signature' });
321
+ }
322
+
323
+ const event = parseWebhookPayload(payload);
324
+ // Process event...
325
+
326
+ return { received: true };
327
+ });
328
+ ```
329
+
214
330
  ## License
215
331
 
216
332
  MIT
package/dist/client.js CHANGED
@@ -1,13 +1,7 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.MailhooksClient = void 0;
7
- const axios_1 = __importDefault(require("axios"));
8
- class MailhooksClient {
1
+ import axios from 'axios';
2
+ export class MailhooksClient {
9
3
  constructor(config) {
10
- this.http = axios_1.default.create({
4
+ this.http = axios.create({
11
5
  baseURL: config.baseUrl ?? 'https://mailhooks.dev/api',
12
6
  headers: {
13
7
  'x-api-key': config.apiKey,
@@ -71,4 +65,3 @@ class MailhooksClient {
71
65
  return this.http;
72
66
  }
73
67
  }
74
- exports.MailhooksClient = MailhooksClient;
package/dist/index.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  export { Mailhooks } from './mailhooks';
2
2
  export { EmailsResource } from './resources/emails';
3
3
  export * from './types';
4
+ export { verifyWebhookSignature, parseWebhookPayload, constructSignature, type WebhookPayload, } from './webhooks';
4
5
  import { Mailhooks } from './mailhooks';
5
6
  export default Mailhooks;
6
7
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACpD,cAAc,SAAS,CAAC;AAGxB,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,eAAe,SAAS,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACpD,cAAc,SAAS,CAAC;AAGxB,OAAO,EACL,sBAAsB,EACtB,mBAAmB,EACnB,kBAAkB,EAClB,KAAK,cAAc,GACpB,MAAM,YAAY,CAAC;AAGpB,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,eAAe,SAAS,CAAC"}
package/dist/index.js CHANGED
@@ -1,25 +1,8 @@
1
- "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
- for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
- };
16
- Object.defineProperty(exports, "__esModule", { value: true });
17
- exports.EmailsResource = exports.Mailhooks = void 0;
18
- var mailhooks_1 = require("./mailhooks");
19
- Object.defineProperty(exports, "Mailhooks", { enumerable: true, get: function () { return mailhooks_1.Mailhooks; } });
20
- var emails_1 = require("./resources/emails");
21
- Object.defineProperty(exports, "EmailsResource", { enumerable: true, get: function () { return emails_1.EmailsResource; } });
22
- __exportStar(require("./types"), exports);
1
+ export { Mailhooks } from './mailhooks';
2
+ export { EmailsResource } from './resources/emails';
3
+ export * from './types';
4
+ // Webhook verification utilities
5
+ export { verifyWebhookSignature, parseWebhookPayload, constructSignature, } from './webhooks';
23
6
  // Default export for convenience
24
- const mailhooks_2 = require("./mailhooks");
25
- exports.default = mailhooks_2.Mailhooks;
7
+ import { Mailhooks } from './mailhooks';
8
+ export default Mailhooks;
package/dist/mailhooks.js CHANGED
@@ -1,10 +1,7 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.Mailhooks = void 0;
4
- const emails_1 = require("./resources/emails");
5
- class Mailhooks {
1
+ import { EmailsResource } from './resources/emails';
2
+ export class Mailhooks {
6
3
  constructor(config) {
7
- this.emails = new emails_1.EmailsResource(config);
4
+ this.emails = new EmailsResource(config);
8
5
  }
9
6
  /**
10
7
  * Create a new Mailhooks SDK instance
@@ -13,4 +10,3 @@ class Mailhooks {
13
10
  return new Mailhooks(config);
14
11
  }
15
12
  }
16
- exports.Mailhooks = Mailhooks;
@@ -1,8 +1,5 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.EmailsResource = void 0;
4
- const client_1 = require("../client");
5
- class EmailsResource extends client_1.MailhooksClient {
1
+ import { MailhooksClient } from '../client';
2
+ export class EmailsResource extends MailhooksClient {
6
3
  /**
7
4
  * Get a paginated list of emails
8
5
  */
@@ -197,4 +194,3 @@ class EmailsResource extends client_1.MailhooksClient {
197
194
  }
198
195
  }
199
196
  }
200
- exports.EmailsResource = EmailsResource;
package/dist/types.js CHANGED
@@ -1,2 +1 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
1
+ export {};
@@ -0,0 +1,123 @@
1
+ /**
2
+ * Webhook payload sent by Mailhooks when an email is received.
3
+ */
4
+ export interface WebhookPayload {
5
+ /** Unique email ID */
6
+ id: string;
7
+ /** Sender email address */
8
+ from: string;
9
+ /** Array of recipient email addresses */
10
+ to: string[];
11
+ /** Email subject line */
12
+ subject: string;
13
+ /** Plain text body of the email */
14
+ body: string;
15
+ /** HTML body of the email (if available) */
16
+ html?: string;
17
+ /** Array of attachment metadata */
18
+ attachments: Array<{
19
+ filename: string;
20
+ contentType: string;
21
+ size: number;
22
+ }>;
23
+ /** ISO 8601 timestamp when the email was received */
24
+ receivedAt: string;
25
+ /** SPF authentication result */
26
+ spfResult?: 'pass' | 'fail' | 'softfail' | 'neutral' | 'none';
27
+ /** DKIM authentication result */
28
+ dkimResult?: 'pass' | 'fail' | 'none' | 'temperror' | 'permerror';
29
+ /** DMARC authentication result */
30
+ dmarcResult?: 'pass' | 'fail' | 'none' | 'temperror' | 'permerror';
31
+ /** Overall authentication summary */
32
+ authSummary?: 'pass' | 'fail' | 'partial';
33
+ /** Email headers as key-value pairs */
34
+ headers?: Record<string, string>;
35
+ /** Authentication diagnostic details */
36
+ authDiagnostics?: {
37
+ spf: {
38
+ clientIp: string;
39
+ domain: string;
40
+ record?: string;
41
+ helo?: string;
42
+ } | null;
43
+ dkim: Array<{
44
+ domain: string;
45
+ selector?: string;
46
+ algorithm?: string;
47
+ aligned?: boolean;
48
+ result: string;
49
+ }>;
50
+ dmarc: {
51
+ domain: string;
52
+ policy: string;
53
+ record?: string;
54
+ alignment: {
55
+ spf: {
56
+ result: string | false;
57
+ strict: boolean;
58
+ };
59
+ dkim: {
60
+ result: string | false;
61
+ strict: boolean;
62
+ };
63
+ };
64
+ } | null;
65
+ };
66
+ }
67
+ /**
68
+ * Verifies a webhook signature using HMAC-SHA256.
69
+ *
70
+ * Each webhook request from Mailhooks includes an `X-Webhook-Signature` header
71
+ * containing a hex-encoded HMAC-SHA256 signature of the request body.
72
+ *
73
+ * @param payload - The raw request body as a string or Buffer
74
+ * @param signature - The signature from the `X-Webhook-Signature` header
75
+ * @param secret - Your webhook secret (starts with `whsec_`)
76
+ * @returns `true` if the signature is valid, `false` otherwise
77
+ *
78
+ * @example
79
+ * ```typescript
80
+ * import { verifyWebhookSignature } from '@mailhooks/sdk';
81
+ *
82
+ * // Express.js with raw body parser
83
+ * app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
84
+ * const signature = req.headers['x-webhook-signature'] as string;
85
+ * const payload = req.body.toString();
86
+ *
87
+ * if (!verifyWebhookSignature(payload, signature, process.env.WEBHOOK_SECRET!)) {
88
+ * return res.status(401).send('Invalid signature');
89
+ * }
90
+ *
91
+ * const event = JSON.parse(payload);
92
+ * // Process the webhook...
93
+ * res.status(200).send('OK');
94
+ * });
95
+ * ```
96
+ */
97
+ export declare function verifyWebhookSignature(payload: string | Buffer, signature: string, secret: string): boolean;
98
+ /**
99
+ * Parses a webhook payload from a JSON string.
100
+ *
101
+ * @param body - The raw request body as a string
102
+ * @returns The parsed webhook payload
103
+ * @throws {SyntaxError} If the body is not valid JSON
104
+ *
105
+ * @example
106
+ * ```typescript
107
+ * import { parseWebhookPayload } from '@mailhooks/sdk';
108
+ *
109
+ * const payload = parseWebhookPayload(req.body.toString());
110
+ * console.log(`Received email from ${payload.from}: ${payload.subject}`);
111
+ * ```
112
+ */
113
+ export declare function parseWebhookPayload(body: string): WebhookPayload;
114
+ /**
115
+ * Constructs the expected signature for a webhook payload.
116
+ * Useful for debugging or manual verification.
117
+ *
118
+ * @param payload - The raw request body as a string or Buffer
119
+ * @param secret - Your webhook secret
120
+ * @returns The expected HMAC-SHA256 signature as a hex string
121
+ */
122
+ export declare function constructSignature(payload: string | Buffer, secret: string): string;
123
+ //# sourceMappingURL=webhooks.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webhooks.d.ts","sourceRoot":"","sources":["../src/webhooks.ts"],"names":[],"mappings":"AAEA;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,sBAAsB;IACtB,EAAE,EAAE,MAAM,CAAC;IACX,2BAA2B;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,yCAAyC;IACzC,EAAE,EAAE,MAAM,EAAE,CAAC;IACb,yBAAyB;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,mCAAmC;IACnC,IAAI,EAAE,MAAM,CAAC;IACb,4CAA4C;IAC5C,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,mCAAmC;IACnC,WAAW,EAAE,KAAK,CAAC;QACjB,QAAQ,EAAE,MAAM,CAAC;QACjB,WAAW,EAAE,MAAM,CAAC;QACpB,IAAI,EAAE,MAAM,CAAC;KACd,CAAC,CAAC;IACH,qDAAqD;IACrD,UAAU,EAAE,MAAM,CAAC;IACnB,gCAAgC;IAChC,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,UAAU,GAAG,SAAS,GAAG,MAAM,CAAC;IAC9D,iCAAiC;IACjC,UAAU,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,WAAW,GAAG,WAAW,CAAC;IAClE,kCAAkC;IAClC,WAAW,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,WAAW,GAAG,WAAW,CAAC;IACnE,qCAAqC;IACrC,WAAW,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC;IAC1C,uCAAuC;IACvC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,wCAAwC;IACxC,eAAe,CAAC,EAAE;QAChB,GAAG,EAAE;YACH,QAAQ,EAAE,MAAM,CAAC;YACjB,MAAM,EAAE,MAAM,CAAC;YACf,MAAM,CAAC,EAAE,MAAM,CAAC;YAChB,IAAI,CAAC,EAAE,MAAM,CAAC;SACf,GAAG,IAAI,CAAC;QACT,IAAI,EAAE,KAAK,CAAC;YACV,MAAM,EAAE,MAAM,CAAC;YACf,QAAQ,CAAC,EAAE,MAAM,CAAC;YAClB,SAAS,CAAC,EAAE,MAAM,CAAC;YACnB,OAAO,CAAC,EAAE,OAAO,CAAC;YAClB,MAAM,EAAE,MAAM,CAAC;SAChB,CAAC,CAAC;QACH,KAAK,EAAE;YACL,MAAM,EAAE,MAAM,CAAC;YACf,MAAM,EAAE,MAAM,CAAC;YACf,MAAM,CAAC,EAAE,MAAM,CAAC;YAChB,SAAS,EAAE;gBACT,GAAG,EAAE;oBAAE,MAAM,EAAE,MAAM,GAAG,KAAK,CAAC;oBAAC,MAAM,EAAE,OAAO,CAAA;iBAAE,CAAC;gBACjD,IAAI,EAAE;oBAAE,MAAM,EAAE,MAAM,GAAG,KAAK,CAAC;oBAAC,MAAM,EAAE,OAAO,CAAA;iBAAE,CAAC;aACnD,CAAC;SACH,GAAG,IAAI,CAAC;KACV,CAAC;CACH;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,MAAM,GAAG,MAAM,EACxB,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,GACb,OAAO,CAqBT;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,cAAc,CAEhE;AAED;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,MAAM,GAAG,MAAM,EACxB,MAAM,EAAE,MAAM,GACb,MAAM,CAER"}
@@ -0,0 +1,76 @@
1
+ import { createHmac, timingSafeEqual } from 'crypto';
2
+ /**
3
+ * Verifies a webhook signature using HMAC-SHA256.
4
+ *
5
+ * Each webhook request from Mailhooks includes an `X-Webhook-Signature` header
6
+ * containing a hex-encoded HMAC-SHA256 signature of the request body.
7
+ *
8
+ * @param payload - The raw request body as a string or Buffer
9
+ * @param signature - The signature from the `X-Webhook-Signature` header
10
+ * @param secret - Your webhook secret (starts with `whsec_`)
11
+ * @returns `true` if the signature is valid, `false` otherwise
12
+ *
13
+ * @example
14
+ * ```typescript
15
+ * import { verifyWebhookSignature } from '@mailhooks/sdk';
16
+ *
17
+ * // Express.js with raw body parser
18
+ * app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
19
+ * const signature = req.headers['x-webhook-signature'] as string;
20
+ * const payload = req.body.toString();
21
+ *
22
+ * if (!verifyWebhookSignature(payload, signature, process.env.WEBHOOK_SECRET!)) {
23
+ * return res.status(401).send('Invalid signature');
24
+ * }
25
+ *
26
+ * const event = JSON.parse(payload);
27
+ * // Process the webhook...
28
+ * res.status(200).send('OK');
29
+ * });
30
+ * ```
31
+ */
32
+ export function verifyWebhookSignature(payload, signature, secret) {
33
+ if (!payload || !signature || !secret) {
34
+ return false;
35
+ }
36
+ // Normalize and validate signature format (should be 64 hex chars for SHA256)
37
+ const normalizedSignature = signature.trim().toLowerCase();
38
+ if (!/^[a-f0-9]{64}$/.test(normalizedSignature)) {
39
+ return false;
40
+ }
41
+ const expectedSignature = createHmac('sha256', secret)
42
+ .update(payload)
43
+ .digest('hex');
44
+ // Use timing-safe comparison to prevent timing attacks
45
+ // Both buffers are guaranteed to be 32 bytes (64 hex chars)
46
+ return timingSafeEqual(Buffer.from(normalizedSignature, 'hex'), Buffer.from(expectedSignature, 'hex'));
47
+ }
48
+ /**
49
+ * Parses a webhook payload from a JSON string.
50
+ *
51
+ * @param body - The raw request body as a string
52
+ * @returns The parsed webhook payload
53
+ * @throws {SyntaxError} If the body is not valid JSON
54
+ *
55
+ * @example
56
+ * ```typescript
57
+ * import { parseWebhookPayload } from '@mailhooks/sdk';
58
+ *
59
+ * const payload = parseWebhookPayload(req.body.toString());
60
+ * console.log(`Received email from ${payload.from}: ${payload.subject}`);
61
+ * ```
62
+ */
63
+ export function parseWebhookPayload(body) {
64
+ return JSON.parse(body);
65
+ }
66
+ /**
67
+ * Constructs the expected signature for a webhook payload.
68
+ * Useful for debugging or manual verification.
69
+ *
70
+ * @param payload - The raw request body as a string or Buffer
71
+ * @param secret - Your webhook secret
72
+ * @returns The expected HMAC-SHA256 signature as a hex string
73
+ */
74
+ export function constructSignature(payload, secret) {
75
+ return createHmac('sha256', secret).update(payload).digest('hex');
76
+ }
package/package.json CHANGED
@@ -1,10 +1,11 @@
1
1
  {
2
2
  "name": "@mailhooks/sdk",
3
- "version": "1.0.5",
3
+ "version": "2.0.0",
4
4
  "description": "TypeScript SDK for Mailhooks API",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
8
+ "type": "module",
8
9
  "main": "dist/index.js",
9
10
  "types": "dist/index.d.ts",
10
11
  "scripts": {
@@ -33,9 +34,9 @@
33
34
  ],
34
35
  "exports": {
35
36
  ".": {
37
+ "types": "./dist/index.d.ts",
36
38
  "import": "./dist/index.js",
37
- "require": "./dist/index.js",
38
- "types": "./dist/index.d.ts"
39
+ "default": "./dist/index.js"
39
40
  }
40
41
  }
41
42
  }