@cloudnux/local-cloud-provider 0.19.0 → 0.21.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/dist/dev-console-plugin/index.d.ts +6 -0
- package/dist/dev-console-plugin/index.js +1048 -16
- package/dist/dev-console-plugin/index.js.map +1 -1
- package/dist/index.js +192 -177
- package/dist/index.js.map +1 -1
- package/dist/queue-plugin/index.d.ts +7 -0
- package/dist/queue-plugin/index.js +37 -2
- package/dist/queue-plugin/index.js.map +1 -1
- package/dist/schedule-plugin/index.js +1 -1
- package/dist/schedule-plugin/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -677,11 +677,11 @@ function stringToArray(string) {
|
|
|
677
677
|
var stringToArray_default = stringToArray;
|
|
678
678
|
|
|
679
679
|
// ../../../node_modules/lodash-es/_hasPath.js
|
|
680
|
-
function hasPath(object,
|
|
681
|
-
|
|
682
|
-
var index = -1, length =
|
|
680
|
+
function hasPath(object, path4, hasFunc) {
|
|
681
|
+
path4 = castPath_default(path4, object);
|
|
682
|
+
var index = -1, length = path4.length, result = false;
|
|
683
683
|
while (++index < length) {
|
|
684
|
-
var key = toKey_default(
|
|
684
|
+
var key = toKey_default(path4[index]);
|
|
685
685
|
if (!(result = object != null && hasFunc(object, key))) {
|
|
686
686
|
break;
|
|
687
687
|
}
|
|
@@ -704,8 +704,8 @@ function baseHas(object, key) {
|
|
|
704
704
|
var baseHas_default = baseHas;
|
|
705
705
|
|
|
706
706
|
// ../../../node_modules/lodash-es/has.js
|
|
707
|
-
function has(object,
|
|
708
|
-
return object != null && hasPath_default(object,
|
|
707
|
+
function has(object, path4) {
|
|
708
|
+
return object != null && hasPath_default(object, path4, baseHas_default);
|
|
709
709
|
}
|
|
710
710
|
var has_default = has;
|
|
711
711
|
|
|
@@ -823,147 +823,11 @@ var utils = {
|
|
|
823
823
|
};
|
|
824
824
|
var env = Object.assign(_env, utils);
|
|
825
825
|
|
|
826
|
-
// ../../utils/src/auth/token.ts
|
|
827
|
-
import * as crypto from "crypto";
|
|
828
|
-
function parseTimeToMs(time) {
|
|
829
|
-
if (typeof time === "number") return time * 1e3;
|
|
830
|
-
const match = time.match(/^(\d+(?:\.\d+)?)\s*(ms|s|m|h|d)$/);
|
|
831
|
-
if (!match) return 0;
|
|
832
|
-
const value = parseFloat(match[1]);
|
|
833
|
-
const unit = match[2];
|
|
834
|
-
switch (unit) {
|
|
835
|
-
case "ms":
|
|
836
|
-
return value;
|
|
837
|
-
case "s":
|
|
838
|
-
return value * 1e3;
|
|
839
|
-
case "m":
|
|
840
|
-
return value * 60 * 1e3;
|
|
841
|
-
case "h":
|
|
842
|
-
return value * 60 * 60 * 1e3;
|
|
843
|
-
case "d":
|
|
844
|
-
return value * 24 * 60 * 60 * 1e3;
|
|
845
|
-
default:
|
|
846
|
-
return 0;
|
|
847
|
-
}
|
|
848
|
-
}
|
|
849
|
-
function base64UrlEncode(input) {
|
|
850
|
-
const str = typeof input === "string" ? input : input.toString("base64");
|
|
851
|
-
return str.replace(/=/g, "").replace(/\+/g, "-").replace(/\//g, "_");
|
|
852
|
-
}
|
|
853
|
-
function base64UrlDecode(input) {
|
|
854
|
-
let str = input.replace(/-/g, "+").replace(/_/g, "/");
|
|
855
|
-
while (str.length % 4) {
|
|
856
|
-
str += "=";
|
|
857
|
-
}
|
|
858
|
-
return Buffer.from(str, "base64");
|
|
859
|
-
}
|
|
860
|
-
function createAccessToken(claims, privateKey, expiresIn = "1h") {
|
|
861
|
-
const now = Math.floor(Date.now() / 1e3);
|
|
862
|
-
const header = {
|
|
863
|
-
alg: "HS256",
|
|
864
|
-
typ: "JWT"
|
|
865
|
-
};
|
|
866
|
-
const payload = {
|
|
867
|
-
...claims,
|
|
868
|
-
aud: ["*"],
|
|
869
|
-
iat: now,
|
|
870
|
-
iss: "vcg-identity",
|
|
871
|
-
jti: Date.now().toString()
|
|
872
|
-
};
|
|
873
|
-
if (expiresIn) {
|
|
874
|
-
const expiryMs = parseTimeToMs(expiresIn);
|
|
875
|
-
payload.exp = now + Math.floor(expiryMs / 1e3);
|
|
876
|
-
}
|
|
877
|
-
const notBefore = "100ms";
|
|
878
|
-
if (notBefore) {
|
|
879
|
-
const notBeforeMs = parseTimeToMs(notBefore);
|
|
880
|
-
payload.nbf = now + Math.floor(notBeforeMs / 1e3);
|
|
881
|
-
}
|
|
882
|
-
const encodedHeader = base64UrlEncode(JSON.stringify(header));
|
|
883
|
-
const encodedPayload = base64UrlEncode(JSON.stringify(payload));
|
|
884
|
-
const signatureInput = `${encodedHeader}.${encodedPayload}`;
|
|
885
|
-
const signature = crypto.createHmac("sha256", privateKey).update(signatureInput).digest();
|
|
886
|
-
const encodedSignature = base64UrlEncode(signature);
|
|
887
|
-
return `${encodedHeader}.${encodedPayload}.${encodedSignature}`;
|
|
888
|
-
}
|
|
889
|
-
function createRefreshToken() {
|
|
890
|
-
const bytes = crypto.randomBytes(16);
|
|
891
|
-
bytes[6] = bytes[6] & 15 | 64;
|
|
892
|
-
bytes[8] = bytes[8] & 63 | 128;
|
|
893
|
-
return bytes.toString("hex");
|
|
894
|
-
}
|
|
895
|
-
function decode(token) {
|
|
896
|
-
try {
|
|
897
|
-
const parts = token.split(".");
|
|
898
|
-
if (parts.length !== 3) return null;
|
|
899
|
-
const payload = JSON.parse(
|
|
900
|
-
base64UrlDecode(parts[1]).toString("utf8")
|
|
901
|
-
);
|
|
902
|
-
return payload;
|
|
903
|
-
} catch {
|
|
904
|
-
return null;
|
|
905
|
-
}
|
|
906
|
-
}
|
|
907
|
-
function verify(token, privateKey, options = {}) {
|
|
908
|
-
return new Promise((resolve, reject) => {
|
|
909
|
-
try {
|
|
910
|
-
const parts = token.split(".");
|
|
911
|
-
if (parts.length !== 3) {
|
|
912
|
-
return reject(new Error("Invalid token format"));
|
|
913
|
-
}
|
|
914
|
-
const [encodedHeader, encodedPayload, encodedSignature] = parts;
|
|
915
|
-
const signatureInput = `${encodedHeader}.${encodedPayload}`;
|
|
916
|
-
const expectedSignature = crypto.createHmac("sha256", privateKey).update(signatureInput).digest();
|
|
917
|
-
const providedSignature = base64UrlDecode(encodedSignature);
|
|
918
|
-
const signatureValid = crypto.timingSafeEqual(
|
|
919
|
-
expectedSignature,
|
|
920
|
-
providedSignature
|
|
921
|
-
);
|
|
922
|
-
if (!signatureValid) {
|
|
923
|
-
return reject(new Error("Invalid signature"));
|
|
924
|
-
}
|
|
925
|
-
const payload = JSON.parse(
|
|
926
|
-
base64UrlDecode(encodedPayload).toString("utf8")
|
|
927
|
-
);
|
|
928
|
-
if (!options.ignoreExpiration && payload.exp) {
|
|
929
|
-
const now = Math.floor(Date.now() / 1e3);
|
|
930
|
-
if (now >= payload.exp) {
|
|
931
|
-
return reject(new Error("Token expired"));
|
|
932
|
-
}
|
|
933
|
-
}
|
|
934
|
-
if (payload.nbf) {
|
|
935
|
-
const now = Math.floor(Date.now() / 1e3);
|
|
936
|
-
if (now < payload.nbf) {
|
|
937
|
-
return reject(new Error("Token not yet valid"));
|
|
938
|
-
}
|
|
939
|
-
}
|
|
940
|
-
if (options.issuer && payload.iss !== options.issuer) {
|
|
941
|
-
return reject(new Error("Invalid issuer"));
|
|
942
|
-
}
|
|
943
|
-
if (options.audience) {
|
|
944
|
-
const audiences = Array.isArray(options.audience) ? options.audience : [options.audience];
|
|
945
|
-
const tokenAudiences = Array.isArray(payload.aud) ? payload.aud : [payload.aud];
|
|
946
|
-
const validAudience = audiences.some(
|
|
947
|
-
(aud) => tokenAudiences.includes(aud)
|
|
948
|
-
);
|
|
949
|
-
if (!validAudience) {
|
|
950
|
-
return reject(new Error("Invalid audience"));
|
|
951
|
-
}
|
|
952
|
-
}
|
|
953
|
-
resolve(payload);
|
|
954
|
-
} catch (error) {
|
|
955
|
-
reject(error);
|
|
956
|
-
}
|
|
957
|
-
});
|
|
958
|
-
}
|
|
959
|
-
var tokenUtils = {
|
|
960
|
-
createAccessToken,
|
|
961
|
-
createRefreshToken,
|
|
962
|
-
decodeAccessToken: decode,
|
|
963
|
-
verifyAccessToken: verify
|
|
964
|
-
};
|
|
965
|
-
|
|
966
826
|
// src/services/event-broker.ts
|
|
827
|
+
var TOPIC_PREFIX = "topic:";
|
|
828
|
+
function isTopicTarget(target) {
|
|
829
|
+
return target.startsWith(TOPIC_PREFIX);
|
|
830
|
+
}
|
|
967
831
|
function createLocalEventBrokerService() {
|
|
968
832
|
const baseURL = env("DEV_CLOUD_EVENT_BROKER_URL");
|
|
969
833
|
function getQueueUrl(queueName) {
|
|
@@ -972,6 +836,10 @@ function createLocalEventBrokerService() {
|
|
|
972
836
|
}
|
|
973
837
|
return `${baseURL}/${queueName}`;
|
|
974
838
|
}
|
|
839
|
+
function getTopicUrl(target) {
|
|
840
|
+
const topicName = target.slice(TOPIC_PREFIX.length);
|
|
841
|
+
return `${baseURL}/topics/${topicName}`;
|
|
842
|
+
}
|
|
975
843
|
function parseMessage(data) {
|
|
976
844
|
const body = typeof data.body === "object" ? JSON.stringify(data.body) : data.body ?? "";
|
|
977
845
|
return {
|
|
@@ -993,7 +861,7 @@ function createLocalEventBrokerService() {
|
|
|
993
861
|
async publish(target, message, options) {
|
|
994
862
|
if (!target)
|
|
995
863
|
throw new Error("Target queue name or URL is required for publishing a message.");
|
|
996
|
-
const
|
|
864
|
+
const publishUrl = isTopicTarget(target) ? getTopicUrl(target) : getQueueUrl(target);
|
|
997
865
|
const headers = {};
|
|
998
866
|
if (options?.attributes) {
|
|
999
867
|
Object.entries(options.attributes).forEach(([key, value]) => {
|
|
@@ -1010,7 +878,7 @@ function createLocalEventBrokerService() {
|
|
|
1010
878
|
headers["x-delay-seconds"] = options.delaySeconds.toString();
|
|
1011
879
|
}
|
|
1012
880
|
const body = typeof message === "string" ? message : message;
|
|
1013
|
-
await axios.post(
|
|
881
|
+
await axios.post(publishUrl, body, { headers });
|
|
1014
882
|
},
|
|
1015
883
|
/**
|
|
1016
884
|
* Peek at messages in a queue without removing them
|
|
@@ -1586,6 +1454,7 @@ function createLocalLocationService() {
|
|
|
1586
1454
|
// src/services/functions.ts
|
|
1587
1455
|
import * as querystring from "querystring";
|
|
1588
1456
|
import "fastify-raw-body";
|
|
1457
|
+
import { parseCookieHeader } from "@cloudnux/core-cloud-provider";
|
|
1589
1458
|
var getFullUrlFromRequest = (request) => {
|
|
1590
1459
|
const protocol = request.protocol;
|
|
1591
1460
|
const hostname = request.hostname;
|
|
@@ -1601,13 +1470,11 @@ function createLocalFunctionsService() {
|
|
|
1601
1470
|
return {
|
|
1602
1471
|
createHttRequest(request) {
|
|
1603
1472
|
const rawBody = request.rawBody;
|
|
1604
|
-
const
|
|
1605
|
-
const index = request.raw.rawHeaders ? request.raw.rawHeaders?.indexOf("Authorization") : -1;
|
|
1606
|
-
const authorization = index !== -1 ? request.raw.rawHeaders[index + 1] : null;
|
|
1607
|
-
const token = authorization ? authorization.replace(/^bearer\s+/i, "") : null;
|
|
1473
|
+
const cookieHeader = request.headers.cookie;
|
|
1608
1474
|
const httpRequest = {
|
|
1609
1475
|
body: String(rawBody),
|
|
1610
1476
|
headers: request.headers,
|
|
1477
|
+
cookies: parseCookieHeader(Array.isArray(cookieHeader) ? cookieHeader.join("; ") : cookieHeader),
|
|
1611
1478
|
method: request.method,
|
|
1612
1479
|
url: getFullUrlFromRequest(request),
|
|
1613
1480
|
matchingKey: request.routeOptions.url,
|
|
@@ -1617,28 +1484,7 @@ function createLocalFunctionsService() {
|
|
|
1617
1484
|
requestId: request.id,
|
|
1618
1485
|
moduleName: request.routeOptions.config?.module
|
|
1619
1486
|
};
|
|
1620
|
-
|
|
1621
|
-
const httpAuth = isAuth ? {
|
|
1622
|
-
appId: env("DEV_APP_ID"),
|
|
1623
|
-
memberId: env("DEV_MEMBER_ID"),
|
|
1624
|
-
customerId: env("DEV_CUSTOMER_ID"),
|
|
1625
|
-
identity: env("DEV_IDENTITY"),
|
|
1626
|
-
token: token ?? "",
|
|
1627
|
-
claims: {
|
|
1628
|
-
app_id: env("DEV_APP_ID"),
|
|
1629
|
-
member_id: env("DEV_MEMBER_ID"),
|
|
1630
|
-
customer_id: env("DEV_CUSTOMER_ID"),
|
|
1631
|
-
identity: env("DEV_IDENTITY", "facebook")
|
|
1632
|
-
}
|
|
1633
|
-
} : jwtClaims ? {
|
|
1634
|
-
token,
|
|
1635
|
-
claims: jwtClaims,
|
|
1636
|
-
appId: jwtClaims.app_id,
|
|
1637
|
-
memberId: jwtClaims.member_id,
|
|
1638
|
-
customerId: jwtClaims.customer_id,
|
|
1639
|
-
identity: jwtClaims.identity
|
|
1640
|
-
} : void 0;
|
|
1641
|
-
return [httpRequest, httpAuth];
|
|
1487
|
+
return [httpRequest];
|
|
1642
1488
|
},
|
|
1643
1489
|
createScheduleRequest: (job, execution) => {
|
|
1644
1490
|
const scheduleName = job.name;
|
|
@@ -1685,7 +1531,11 @@ function createLocalFunctionsService() {
|
|
|
1685
1531
|
return [invokeRequest];
|
|
1686
1532
|
},
|
|
1687
1533
|
buildHttpResponse: (context, _, reply) => {
|
|
1688
|
-
|
|
1534
|
+
const headers = { ...context.response.headers ?? {} };
|
|
1535
|
+
if (context.response.cookies?.length) {
|
|
1536
|
+
headers["set-cookie"] = context.response.cookies;
|
|
1537
|
+
}
|
|
1538
|
+
reply.headers(headers).status(context.response.status).send(context.response.body);
|
|
1689
1539
|
},
|
|
1690
1540
|
buildScheduleResponse: (context) => {
|
|
1691
1541
|
if (context.response.status === "error") {
|
|
@@ -1883,6 +1733,168 @@ function createLocalLoggerService() {
|
|
|
1883
1733
|
return logger;
|
|
1884
1734
|
}
|
|
1885
1735
|
|
|
1736
|
+
// src/services/email.ts
|
|
1737
|
+
import * as fs3 from "fs";
|
|
1738
|
+
import * as path3 from "path";
|
|
1739
|
+
import { randomUUID } from "crypto";
|
|
1740
|
+
import { getService as getService3 } from "@cloudnux/core-cloud-provider";
|
|
1741
|
+
var { mkdir: mkdir3, writeFile: writeFile3, readdir, readFile: readFile3, unlink: unlink2 } = fs3.promises;
|
|
1742
|
+
var MAX_HISTORY = 100;
|
|
1743
|
+
var PREVIEW_LENGTH = 150;
|
|
1744
|
+
function resolveBaseDir() {
|
|
1745
|
+
return env("DEV_CLOUD_EMAIL_DIR", path3.join(process.cwd(), ".local-email"));
|
|
1746
|
+
}
|
|
1747
|
+
function toArray(value) {
|
|
1748
|
+
if (!value) return [];
|
|
1749
|
+
return Array.isArray(value) ? value : [value];
|
|
1750
|
+
}
|
|
1751
|
+
function assertValidMessage(message) {
|
|
1752
|
+
if (!message.to || Array.isArray(message.to) && message.to.length === 0) {
|
|
1753
|
+
throw new Error("At least one recipient (to) is required");
|
|
1754
|
+
}
|
|
1755
|
+
if (!message.from) {
|
|
1756
|
+
throw new Error("Sender (from) is required");
|
|
1757
|
+
}
|
|
1758
|
+
if (!message.subject) {
|
|
1759
|
+
throw new Error("Subject is required");
|
|
1760
|
+
}
|
|
1761
|
+
if (!message.html && !message.text) {
|
|
1762
|
+
throw new Error("At least one of html or text is required");
|
|
1763
|
+
}
|
|
1764
|
+
}
|
|
1765
|
+
function slugify(subject) {
|
|
1766
|
+
const slug = subject.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
1767
|
+
return (slug || "email").slice(0, 50);
|
|
1768
|
+
}
|
|
1769
|
+
function stripHtml(html) {
|
|
1770
|
+
return html.replace(/<style[\s\S]*?<\/style>/gi, " ").replace(/<script[\s\S]*?<\/script>/gi, " ").replace(/<[^>]+>/g, " ");
|
|
1771
|
+
}
|
|
1772
|
+
function buildPreview(message) {
|
|
1773
|
+
const source = message.text ?? stripHtml(message.html ?? "");
|
|
1774
|
+
const collapsed = source.replace(/\s+/g, " ").trim();
|
|
1775
|
+
return collapsed.length > PREVIEW_LENGTH ? collapsed.slice(0, PREVIEW_LENGTH).trimEnd() + "\u2026" : collapsed;
|
|
1776
|
+
}
|
|
1777
|
+
function encodeHeaderValue(value) {
|
|
1778
|
+
return /^[\x00-\x7F]*$/.test(value) ? value : `=?UTF-8?B?${Buffer.from(value, "utf8").toString("base64")}?=`;
|
|
1779
|
+
}
|
|
1780
|
+
function randomBoundary() {
|
|
1781
|
+
return `----=_Part_${randomUUID().replace(/-/g, "")}`;
|
|
1782
|
+
}
|
|
1783
|
+
function base64Wrap(data) {
|
|
1784
|
+
return data.toString("base64").replace(/(.{76})/g, "$1\r\n");
|
|
1785
|
+
}
|
|
1786
|
+
function textPart(contentType, data) {
|
|
1787
|
+
return [
|
|
1788
|
+
`Content-Type: ${contentType}; charset=UTF-8`,
|
|
1789
|
+
"Content-Transfer-Encoding: base64",
|
|
1790
|
+
"",
|
|
1791
|
+
base64Wrap(Buffer.from(data, "utf8"))
|
|
1792
|
+
].join("\r\n");
|
|
1793
|
+
}
|
|
1794
|
+
function attachmentContent(attachment) {
|
|
1795
|
+
return Buffer.isBuffer(attachment.content) ? attachment.content : Buffer.from(attachment.content, "base64");
|
|
1796
|
+
}
|
|
1797
|
+
function attachmentPart(attachment) {
|
|
1798
|
+
return [
|
|
1799
|
+
`Content-Type: ${attachment.contentType}; name="${attachment.filename}"`,
|
|
1800
|
+
`Content-Disposition: attachment; filename="${attachment.filename}"`,
|
|
1801
|
+
"Content-Transfer-Encoding: base64",
|
|
1802
|
+
"",
|
|
1803
|
+
base64Wrap(attachmentContent(attachment))
|
|
1804
|
+
].join("\r\n");
|
|
1805
|
+
}
|
|
1806
|
+
function multipart(boundary, type, parts) {
|
|
1807
|
+
const body = parts.map((part) => `--${boundary}\r
|
|
1808
|
+
${part}`).join("\r\n");
|
|
1809
|
+
return `Content-Type: multipart/${type}; boundary="${boundary}"\r
|
|
1810
|
+
\r
|
|
1811
|
+
${body}\r
|
|
1812
|
+
--${boundary}--`;
|
|
1813
|
+
}
|
|
1814
|
+
function buildEml(message, messageId) {
|
|
1815
|
+
const headers = [
|
|
1816
|
+
`From: ${message.from}`,
|
|
1817
|
+
`To: ${toArray(message.to).join(", ")}`
|
|
1818
|
+
];
|
|
1819
|
+
const cc = toArray(message.cc);
|
|
1820
|
+
if (cc.length) headers.push(`Cc: ${cc.join(", ")}`);
|
|
1821
|
+
const bcc = toArray(message.bcc);
|
|
1822
|
+
if (bcc.length) headers.push(`Bcc: ${bcc.join(", ")}`);
|
|
1823
|
+
if (message.replyTo) headers.push(`Reply-To: ${message.replyTo}`);
|
|
1824
|
+
headers.push(`Subject: ${encodeHeaderValue(message.subject)}`);
|
|
1825
|
+
headers.push(`Date: ${(/* @__PURE__ */ new Date()).toUTCString()}`);
|
|
1826
|
+
headers.push(`Message-ID: <${messageId}@local-cloud-provider>`);
|
|
1827
|
+
headers.push("MIME-Version: 1.0");
|
|
1828
|
+
const bodyParts = [];
|
|
1829
|
+
if (message.text) bodyParts.push(textPart("text/plain", message.text));
|
|
1830
|
+
if (message.html) bodyParts.push(textPart("text/html", message.html));
|
|
1831
|
+
const bodyContent = bodyParts.length > 1 ? multipart(randomBoundary(), "alternative", bodyParts) : bodyParts[0];
|
|
1832
|
+
if (!message.attachments?.length) {
|
|
1833
|
+
return [...headers, bodyContent].join("\r\n");
|
|
1834
|
+
}
|
|
1835
|
+
const attachmentParts = message.attachments.map(attachmentPart);
|
|
1836
|
+
const mixedBody = multipart(randomBoundary(), "mixed", [bodyContent, ...attachmentParts]);
|
|
1837
|
+
return [...headers, mixedBody].join("\r\n");
|
|
1838
|
+
}
|
|
1839
|
+
async function listEntryIds(baseDir) {
|
|
1840
|
+
if (!fs3.existsSync(baseDir)) return [];
|
|
1841
|
+
const files = await readdir(baseDir);
|
|
1842
|
+
return files.filter((file) => file.endsWith(".json")).map((file) => file.slice(0, -".json".length)).sort().reverse();
|
|
1843
|
+
}
|
|
1844
|
+
async function enforceRetention(baseDir, logger2) {
|
|
1845
|
+
const ids = await listEntryIds(baseDir);
|
|
1846
|
+
const overflow = ids.length - MAX_HISTORY;
|
|
1847
|
+
if (overflow <= 0) return;
|
|
1848
|
+
const toRemove = ids.slice(-overflow);
|
|
1849
|
+
await Promise.all(toRemove.map(async (id) => {
|
|
1850
|
+
try {
|
|
1851
|
+
await unlink2(path3.join(baseDir, `${id}.eml`));
|
|
1852
|
+
await unlink2(path3.join(baseDir, `${id}.json`));
|
|
1853
|
+
} catch (error) {
|
|
1854
|
+
logger2.warn({ error }, `Failed to prune local email history entry ${id}`);
|
|
1855
|
+
}
|
|
1856
|
+
}));
|
|
1857
|
+
}
|
|
1858
|
+
function createLocalEmailService() {
|
|
1859
|
+
const baseDir = resolveBaseDir();
|
|
1860
|
+
const logger2 = getService3("logger");
|
|
1861
|
+
if (!fs3.existsSync(baseDir)) {
|
|
1862
|
+
fs3.mkdirSync(baseDir, { recursive: true });
|
|
1863
|
+
}
|
|
1864
|
+
return {
|
|
1865
|
+
async send(message) {
|
|
1866
|
+
assertValidMessage(message);
|
|
1867
|
+
const messageId = randomUUID();
|
|
1868
|
+
const id = `${Date.now()}-${slugify(message.subject)}-${Math.random().toString(36).slice(2, 7)}`;
|
|
1869
|
+
try {
|
|
1870
|
+
await mkdir3(baseDir, { recursive: true });
|
|
1871
|
+
const eml = buildEml(message, messageId);
|
|
1872
|
+
await writeFile3(path3.join(baseDir, `${id}.eml`), eml, "utf8");
|
|
1873
|
+
const metadata = {
|
|
1874
|
+
id,
|
|
1875
|
+
messageId,
|
|
1876
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1877
|
+
from: message.from,
|
|
1878
|
+
to: toArray(message.to),
|
|
1879
|
+
cc: toArray(message.cc).length ? toArray(message.cc) : void 0,
|
|
1880
|
+
subject: message.subject,
|
|
1881
|
+
preview: buildPreview(message),
|
|
1882
|
+
hasAttachments: !!message.attachments?.length,
|
|
1883
|
+
attachmentCount: message.attachments?.length || void 0,
|
|
1884
|
+
configurationSet: message.configurationSet,
|
|
1885
|
+
status: "sent"
|
|
1886
|
+
};
|
|
1887
|
+
await writeFile3(path3.join(baseDir, `${id}.json`), JSON.stringify(metadata, null, 2), "utf8");
|
|
1888
|
+
await enforceRetention(baseDir, logger2);
|
|
1889
|
+
return { status: "sent", messageId };
|
|
1890
|
+
} catch (error) {
|
|
1891
|
+
logger2.error({ error }, "Failed to write local email");
|
|
1892
|
+
return { status: "failed", error: error instanceof Error ? error.message : String(error) };
|
|
1893
|
+
}
|
|
1894
|
+
}
|
|
1895
|
+
};
|
|
1896
|
+
}
|
|
1897
|
+
|
|
1886
1898
|
// src/router/index.ts
|
|
1887
1899
|
import Fastify from "fastify";
|
|
1888
1900
|
import fastifyRawBody from "fastify-raw-body";
|
|
@@ -1899,7 +1911,9 @@ function createRouter() {
|
|
|
1899
1911
|
encoding: "utf8"
|
|
1900
1912
|
});
|
|
1901
1913
|
fastify.register(cors, {
|
|
1902
|
-
|
|
1914
|
+
methods: ["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"],
|
|
1915
|
+
origin: true,
|
|
1916
|
+
credentials: true
|
|
1903
1917
|
});
|
|
1904
1918
|
fastify.register(fastifyPrintRoutes);
|
|
1905
1919
|
fastify.addHook("onRequest", (request, _reply, done) => {
|
|
@@ -1936,7 +1950,8 @@ var localCloudProvider = {
|
|
|
1936
1950
|
createFunctionsService: createLocalFunctionsService,
|
|
1937
1951
|
createWebSocketService: createLocalWebSocketService,
|
|
1938
1952
|
createInvokeService: createLocalInvokeService,
|
|
1939
|
-
createLoggerService: createLocalLoggerService
|
|
1953
|
+
createLoggerService: createLocalLoggerService,
|
|
1954
|
+
createEmailService: createLocalEmailService
|
|
1940
1955
|
};
|
|
1941
1956
|
export {
|
|
1942
1957
|
createRouter,
|