@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/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, path3, hasFunc) {
681
- path3 = castPath_default(path3, object);
682
- var index = -1, length = path3.length, result = false;
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(path3[index]);
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, path3) {
708
- return object != null && hasPath_default(object, path3, baseHas_default);
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 queueUrl = getQueueUrl(target);
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(queueUrl, body, { headers });
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 isAuth = env("DEV_IDENTITY");
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
- const jwtClaims = token && tokenUtils.decodeAccessToken(token);
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
- reply.headers(context.response.headers ?? {}).status(context.response.status).send(context.response.body);
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
- "methods": ["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"]
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,