@unchainedshop/plugins 4.7.2 → 4.8.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.
- package/README.md +2 -0
- package/lib/delivery/post.d.ts +1 -0
- package/lib/delivery/post.js +23 -0
- package/lib/delivery/send-message.d.ts +1 -0
- package/lib/delivery/send-message.js +40 -0
- package/lib/delivery/stores.d.ts +1 -0
- package/lib/delivery/stores.js +43 -0
- package/lib/enrollments/licensed.d.ts +4 -0
- package/lib/enrollments/licensed.js +52 -0
- package/lib/events/node-event-emitter.d.ts +1 -3
- package/lib/events/node-event-emitter.js +3 -3
- package/lib/files/gridfs/adapter.d.ts +2 -2
- package/lib/files/gridfs/adapter.js +5 -2
- package/lib/files/gridfs/handler-express.d.ts +9 -0
- package/lib/files/gridfs/handler-express.js +110 -0
- package/lib/files/gridfs/handler-fastify.d.ts +3 -0
- package/lib/files/gridfs/handler-fastify.js +138 -0
- package/lib/files/gridfs/index.d.ts +4 -5
- package/lib/files/gridfs/index.js +3 -30
- package/lib/files/minio/handler-express.d.ts +2 -0
- package/lib/files/minio/handler-express.js +36 -0
- package/lib/files/minio/handler-fastify.d.ts +3 -0
- package/lib/files/minio/handler-fastify.js +40 -0
- package/lib/files/minio/index.d.ts +5 -4
- package/lib/files/minio/index.js +157 -18
- package/lib/filters/local-search.d.ts +3 -0
- package/lib/filters/local-search.js +66 -0
- package/lib/filters/strict-equal.d.ts +1 -0
- package/lib/filters/strict-equal.js +24 -0
- package/lib/payment/apple-iap/adapter.d.ts +1 -2
- package/lib/payment/apple-iap/adapter.js +3 -2
- package/lib/payment/apple-iap/handler-express.d.ts +2 -0
- package/lib/payment/apple-iap/handler-express.js +103 -0
- package/lib/payment/apple-iap/handler-fastify.d.ts +3 -0
- package/lib/payment/apple-iap/handler-fastify.js +102 -0
- package/lib/payment/apple-iap/index.d.ts +4 -6
- package/lib/payment/apple-iap/index.js +3 -28
- package/lib/payment/braintree.d.ts +1 -0
- package/lib/payment/braintree.js +138 -0
- package/lib/payment/cryptopay/handler-express.d.ts +2 -0
- package/lib/payment/cryptopay/handler-express.js +19 -0
- package/lib/payment/cryptopay/handler-fastify.d.ts +3 -0
- package/lib/payment/cryptopay/handler-fastify.js +17 -0
- package/lib/payment/cryptopay/index.d.ts +4 -5
- package/lib/payment/cryptopay/index.js +3 -31
- package/lib/payment/cryptopay/plugin.d.ts +3 -0
- package/lib/payment/cryptopay/plugin.js +159 -0
- package/lib/payment/datatrans-v2/handler-express.d.ts +1 -0
- package/lib/payment/datatrans-v2/handler-express.js +71 -0
- package/lib/payment/datatrans-v2/handler-fastify.d.ts +2 -0
- package/lib/payment/datatrans-v2/handler-fastify.js +71 -0
- package/lib/payment/datatrans-v2/index.d.ts +1 -4
- package/lib/payment/datatrans-v2/index.js +400 -17
- package/lib/payment/invoice-prepaid.d.ts +1 -0
- package/lib/payment/invoice-prepaid.js +30 -0
- package/lib/payment/invoice.d.ts +1 -0
- package/lib/payment/invoice.js +27 -0
- package/lib/payment/paypal-checkout.d.ts +1 -0
- package/lib/payment/paypal-checkout.js +87 -0
- package/lib/payment/payrexx/handler-express.d.ts +1 -0
- package/lib/payment/payrexx/handler-express.js +85 -0
- package/lib/payment/payrexx/handler-fastify.d.ts +2 -0
- package/lib/payment/payrexx/handler-fastify.js +91 -0
- package/lib/payment/payrexx/index.d.ts +1 -4
- package/lib/payment/payrexx/index.js +158 -19
- package/lib/payment/postfinance-checkout/handler-express.d.ts +1 -0
- package/lib/payment/postfinance-checkout/handler-express.js +37 -0
- package/lib/payment/postfinance-checkout/handler-fastify.d.ts +2 -0
- package/lib/payment/postfinance-checkout/handler-fastify.js +40 -0
- package/lib/payment/postfinance-checkout/index.d.ts +0 -5
- package/lib/payment/postfinance-checkout/index.js +186 -25
- package/lib/payment/saferpay/adapter.js +2 -1
- package/lib/payment/saferpay/handler-express.d.ts +1 -0
- package/lib/payment/saferpay/handler-express.js +59 -0
- package/lib/payment/saferpay/handler-fastify.d.ts +2 -0
- package/lib/payment/saferpay/handler-fastify.js +62 -0
- package/lib/payment/saferpay/index.d.ts +4 -5
- package/lib/payment/saferpay/index.js +3 -29
- package/lib/payment/stripe/handler-express.d.ts +5 -0
- package/lib/payment/stripe/handler-express.js +121 -0
- package/lib/payment/stripe/handler-fastify.d.ts +6 -0
- package/lib/payment/stripe/handler-fastify.js +131 -0
- package/lib/payment/stripe/index.d.ts +1 -4
- package/lib/payment/stripe/index.js +116 -22
- package/lib/payment/stripe/stripe.js +4 -1
- package/lib/presets/all-express.d.ts +6 -0
- package/lib/presets/all-express.js +26 -0
- package/lib/presets/all-fastify.d.ts +6 -0
- package/lib/presets/all-fastify.js +49 -0
- package/lib/presets/all.d.ts +70 -1
- package/lib/presets/all.js +27 -41
- package/lib/presets/base-express.d.ts +2 -0
- package/lib/presets/base-express.js +5 -0
- package/lib/presets/base-fastify.d.ts +3 -0
- package/lib/presets/base-fastify.js +16 -0
- package/lib/presets/base.d.ts +25 -1
- package/lib/presets/base.js +25 -56
- package/lib/presets/countries/ch.d.ts +2 -1
- package/lib/presets/countries/ch.js +2 -7
- package/lib/presets/crypto-express.d.ts +3 -0
- package/lib/presets/crypto-express.js +8 -0
- package/lib/presets/crypto-fastify.d.ts +3 -0
- package/lib/presets/crypto-fastify.js +11 -0
- package/lib/presets/crypto.d.ts +7 -3
- package/lib/presets/crypto.js +7 -18
- package/lib/pricing/delivery-swiss-tax.d.ts +2 -0
- package/lib/pricing/delivery-swiss-tax.js +62 -0
- package/lib/pricing/discount-100-off.d.ts +2 -0
- package/lib/pricing/discount-100-off.js +30 -0
- package/lib/pricing/discount-half-price-manual.d.ts +2 -0
- package/lib/pricing/discount-half-price-manual.js +30 -0
- package/lib/pricing/discount-half-price.d.ts +2 -0
- package/lib/pricing/discount-half-price.js +33 -0
- package/lib/pricing/free-delivery.d.ts +2 -0
- package/lib/pricing/free-delivery.js +27 -0
- package/lib/pricing/free-payment.d.ts +2 -0
- package/lib/pricing/free-payment.js +27 -0
- package/lib/pricing/order-delivery.d.ts +2 -0
- package/lib/pricing/order-delivery.js +33 -0
- package/lib/pricing/order-discount.d.ts +2 -0
- package/lib/pricing/order-discount.js +74 -0
- package/lib/pricing/order-items-discount.d.ts +2 -0
- package/lib/pricing/order-items-discount.js +50 -0
- package/lib/pricing/order-items.d.ts +2 -0
- package/lib/pricing/order-items.js +43 -0
- package/lib/pricing/order-payment.d.ts +2 -0
- package/lib/pricing/order-payment.js +33 -0
- package/lib/pricing/order-round.d.ts +10 -0
- package/lib/pricing/order-round.js +135 -0
- package/lib/pricing/product-catalog-price-options.d.ts +2 -0
- package/lib/pricing/product-catalog-price-options.js +51 -0
- package/lib/pricing/product-catalog-price.d.ts +2 -0
- package/lib/pricing/product-catalog-price.js +60 -0
- package/lib/pricing/product-discount.d.ts +2 -0
- package/lib/pricing/product-discount.js +69 -0
- package/lib/pricing/product-price-rateconversion.d.ts +2 -0
- package/lib/pricing/product-price-rateconversion.js +53 -0
- package/lib/pricing/product-round.d.ts +10 -0
- package/lib/pricing/product-round.js +44 -0
- package/lib/pricing/product-swiss-tax.d.ts +2 -0
- package/lib/pricing/product-swiss-tax.js +76 -0
- package/lib/quotations/manual.d.ts +1 -0
- package/lib/quotations/manual.js +20 -0
- package/lib/warehousing/eth-minter.d.ts +1 -0
- package/lib/warehousing/eth-minter.js +120 -0
- package/lib/warehousing/store.d.ts +1 -0
- package/lib/warehousing/store.js +34 -0
- package/lib/worker/budgetsms.d.ts +9 -0
- package/lib/worker/budgetsms.js +169 -0
- package/lib/worker/bulk-export.d.ts +2 -0
- package/lib/worker/bulk-export.js +64 -0
- package/lib/worker/bulk-import.d.ts +2 -0
- package/lib/worker/bulk-import.js +65 -0
- package/lib/worker/bulkgate.d.ts +1 -0
- package/lib/worker/bulkgate.js +91 -0
- package/lib/worker/email.d.ts +1 -0
- package/lib/worker/email.js +136 -0
- package/lib/worker/enrollment-order-generator.d.ts +1 -0
- package/lib/worker/enrollment-order-generator.js +77 -0
- package/lib/worker/error-notifications.d.ts +9 -0
- package/lib/worker/error-notifications.js +61 -0
- package/lib/worker/export-token.d.ts +4 -0
- package/lib/worker/export-token.js +26 -0
- package/lib/worker/external.d.ts +2 -0
- package/lib/worker/external.js +13 -0
- package/lib/worker/heartbeat.d.ts +1 -0
- package/lib/worker/heartbeat.js +26 -0
- package/lib/worker/http-request.d.ts +8 -0
- package/lib/worker/http-request.js +56 -0
- package/lib/worker/message.d.ts +10 -0
- package/lib/worker/message.js +42 -0
- package/lib/worker/push-notification.d.ts +1 -0
- package/lib/worker/push-notification.js +87 -0
- package/lib/worker/twilio.d.ts +1 -0
- package/lib/worker/twilio.js +59 -0
- package/lib/worker/update-coinbase-rates.d.ts +1 -0
- package/lib/worker/update-coinbase-rates.js +59 -0
- package/lib/worker/update-ecb-rates.d.ts +1 -0
- package/lib/worker/update-ecb-rates.js +92 -0
- package/lib/worker/update-token-ownership.d.ts +3 -0
- package/lib/worker/update-token-ownership.js +57 -0
- package/lib/worker/zombie-killer.d.ts +13 -0
- package/lib/worker/zombie-killer.js +91 -0
- package/package.json +9 -14
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { WorkerDirector, WorkerAdapter } from '@unchainedshop/core';
|
|
2
|
+
import { createLogger } from '@unchainedshop/logger';
|
|
3
|
+
const logger = createLogger('unchained:bulk-import');
|
|
4
|
+
export const BulkImportWorker = {
|
|
5
|
+
...WorkerAdapter,
|
|
6
|
+
key: 'shop.unchained.worker-plugin.bulk-import',
|
|
7
|
+
label: 'Bulk Import',
|
|
8
|
+
version: '1.0.0',
|
|
9
|
+
type: 'BULK_IMPORT',
|
|
10
|
+
maxParallelAllocations: 1,
|
|
11
|
+
doWork: async (rawPayload, unchainedAPI) => {
|
|
12
|
+
try {
|
|
13
|
+
const { createShouldUpsertIfIDExists = false, skipCacheInvalidation = false, updateShouldUpsertIfIDNotExists = false, } = rawPayload;
|
|
14
|
+
const bulkImporter = unchainedAPI.bulkImporter.createBulkImporter({
|
|
15
|
+
createShouldUpsertIfIDExists,
|
|
16
|
+
updateShouldUpsertIfIDNotExists,
|
|
17
|
+
skipCacheInvalidation,
|
|
18
|
+
});
|
|
19
|
+
if (rawPayload.payloadId) {
|
|
20
|
+
const readStream = await unchainedAPI.services.files.createDownloadStream({
|
|
21
|
+
fileId: rawPayload.payloadId,
|
|
22
|
+
});
|
|
23
|
+
if (!readStream) {
|
|
24
|
+
throw new Error('Could not create download stream from uploaded file');
|
|
25
|
+
}
|
|
26
|
+
await unchainedAPI.bulkImporter.pipeEventStream(readStream, bulkImporter, unchainedAPI);
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
const { events } = rawPayload;
|
|
30
|
+
if (!events?.length)
|
|
31
|
+
throw new Error('No events submitted');
|
|
32
|
+
await events.reduce(async (currentEventPromise, nextEvent) => {
|
|
33
|
+
await currentEventPromise;
|
|
34
|
+
await bulkImporter.validate(nextEvent);
|
|
35
|
+
return bulkImporter.prepare(nextEvent, unchainedAPI);
|
|
36
|
+
}, Promise.resolve());
|
|
37
|
+
}
|
|
38
|
+
const [result, error] = await bulkImporter.execute();
|
|
39
|
+
await bulkImporter.invalidateCaches(unchainedAPI);
|
|
40
|
+
if (error) {
|
|
41
|
+
return {
|
|
42
|
+
success: false,
|
|
43
|
+
result: result,
|
|
44
|
+
error,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
return {
|
|
48
|
+
success: true,
|
|
49
|
+
result: result,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
catch (err) {
|
|
53
|
+
logger.error(err);
|
|
54
|
+
return {
|
|
55
|
+
success: false,
|
|
56
|
+
error: {
|
|
57
|
+
name: err.name,
|
|
58
|
+
message: err.message,
|
|
59
|
+
stack: err.stack,
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
WorkerDirector.registerAdapter(BulkImportWorker);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { WorkerAdapter, WorkerDirector } from '@unchainedshop/core';
|
|
2
|
+
const { BULKGATE_APPLICATION_ID, BULKGATE_APPLICATION_TOKEN } = process.env;
|
|
3
|
+
const BulkGateWorker = {
|
|
4
|
+
...WorkerAdapter,
|
|
5
|
+
key: 'shop.unchained.worker-plugin.bulkgate',
|
|
6
|
+
label: 'Send SMS through BulkGate',
|
|
7
|
+
version: '1.0.0',
|
|
8
|
+
type: 'BULKGATE',
|
|
9
|
+
doWork: async ({ from, to, text, unicode = false, country, schedule, promotional = false, ...params }) => {
|
|
10
|
+
try {
|
|
11
|
+
const url = promotional
|
|
12
|
+
? 'https://portal.bulkgate.com/api/1.0/simple/promotional'
|
|
13
|
+
: 'https://portal.bulkgate.com/api/1.0/simple/transactional';
|
|
14
|
+
const requestBody = {
|
|
15
|
+
application_id: BULKGATE_APPLICATION_ID,
|
|
16
|
+
application_token: BULKGATE_APPLICATION_TOKEN,
|
|
17
|
+
number: to,
|
|
18
|
+
text: text || '',
|
|
19
|
+
unicode: unicode,
|
|
20
|
+
sender_id: from ? 'gText' : 'gSystem',
|
|
21
|
+
sender_id_value: from || '',
|
|
22
|
+
...(country && { country }),
|
|
23
|
+
...(schedule && { schedule }),
|
|
24
|
+
...params,
|
|
25
|
+
};
|
|
26
|
+
const response = await fetch(url, {
|
|
27
|
+
method: 'POST',
|
|
28
|
+
headers: {
|
|
29
|
+
'Content-Type': 'application/json',
|
|
30
|
+
'Cache-Control': 'no-cache',
|
|
31
|
+
},
|
|
32
|
+
body: JSON.stringify(requestBody),
|
|
33
|
+
});
|
|
34
|
+
const responseData = await response.json();
|
|
35
|
+
if (responseData.error || responseData.type) {
|
|
36
|
+
return {
|
|
37
|
+
success: false,
|
|
38
|
+
error: {
|
|
39
|
+
name: 'BULKGATE_ERROR',
|
|
40
|
+
message: responseData.error ||
|
|
41
|
+
`Failed to send ${promotional ? 'promotional' : 'transactional'} SMS`,
|
|
42
|
+
type: responseData.type || null,
|
|
43
|
+
code: responseData.code || response.status,
|
|
44
|
+
detail: responseData.detail || null,
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
if (responseData.data) {
|
|
49
|
+
if (!promotional && responseData.data.status === 'accepted') {
|
|
50
|
+
return {
|
|
51
|
+
success: true,
|
|
52
|
+
result: {
|
|
53
|
+
sms_id: responseData.data.sms_id,
|
|
54
|
+
price: responseData.data.price,
|
|
55
|
+
credit: responseData.data.credit,
|
|
56
|
+
number: responseData.data.number,
|
|
57
|
+
status: responseData.data.status,
|
|
58
|
+
},
|
|
59
|
+
error: null,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
if (promotional || responseData.data.status) {
|
|
63
|
+
return {
|
|
64
|
+
success: true,
|
|
65
|
+
result: responseData.data,
|
|
66
|
+
error: null,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return {
|
|
71
|
+
success: false,
|
|
72
|
+
error: {
|
|
73
|
+
name: 'BULKGATE_ERROR',
|
|
74
|
+
message: 'Unexpected response format from BulkGate API',
|
|
75
|
+
response: responseData,
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
catch (err) {
|
|
80
|
+
return {
|
|
81
|
+
success: false,
|
|
82
|
+
error: {
|
|
83
|
+
name: err.name,
|
|
84
|
+
message: err.message,
|
|
85
|
+
stack: err.stack,
|
|
86
|
+
},
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
},
|
|
90
|
+
};
|
|
91
|
+
WorkerDirector.registerAdapter(BulkGateWorker);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const checkEmailInterceptionEnabled: () => boolean;
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { WorkerDirector, WorkerAdapter } from '@unchainedshop/core';
|
|
2
|
+
import { spawn } from 'node:child_process';
|
|
3
|
+
import { writeFile, mkdtemp } from 'node:fs/promises';
|
|
4
|
+
import { tmpdir } from 'node:os';
|
|
5
|
+
import { join } from 'node:path';
|
|
6
|
+
import { createLogger } from '@unchainedshop/logger';
|
|
7
|
+
const logger = createLogger('unchained:worker:email');
|
|
8
|
+
export const checkEmailInterceptionEnabled = () => {
|
|
9
|
+
return process.env.NODE_ENV !== 'production' && !process.env.UNCHAINED_DISABLE_EMAIL_INTERCEPTION;
|
|
10
|
+
};
|
|
11
|
+
const buildLink = async ({ filename, content, href, contentType, encoding, path }) => {
|
|
12
|
+
if (path) {
|
|
13
|
+
return `<a href="file:/${path.startsWith('/') ? path : `${process.cwd()}/${path}`}">${filename}</a>`;
|
|
14
|
+
}
|
|
15
|
+
if (href) {
|
|
16
|
+
return `<a href="${href}">${filename}</a>`;
|
|
17
|
+
}
|
|
18
|
+
if (content && encoding === 'base64') {
|
|
19
|
+
return `<a target="_blank" href="${`data:${contentType};base64,${content}`}">${filename}</a>`;
|
|
20
|
+
}
|
|
21
|
+
return '';
|
|
22
|
+
};
|
|
23
|
+
let nodemailer;
|
|
24
|
+
try {
|
|
25
|
+
const nodemailerModule = await import('nodemailer');
|
|
26
|
+
nodemailer = nodemailerModule.default;
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
logger.warn(`optional peer npm package 'nodemailer' not installed, emails can't be sent`);
|
|
30
|
+
}
|
|
31
|
+
const openInBrowser = async (options) => {
|
|
32
|
+
const command = {
|
|
33
|
+
darwin: 'open',
|
|
34
|
+
win32: 'explorer.exe',
|
|
35
|
+
linux: 'xdg-open',
|
|
36
|
+
}[process.platform];
|
|
37
|
+
if (!command) {
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
const messageBody = options.html || options.text.replace(/(\r\n|\n|\r)/gm, '<br/>');
|
|
41
|
+
const attachmentLinks = await Promise.all((options.attachments || []).map(buildLink));
|
|
42
|
+
const content = `
|
|
43
|
+
<!DOCTYPE html>
|
|
44
|
+
<html lang="en" xmlns="https://www.w3.org/1999/xhtml" xmlns:o="urn:schemas-microsoft-com:office:office">
|
|
45
|
+
<head>
|
|
46
|
+
<meta charset="UTF-8"/>
|
|
47
|
+
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
48
|
+
</head>
|
|
49
|
+
<body>
|
|
50
|
+
<b>From: </b>${options.from}<br/>
|
|
51
|
+
<b>To: </b>${options.to}<br/>
|
|
52
|
+
<b>Cc: </b>${options.cc}<br/>
|
|
53
|
+
<b>Bcc: </b>${options.bcc}<br/>
|
|
54
|
+
<b>Reply-To: </b>${options.replyTo}<br/>
|
|
55
|
+
<br/>
|
|
56
|
+
<b>subject: </b>${options.subject}<br/>
|
|
57
|
+
<b>attachments: </b>${attachmentLinks.join(', ')}<br/>
|
|
58
|
+
<hr/>
|
|
59
|
+
${messageBody}
|
|
60
|
+
</body>
|
|
61
|
+
</html>`;
|
|
62
|
+
const tempDir = await mkdtemp(join(tmpdir(), 'unchained-email-'));
|
|
63
|
+
const tempFile = join(tempDir, 'email-preview.html');
|
|
64
|
+
await writeFile(tempFile, content, 'utf8');
|
|
65
|
+
return new Promise((resolve) => {
|
|
66
|
+
const child = spawn(command, [tempFile], {
|
|
67
|
+
detached: true,
|
|
68
|
+
stdio: 'ignore',
|
|
69
|
+
});
|
|
70
|
+
child.unref();
|
|
71
|
+
resolve(true);
|
|
72
|
+
});
|
|
73
|
+
};
|
|
74
|
+
const EmailWorkerPlugin = {
|
|
75
|
+
...WorkerAdapter,
|
|
76
|
+
key: 'shop.unchained.worker-plugin.email',
|
|
77
|
+
label: 'Send a Mail through Nodemailer',
|
|
78
|
+
version: '1.0.0',
|
|
79
|
+
type: 'EMAIL',
|
|
80
|
+
doWork: async ({ from, to, subject, ...rest }) => {
|
|
81
|
+
if (!to) {
|
|
82
|
+
return {
|
|
83
|
+
success: false,
|
|
84
|
+
error: {
|
|
85
|
+
name: 'RECIPIENT_REQUIRED',
|
|
86
|
+
message: 'EMAIL requires a to',
|
|
87
|
+
},
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
try {
|
|
91
|
+
const sendMailOptions = {
|
|
92
|
+
from,
|
|
93
|
+
to,
|
|
94
|
+
subject,
|
|
95
|
+
...rest,
|
|
96
|
+
};
|
|
97
|
+
if (checkEmailInterceptionEnabled()) {
|
|
98
|
+
const opened = await openInBrowser(sendMailOptions);
|
|
99
|
+
return {
|
|
100
|
+
success: opened,
|
|
101
|
+
result: opened ? { intercepted: true } : undefined,
|
|
102
|
+
error: !opened ? { message: "Interception failed due to missing package 'open'" } : undefined,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
if (!process.env.MAIL_URL) {
|
|
106
|
+
return {
|
|
107
|
+
success: false,
|
|
108
|
+
error: { name: 'NO_MAIL_URL_SET', message: 'MAIL_URL is not set' },
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
if (!nodemailer) {
|
|
112
|
+
return {
|
|
113
|
+
success: false,
|
|
114
|
+
error: {
|
|
115
|
+
name: 'NODEMAILER_NOT_INSTALLED',
|
|
116
|
+
message: 'npm dependency nodemailer is not installed, please install it to use email features',
|
|
117
|
+
},
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
const transporter = nodemailer.createTransport(process.env.MAIL_URL);
|
|
121
|
+
const result = await transporter.sendMail(sendMailOptions);
|
|
122
|
+
return { success: true, result };
|
|
123
|
+
}
|
|
124
|
+
catch (err) {
|
|
125
|
+
return {
|
|
126
|
+
success: false,
|
|
127
|
+
error: {
|
|
128
|
+
name: err.name,
|
|
129
|
+
message: err.message,
|
|
130
|
+
stack: err.stack,
|
|
131
|
+
},
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
},
|
|
135
|
+
};
|
|
136
|
+
WorkerDirector.registerAdapter(EmailWorkerPlugin);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const configureGenerateOrderAutoscheduling: () => void;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { enrollmentsSettings, EnrollmentStatus } from '@unchainedshop/core-enrollments';
|
|
2
|
+
import { EnrollmentDirector, WorkerDirector, WorkerAdapter, } from '@unchainedshop/core';
|
|
3
|
+
const GenerateOrderWorker = {
|
|
4
|
+
...WorkerAdapter,
|
|
5
|
+
key: 'shop.unchained.worker-plugin.generate-enrollment-orders',
|
|
6
|
+
label: 'Generates new Orders from Enrollments',
|
|
7
|
+
version: '1.0.0',
|
|
8
|
+
type: 'ENROLLMENT_ORDER_GENERATOR',
|
|
9
|
+
doWork: async (input, unchainedAPI) => {
|
|
10
|
+
const { modules, services } = unchainedAPI;
|
|
11
|
+
const enrollments = await modules.enrollments.findEnrollments({
|
|
12
|
+
status: [EnrollmentStatus.ACTIVE, EnrollmentStatus.PAUSED],
|
|
13
|
+
});
|
|
14
|
+
const errors = (await Promise.all(enrollments.map(async (enrollment) => {
|
|
15
|
+
try {
|
|
16
|
+
const product = await unchainedAPI.modules.products.findProduct({
|
|
17
|
+
productId: enrollment.productId,
|
|
18
|
+
});
|
|
19
|
+
const director = await EnrollmentDirector.actions({ enrollment, product: product }, unchainedAPI);
|
|
20
|
+
const period = await director.nextPeriod();
|
|
21
|
+
if (period) {
|
|
22
|
+
if (period.isTrial) {
|
|
23
|
+
await modules.enrollments.addEnrollmentPeriod(enrollment._id, {
|
|
24
|
+
...period,
|
|
25
|
+
});
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
const configuration = await director.configurationForOrder({
|
|
29
|
+
period,
|
|
30
|
+
});
|
|
31
|
+
if (configuration) {
|
|
32
|
+
const order = await services.enrollments.generateOrderFromEnrollment(enrollment, configuration);
|
|
33
|
+
if (order) {
|
|
34
|
+
await modules.enrollments.addEnrollmentPeriod(enrollment._id, {
|
|
35
|
+
...period,
|
|
36
|
+
orderId: order._id,
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
catch (e) {
|
|
43
|
+
return {
|
|
44
|
+
name: e.name,
|
|
45
|
+
message: e.message,
|
|
46
|
+
stack: e.stack,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
return null;
|
|
50
|
+
}))).filter(Boolean);
|
|
51
|
+
if (errors.length) {
|
|
52
|
+
return {
|
|
53
|
+
success: false,
|
|
54
|
+
error: {
|
|
55
|
+
name: 'SOME_ENROLLMENTS_COULD_NOT_PROCESS',
|
|
56
|
+
message: 'Some errors have been reported during order generation',
|
|
57
|
+
logs: errors,
|
|
58
|
+
},
|
|
59
|
+
result: {},
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
return {
|
|
63
|
+
success: true,
|
|
64
|
+
result: input,
|
|
65
|
+
};
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
WorkerDirector.registerAdapter(GenerateOrderWorker);
|
|
69
|
+
export const configureGenerateOrderAutoscheduling = () => {
|
|
70
|
+
if (enrollmentsSettings.autoSchedulingSchedule) {
|
|
71
|
+
WorkerDirector.configureAutoscheduling({
|
|
72
|
+
type: GenerateOrderWorker.type,
|
|
73
|
+
schedule: enrollmentsSettings.autoSchedulingSchedule,
|
|
74
|
+
retries: 5,
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
};
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { WorkStatus } from '@unchainedshop/core-worker';
|
|
2
|
+
import { WorkerDirector, WorkerAdapter, schedule } from '@unchainedshop/core';
|
|
3
|
+
const everyDayAtFourInTheNight = schedule.parse.cron('0 3 * * *');
|
|
4
|
+
export const ErrorNotifications = {
|
|
5
|
+
...WorkerAdapter,
|
|
6
|
+
key: 'shop.unchained.worker.error-notifications',
|
|
7
|
+
label: 'Send a daily report about errors that failed permanently/unrecoverable',
|
|
8
|
+
version: '1.0',
|
|
9
|
+
type: 'ERROR_NOTIFICATIONS',
|
|
10
|
+
doWork: async (args, context, workId) => {
|
|
11
|
+
const work = (await context.modules.worker.findWork({ workId }));
|
|
12
|
+
const secondsPassed = args?.secondsPassed || 60;
|
|
13
|
+
const to = new Date();
|
|
14
|
+
const from = new Date(new Date(work.scheduled).getTime() - secondsPassed * 1000);
|
|
15
|
+
try {
|
|
16
|
+
const workItems = (await context.modules.worker.findWorkQueue({
|
|
17
|
+
scheduled: {
|
|
18
|
+
end: to,
|
|
19
|
+
start: from,
|
|
20
|
+
},
|
|
21
|
+
types: undefined,
|
|
22
|
+
status: [WorkStatus.FAILED],
|
|
23
|
+
})).filter((workItem) => workItem.type !== ErrorNotifications.type && workItem.retries === 0);
|
|
24
|
+
if (!workItems.length) {
|
|
25
|
+
return { success: true, result: {} };
|
|
26
|
+
}
|
|
27
|
+
const message = await context.modules.worker.addWork({
|
|
28
|
+
type: 'MESSAGE',
|
|
29
|
+
retries: 0,
|
|
30
|
+
input: {
|
|
31
|
+
workItems,
|
|
32
|
+
template: 'ERROR_REPORT',
|
|
33
|
+
},
|
|
34
|
+
originalWorkId: workId,
|
|
35
|
+
});
|
|
36
|
+
return {
|
|
37
|
+
success: true,
|
|
38
|
+
result: { forked: message._id },
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
catch (err) {
|
|
42
|
+
return {
|
|
43
|
+
success: false,
|
|
44
|
+
error: {
|
|
45
|
+
name: err.name,
|
|
46
|
+
message: err.message,
|
|
47
|
+
stack: err.stack,
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
WorkerDirector.registerAdapter(ErrorNotifications);
|
|
54
|
+
export default ErrorNotifications;
|
|
55
|
+
const ONE_DAY_IN_SECONDS = 24 * 60 * 60;
|
|
56
|
+
WorkerDirector.configureAutoscheduling({
|
|
57
|
+
type: ErrorNotifications.type,
|
|
58
|
+
schedule: everyDayAtFourInTheNight,
|
|
59
|
+
input: async () => ({ secondsPassed: ONE_DAY_IN_SECONDS }),
|
|
60
|
+
retries: 0,
|
|
61
|
+
});
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { WorkerEventTypes } from '@unchainedshop/core-worker';
|
|
2
|
+
import { WorkerAdapter, WorkerDirector, } from '@unchainedshop/core';
|
|
3
|
+
import { subscribe } from '@unchainedshop/events';
|
|
4
|
+
export const ExportTokenWorker = {
|
|
5
|
+
...WorkerAdapter,
|
|
6
|
+
key: 'shop.unchained.worker-plugin.export-token',
|
|
7
|
+
label: 'External worker to hold the state of the minting/export process of tokens',
|
|
8
|
+
version: '1.0.0',
|
|
9
|
+
type: 'EXPORT_TOKEN',
|
|
10
|
+
external: true,
|
|
11
|
+
async doWork() {
|
|
12
|
+
throw new Error('Cannot do work for external workers');
|
|
13
|
+
},
|
|
14
|
+
};
|
|
15
|
+
export const configureExportToken = (unchainedAPI) => {
|
|
16
|
+
subscribe(WorkerEventTypes.FINISHED, async ({ payload: work }) => {
|
|
17
|
+
if (work.type === 'EXPORT_TOKEN' && work.success) {
|
|
18
|
+
await unchainedAPI.modules.warehousing.updateTokenOwnership({
|
|
19
|
+
tokenId: work.input.token._id,
|
|
20
|
+
walletAddress: work.input.recipientWalletAddress,
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
};
|
|
25
|
+
export default configureExportToken;
|
|
26
|
+
WorkerDirector.registerAdapter(ExportTokenWorker);
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { WorkerAdapter, WorkerDirector } from '@unchainedshop/core';
|
|
2
|
+
export const ExternalWorkerPlugin = {
|
|
3
|
+
...WorkerAdapter,
|
|
4
|
+
key: 'shop.unchained.worker-plugin.external',
|
|
5
|
+
label: 'External plugin as a placeholder for workers who interact with the system only via GraphQL',
|
|
6
|
+
version: '1.0.0',
|
|
7
|
+
type: 'EXTERNAL',
|
|
8
|
+
external: true,
|
|
9
|
+
async doWork() {
|
|
10
|
+
throw new Error('Cannot do work for external workers');
|
|
11
|
+
},
|
|
12
|
+
};
|
|
13
|
+
WorkerDirector.registerAdapter(ExternalWorkerPlugin);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { WorkerAdapter, WorkerDirector } from '@unchainedshop/core';
|
|
2
|
+
import { setTimeout } from 'node:timers/promises';
|
|
3
|
+
const Heartbeat = {
|
|
4
|
+
...WorkerAdapter,
|
|
5
|
+
key: 'shop.unchained.worker-plugin.heartbeat',
|
|
6
|
+
label: 'Heartbeat plugin to check if workers are working',
|
|
7
|
+
version: '1.0.0',
|
|
8
|
+
type: 'HEARTBEAT',
|
|
9
|
+
maxParallelAllocations: 1,
|
|
10
|
+
doWork: async (input) => {
|
|
11
|
+
if (input?.wait) {
|
|
12
|
+
await setTimeout(input.wait);
|
|
13
|
+
}
|
|
14
|
+
if (input?.fails) {
|
|
15
|
+
return {
|
|
16
|
+
success: false,
|
|
17
|
+
result: input,
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
return {
|
|
21
|
+
success: true,
|
|
22
|
+
result: input,
|
|
23
|
+
};
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
WorkerDirector.registerAdapter(Heartbeat);
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { WorkerAdapter, WorkerDirector } from '@unchainedshop/core';
|
|
2
|
+
const postFetch = async (url, { data, headers }) => {
|
|
3
|
+
return fetch(url, {
|
|
4
|
+
method: 'POST',
|
|
5
|
+
body: JSON.stringify(data),
|
|
6
|
+
duplex: 'half',
|
|
7
|
+
headers: { 'Content-Type': 'application/json', ...headers },
|
|
8
|
+
});
|
|
9
|
+
};
|
|
10
|
+
const nonPostFetch = async (url, { headers, method = 'GET' }) => {
|
|
11
|
+
return fetch(url, {
|
|
12
|
+
method,
|
|
13
|
+
headers,
|
|
14
|
+
});
|
|
15
|
+
};
|
|
16
|
+
const HttpRequestWorkerPlugin = {
|
|
17
|
+
...WorkerAdapter,
|
|
18
|
+
key: 'shop.unchained.worker-plugin.http-request',
|
|
19
|
+
label: 'Request a resource via http request. 200 = success',
|
|
20
|
+
version: '1.0.0',
|
|
21
|
+
type: 'HTTP_REQUEST',
|
|
22
|
+
async doWork({ url, data = {}, headers, method = 'POST' } = { method: 'POST' }) {
|
|
23
|
+
if (!url) {
|
|
24
|
+
return {
|
|
25
|
+
success: false,
|
|
26
|
+
error: {
|
|
27
|
+
name: 'URL_REQUIRED',
|
|
28
|
+
message: 'HTTP_REQUEST requires an url',
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
try {
|
|
33
|
+
const normalizedFetch = method.toUpperCase() === 'POST' ? postFetch : nonPostFetch;
|
|
34
|
+
const res = await normalizedFetch(url, { data, headers, method });
|
|
35
|
+
const contentType = res.headers.get('content-type');
|
|
36
|
+
const isJson = contentType && contentType.includes('application/json');
|
|
37
|
+
const result = isJson ? await res.json() : { text: await res.text() };
|
|
38
|
+
if (res.status === 200 && res.ok) {
|
|
39
|
+
return { success: true, result };
|
|
40
|
+
}
|
|
41
|
+
return { success: false, result };
|
|
42
|
+
}
|
|
43
|
+
catch (err) {
|
|
44
|
+
return {
|
|
45
|
+
success: false,
|
|
46
|
+
error: {
|
|
47
|
+
name: err.name,
|
|
48
|
+
message: err.message,
|
|
49
|
+
stack: err.stack,
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
WorkerDirector.registerAdapter(HttpRequestWorkerPlugin);
|
|
56
|
+
export default HttpRequestWorkerPlugin;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type IWorkerAdapter } from '@unchainedshop/core';
|
|
2
|
+
import type { Work } from '@unchainedshop/core-worker';
|
|
3
|
+
export declare const MessageWorker: IWorkerAdapter<{
|
|
4
|
+
template: string;
|
|
5
|
+
_id: string;
|
|
6
|
+
[x: string]: any;
|
|
7
|
+
}, {
|
|
8
|
+
info?: string;
|
|
9
|
+
forked?: Work[];
|
|
10
|
+
}>;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { MessagingDirector, WorkerAdapter, WorkerDirector, } from '@unchainedshop/core';
|
|
2
|
+
export const MessageWorker = {
|
|
3
|
+
...WorkerAdapter,
|
|
4
|
+
key: 'shop.unchained.worker-plugin.message',
|
|
5
|
+
label: 'Send Message by combining payload with a template and start concrete jobs',
|
|
6
|
+
version: '1.0.0',
|
|
7
|
+
type: 'MESSAGE',
|
|
8
|
+
doWork: async ({ template, ...payload }, unchainedAPI, workId) => {
|
|
9
|
+
try {
|
|
10
|
+
const templateResolver = MessagingDirector.getTemplate(template);
|
|
11
|
+
if (!templateResolver)
|
|
12
|
+
throw new Error(`No template resolver found for template ${template}`);
|
|
13
|
+
const workConfigurations = await templateResolver({
|
|
14
|
+
template,
|
|
15
|
+
...payload,
|
|
16
|
+
}, unchainedAPI);
|
|
17
|
+
if (workConfigurations.length > 0) {
|
|
18
|
+
const forked = await Promise.all(workConfigurations.map(async (workConfiguration) => {
|
|
19
|
+
const work = await unchainedAPI.modules.worker.addWork({
|
|
20
|
+
...workConfiguration,
|
|
21
|
+
originalWorkId: workId,
|
|
22
|
+
});
|
|
23
|
+
delete work.input;
|
|
24
|
+
return work;
|
|
25
|
+
}));
|
|
26
|
+
return { success: true, result: { forked } };
|
|
27
|
+
}
|
|
28
|
+
return { success: true, result: { forked: [] } };
|
|
29
|
+
}
|
|
30
|
+
catch (err) {
|
|
31
|
+
return {
|
|
32
|
+
success: false,
|
|
33
|
+
error: {
|
|
34
|
+
name: err.name,
|
|
35
|
+
message: err.message,
|
|
36
|
+
stack: err.stack,
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
WorkerDirector.registerAdapter(MessageWorker);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|