conversion api wrapper

This commit is contained in:
Elijah Duffy
2025-12-18 12:56:49 -08:00
parent 99c1f003c6
commit 2e12d281ef
7 changed files with 593 additions and 0 deletions

View File

@@ -62,7 +62,10 @@
"svelte"
],
"dependencies": {
"@types/facebook-nodejs-business-sdk": "^23.0.0",
"@types/umami": "^2.10.1",
"facebook-nodejs-business-sdk": "^24.0.1",
"http-status-codes": "^2.3.0",
"loglevel": "^1.9.2"
},
"publishConfig": {

259
pnpm-lock.yaml generated
View File

@@ -8,9 +8,18 @@ importers:
.:
dependencies:
'@types/facebook-nodejs-business-sdk':
specifier: ^23.0.0
version: 23.0.0
'@types/umami':
specifier: ^2.10.1
version: 2.10.1
facebook-nodejs-business-sdk:
specifier: ^24.0.1
version: 24.0.1
http-status-codes:
specifier: ^2.3.0
version: 2.3.0
loglevel:
specifier: ^1.9.2
version: 1.9.2
@@ -473,6 +482,9 @@ packages:
'@types/estree@1.0.8':
resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==}
'@types/facebook-nodejs-business-sdk@23.0.0':
resolution: {integrity: sha512-k9saLiTd4kvBCdBHEpTyDMYlqSsiDw0yHs2ePe5M1PTIldtdtNOGx73Gtu8dQqbTAlwBWbiG3VfI+9s45Cf88w==}
'@types/json-schema@7.0.15':
resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
@@ -565,6 +577,12 @@ packages:
resolution: {integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==}
engines: {node: '>= 0.4'}
asynckit@0.4.0:
resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==}
axios@1.13.2:
resolution: {integrity: sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==}
axobject-query@4.1.0:
resolution: {integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==}
engines: {node: '>= 0.4'}
@@ -578,6 +596,10 @@ packages:
brace-expansion@2.0.2:
resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==}
call-bind-apply-helpers@1.0.2:
resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==}
engines: {node: '>= 0.4'}
callsites@3.1.0:
resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==}
engines: {node: '>=6'}
@@ -605,6 +627,10 @@ packages:
color-name@1.1.4:
resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
combined-stream@1.0.8:
resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==}
engines: {node: '>= 0.8'}
concat-map@0.0.1:
resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
@@ -621,6 +647,9 @@ packages:
engines: {node: '>=4'}
hasBin: true
currency-codes@1.5.1:
resolution: {integrity: sha512-hqy8vtlIYKzO6pe2TE0V4/riZALIc7nhtE9cvxk5FDRCvfGplgzUvpTmZlMsyO+NeK5U41j+sQXJOo8l8v9kdg==}
debug@4.4.3:
resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==}
engines: {node: '>=6.0'}
@@ -640,9 +669,37 @@ packages:
resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==}
engines: {node: '>=0.10.0'}
delayed-stream@1.0.0:
resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==}
engines: {node: '>=0.4.0'}
devalue@5.6.1:
resolution: {integrity: sha512-jDwizj+IlEZBunHcOuuFVBnIMPAEHvTsJj0BcIp94xYguLRVBcXO853px/MyIJvbVzWdsGvrRweIUWJw8hBP7A==}
dunder-proto@1.0.1:
resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==}
engines: {node: '>= 0.4'}
email-validator@2.0.4:
resolution: {integrity: sha512-gYCwo7kh5S3IDyZPLZf6hSS0MnZT8QmJFqYvbqlDZSbwdZlY6QZWxJ4i/6UhITOJ4XzyI647Bm2MXKCLqnJ4nQ==}
engines: {node: '>4.0'}
es-define-property@1.0.1:
resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==}
engines: {node: '>= 0.4'}
es-errors@1.3.0:
resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==}
engines: {node: '>= 0.4'}
es-object-atoms@1.1.1:
resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==}
engines: {node: '>= 0.4'}
es-set-tostringtag@2.1.0:
resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==}
engines: {node: '>= 0.4'}
esbuild@0.27.1:
resolution: {integrity: sha512-yY35KZckJJuVVPXpvjgxiCuVEJT67F6zDeVTv4rizyPrfGBUpZQsvmxnN+C371c2esD/hNMjj4tpBhuueLN7aA==}
engines: {node: '>=18'}
@@ -716,6 +773,9 @@ packages:
resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==}
engines: {node: '>=0.10.0'}
facebook-nodejs-business-sdk@24.0.1:
resolution: {integrity: sha512-31uR42FO+V3ikQfVZyrnoc34pVYzxo4PE+BnKt1ziX9nGhSwsS7ogGDsMJQk7A3SdoWPIGgR2G4UbAY2s9zNVQ==}
fast-deep-equal@3.1.3:
resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
@@ -742,6 +802,9 @@ packages:
resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==}
engines: {node: '>=10'}
first-match@0.0.1:
resolution: {integrity: sha512-VvKbnaxrC0polTFDC+teKPTdl2mn6B/KUW+WB3C9RzKDeNwbzfLdnUz3FxC+tnjvus6bI0jWrWicQyVIPdS37A==}
flat-cache@4.0.1:
resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==}
engines: {node: '>=16'}
@@ -749,11 +812,35 @@ packages:
flatted@3.3.3:
resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==}
follow-redirects@1.15.11:
resolution: {integrity: sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==}
engines: {node: '>=4.0'}
peerDependencies:
debug: '*'
peerDependenciesMeta:
debug:
optional: true
form-data@4.0.5:
resolution: {integrity: sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==}
engines: {node: '>= 6'}
fsevents@2.3.3:
resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
os: [darwin]
function-bind@1.1.2:
resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==}
get-intrinsic@1.3.0:
resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==}
engines: {node: '>= 0.4'}
get-proto@1.0.1:
resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==}
engines: {node: '>= 0.4'}
glob-parent@6.0.2:
resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==}
engines: {node: '>=10.13.0'}
@@ -766,10 +853,29 @@ packages:
resolution: {integrity: sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ==}
engines: {node: '>=18'}
gopd@1.2.0:
resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==}
engines: {node: '>= 0.4'}
has-flag@4.0.0:
resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==}
engines: {node: '>=8'}
has-symbols@1.1.0:
resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==}
engines: {node: '>= 0.4'}
has-tostringtag@1.0.2:
resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==}
engines: {node: '>= 0.4'}
hasown@2.0.2:
resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
engines: {node: '>= 0.4'}
http-status-codes@2.3.0:
resolution: {integrity: sha512-RJ8XvFvpPM/Dmc5SV+dC4y5PCeOhT3x1Hq0NU3rjGeg5a/CqlhZ7uudknPwZFz4aeAXDcbAyaeP7GAo9lvngtA==}
ignore@5.3.2:
resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==}
engines: {node: '>= 4'}
@@ -800,6 +906,12 @@ packages:
isexe@2.0.0:
resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
iso-3166-1@2.1.1:
resolution: {integrity: sha512-RZxXf8cw5Y8LyHZIwIRvKw8sWTIHh2/txBT+ehO0QroesVfnz3JNFFX4i/OC/Yuv2bDIVYrHna5PMvjtpefq5w==}
js-sha256@0.9.0:
resolution: {integrity: sha512-sga3MHh9sgQN2+pJ9VYZ+1LPwXOxuBJBA5nrR5/ofPfuiJBE2hnjsaN8se8JznOmGLN2p49Pe5U/ttafcs/apA==}
js-yaml@4.1.1:
resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==}
hasBin: true
@@ -848,6 +960,18 @@ packages:
magic-string@0.30.21:
resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==}
math-intrinsics@1.1.0:
resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==}
engines: {node: '>= 0.4'}
mime-db@1.52.0:
resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==}
engines: {node: '>= 0.6'}
mime-types@2.1.35:
resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==}
engines: {node: '>= 0.6'}
minimatch@3.1.2:
resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
@@ -855,6 +979,9 @@ packages:
resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==}
engines: {node: '>=16 || 14 >=14.17'}
mixwith@0.1.1:
resolution: {integrity: sha512-DQsf/liljH/9e+94jR+xfK8vlKceeKdOM9H9UEXLwGuvEEpO6debNtJ9yt1ZKzPKPrwqGxzMdu0BR1fnQb6i4A==}
mri@1.2.0:
resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==}
engines: {node: '>=4'}
@@ -874,6 +1001,9 @@ packages:
natural-compare@1.4.0:
resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
nub@0.0.0:
resolution: {integrity: sha512-dK0Ss9C34R/vV0FfYJXuqDAqHlaW9fvWVufq9MmGF2umCuDbd5GRfRD9fpi/LiM0l4ZXf8IBB+RYmZExqCrf0w==}
optionator@0.9.4:
resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==}
engines: {node: '>= 0.8.0'}
@@ -955,6 +1085,9 @@ packages:
engines: {node: '>=14'}
hasBin: true
proxy-from-env@1.1.0:
resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==}
publint@0.3.16:
resolution: {integrity: sha512-MFqyfRLAExPVZdTQFwkAQELzA8idyXzROVOytg6nEJ/GEypXBUmMGrVaID8cTuzRS1U5L8yTOdOJtMXgFUJAeA==}
engines: {node: '>=18'}
@@ -1447,6 +1580,8 @@ snapshots:
'@types/estree@1.0.8': {}
'@types/facebook-nodejs-business-sdk@23.0.0': {}
'@types/json-schema@7.0.15': {}
'@types/node@24.10.4':
@@ -1567,6 +1702,16 @@ snapshots:
aria-query@5.3.2: {}
asynckit@0.4.0: {}
axios@1.13.2:
dependencies:
follow-redirects: 1.15.11
form-data: 4.0.5
proxy-from-env: 1.1.0
transitivePeerDependencies:
- debug
axobject-query@4.1.0: {}
balanced-match@1.0.2: {}
@@ -1580,6 +1725,11 @@ snapshots:
dependencies:
balanced-match: 1.0.2
call-bind-apply-helpers@1.0.2:
dependencies:
es-errors: 1.3.0
function-bind: 1.1.2
callsites@3.1.0: {}
chalk@4.1.2:
@@ -1603,6 +1753,10 @@ snapshots:
color-name@1.1.4: {}
combined-stream@1.0.8:
dependencies:
delayed-stream: 1.0.0
concat-map@0.0.1: {}
cookie@0.6.0: {}
@@ -1615,6 +1769,11 @@ snapshots:
cssesc@3.0.0: {}
currency-codes@1.5.1:
dependencies:
first-match: 0.0.1
nub: 0.0.0
debug@4.4.3:
dependencies:
ms: 2.1.3
@@ -1625,8 +1784,33 @@ snapshots:
deepmerge@4.3.1: {}
delayed-stream@1.0.0: {}
devalue@5.6.1: {}
dunder-proto@1.0.1:
dependencies:
call-bind-apply-helpers: 1.0.2
es-errors: 1.3.0
gopd: 1.2.0
email-validator@2.0.4: {}
es-define-property@1.0.1: {}
es-errors@1.3.0: {}
es-object-atoms@1.1.1:
dependencies:
es-errors: 1.3.0
es-set-tostringtag@2.1.0:
dependencies:
es-errors: 1.3.0
get-intrinsic: 1.3.0
has-tostringtag: 1.0.2
hasown: 2.0.2
esbuild@0.27.1:
optionalDependencies:
'@esbuild/aix-ppc64': 0.27.1
@@ -1752,6 +1936,17 @@ snapshots:
esutils@2.0.3: {}
facebook-nodejs-business-sdk@24.0.1:
dependencies:
axios: 1.13.2
currency-codes: 1.5.1
email-validator: 2.0.4
iso-3166-1: 2.1.1
js-sha256: 0.9.0
mixwith: 0.1.1
transitivePeerDependencies:
- debug
fast-deep-equal@3.1.3: {}
fast-json-stable-stringify@2.1.0: {}
@@ -1771,6 +1966,8 @@ snapshots:
locate-path: 6.0.0
path-exists: 4.0.0
first-match@0.0.1: {}
flat-cache@4.0.1:
dependencies:
flatted: 3.3.3
@@ -1778,9 +1975,39 @@ snapshots:
flatted@3.3.3: {}
follow-redirects@1.15.11: {}
form-data@4.0.5:
dependencies:
asynckit: 0.4.0
combined-stream: 1.0.8
es-set-tostringtag: 2.1.0
hasown: 2.0.2
mime-types: 2.1.35
fsevents@2.3.3:
optional: true
function-bind@1.1.2: {}
get-intrinsic@1.3.0:
dependencies:
call-bind-apply-helpers: 1.0.2
es-define-property: 1.0.1
es-errors: 1.3.0
es-object-atoms: 1.1.1
function-bind: 1.1.2
get-proto: 1.0.1
gopd: 1.2.0
has-symbols: 1.1.0
hasown: 2.0.2
math-intrinsics: 1.1.0
get-proto@1.0.1:
dependencies:
dunder-proto: 1.0.1
es-object-atoms: 1.1.1
glob-parent@6.0.2:
dependencies:
is-glob: 4.0.3
@@ -1789,8 +2016,22 @@ snapshots:
globals@16.5.0: {}
gopd@1.2.0: {}
has-flag@4.0.0: {}
has-symbols@1.1.0: {}
has-tostringtag@1.0.2:
dependencies:
has-symbols: 1.1.0
hasown@2.0.2:
dependencies:
function-bind: 1.1.2
http-status-codes@2.3.0: {}
ignore@5.3.2: {}
ignore@7.0.5: {}
@@ -1814,6 +2055,10 @@ snapshots:
isexe@2.0.0: {}
iso-3166-1@2.1.1: {}
js-sha256@0.9.0: {}
js-yaml@4.1.1:
dependencies:
argparse: 2.0.1
@@ -1853,6 +2098,14 @@ snapshots:
dependencies:
'@jridgewell/sourcemap-codec': 1.5.5
math-intrinsics@1.1.0: {}
mime-db@1.52.0: {}
mime-types@2.1.35:
dependencies:
mime-db: 1.52.0
minimatch@3.1.2:
dependencies:
brace-expansion: 1.1.12
@@ -1861,6 +2114,8 @@ snapshots:
dependencies:
brace-expansion: 2.0.2
mixwith@0.1.1: {}
mri@1.2.0: {}
mrmime@2.0.1: {}
@@ -1871,6 +2126,8 @@ snapshots:
natural-compare@1.4.0: {}
nub@0.0.0: {}
optionator@0.9.4:
dependencies:
deep-is: 0.1.4
@@ -1937,6 +2194,8 @@ snapshots:
prettier@3.7.4: {}
proxy-from-env@1.1.0: {}
publint@0.3.16:
dependencies:
'@publint/pack': 0.1.2

View File

@@ -0,0 +1,91 @@
import type { TrackingManager } from '$lib/tracking.svelte';
import type {
ConversionErrorResponseBody,
ConversionRequestBody,
ConversionResponseBody,
ConversionUserData
} from '$lib/types/conversion.js';
import type { StandardEventName } from '$lib/types/fbq.js';
/**
* Client for sending conversion events to a server endpoint.
*/
export class ConversionClient {
private _href: string;
private _trackingManager: TrackingManager;
/**
* Creates a new ConversionClient.
*
* @param serverHref - The server endpoint URL.
* @param trackingManager - The tracking manager instance.
*/
constructor(serverHref: string, trackingManager: TrackingManager) {
this._href = serverHref;
this._trackingManager = trackingManager;
}
/**
* Sends a conversion event to the server.
*
* @param eventName - The name of the standard event to send.
* @param options - Additional options for the event.
* @returns A promise that resolves to the event response or error.
*/
async trackEvent(
eventName: StandardEventName,
options: {
eventID: string;
user?: Omit<ConversionUserData, 'ip' | 'fbp' | 'fbc' | 'ua'>;
customData?: Record<string, string>;
}
): Promise<ConversionResponseBody | ConversionErrorResponseBody> {
// Extract user data
const fbp = await cookieStore.get('_fbp');
const fbc = await cookieStore.get('_fbc');
const user: ConversionUserData = {
...options.user,
fbp: fbp?.value,
fbc: fbc?.value
};
// Get event source URL & extract UTM params if present
const eventSourceURL = window.location.href;
const url = new URL(eventSourceURL);
const utms: Record<string, string> = {};
url.searchParams.forEach((value, key) => {
if (key.startsWith('utm_')) {
utms[key] = value;
}
});
// Build request body
const requestBody: ConversionRequestBody = {
consent: this._trackingManager.consent === true,
eventName,
eventID: options.eventID,
user,
eventSourceURL,
utms,
customData: options.customData
};
// Send request to server
const response = await fetch(this._href, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(requestBody)
});
if (response.ok) {
const data = (await response.json()) as ConversionResponseBody;
return data;
} else {
const errorData = (await response.json()) as ConversionErrorResponseBody;
return errorData;
}
}
}

View File

@@ -0,0 +1,117 @@
import {
CustomData,
EventRequest,
EventResponse,
ServerEvent,
UserData
} from 'facebook-nodejs-business-sdk';
import type { StandardEventName } from '../types/fbq.js';
import type { ConversionUserData } from '$lib/types/conversion.js';
/**
* Builds UserData for conversion events.
*
* @param data - The user data to include.
* @returns The constructed UserData object.
*/
export const buildConversionUserData = (data: ConversionUserData): UserData => {
const userData = new UserData();
if (data.email) userData.setEmail(data.email);
if (data.phone) userData.setPhone(data.phone);
if (data.firstName) userData.setFirstName(data.firstName);
if (data.lastName) userData.setLastName(data.lastName);
if (data.ip) userData.setClientIpAddress(data.ip);
if (data.fbp) userData.setFbp(data.fbp);
if (data.fbc) userData.setFbc(data.fbc);
if (data.ua) userData.setClientUserAgent(data.ua);
if (data.externalId) userData.setExternalId(data.externalId);
return userData;
};
/**
* Builds CustomData for conversion events.
*
* @param params - The custom data parameters.
* @returns The constructed CustomData object.
*/
export const buildCustomData = (params: Record<string, string>): CustomData => {
const c = new CustomData();
// Map known fields safely
if (params.currency) c.setCurrency(params.currency);
if (typeof params.value === 'number') c.setValue(params.value);
if (Array.isArray(params.contents)) c.setContents(params.contents);
if (params.content_type) c.setContentType(params.content_type);
if (Array.isArray(params.content_ids)) c.setContentIds(params.content_ids);
if (typeof params.num_items === 'number') c.setNumItems(params.num_items);
if (params.search_string) c.setSearchString(params.search_string);
// Attach anything else as custom_properties
const extras = { ...params };
delete extras.currency;
delete extras.value;
delete extras.contents;
delete extras.content_type;
delete extras.content_ids;
delete extras.num_items;
delete extras.search_string;
if (Object.keys(extras).length) c.setCustomProperties(extras);
return c;
};
/**
* Parameters for sending a conversion event to Meta Pixel.
*/
export type ConversionEventOptions = {
eventID: string;
eventSourceURL?: string;
actionSource: 'website' | 'app' | 'offline' | 'other';
userData: UserData;
customData?: CustomData;
};
/**
* Control class for sending Conversion API events to Meta Pixel.
*/
export class ConversionControl {
private _accessToken: string;
private _pixelID: string;
private _testEventCode?: string;
/**
* Creates a new ConversionControl instance.
*
* @param accessToken - Your Meta Pixel Conversion API access token.
* @param pixelID - Your Meta Pixel ID.
* @param testEventCode - Optional test event code for testing events.
*/
constructor(accessToken: string, pixelID: string, testEventCode?: string) {
this._accessToken = accessToken;
this._pixelID = pixelID;
this._testEventCode = testEventCode;
}
/**
* Sends a conversion event to Meta Pixel.
*
* @param eventName - The name of the standard event to send.
* @param options - Additional options for the event.
* @returns A promise that resolves to the event response.
*/
async trackEvent(
eventName: StandardEventName,
options: ConversionEventOptions
): Promise<EventResponse> {
const event = new ServerEvent()
.setEventName(eventName)
.setEventTime(Math.floor(Date.now() / 1000))
.setEventId(options.eventID)
.setUserData(options.userData)
.setActionSource(options.actionSource);
if (options?.eventSourceURL) event.setEventSourceUrl(options.eventSourceURL);
if (options?.customData) event.setCustomData(options.customData);
const req = new EventRequest(this._accessToken, this._pixelID).setEvents([event]);
if (this._testEventCode) req.setTestEventCode(this._testEventCode);
return req.execute();
}
}

View File

@@ -0,0 +1,3 @@
export * from './client.ts';
export * from './server.ts';
export * from './control.ts';

View File

@@ -0,0 +1,67 @@
import { json, type RequestHandler } from '@sveltejs/kit';
import { buildConversionUserData, buildCustomData, ConversionControl } from './control.ts';
import type {
ConversionErrorResponseBody,
ConversionRequestBody,
ConversionResponseBody
} from '$lib/types/conversion.js';
import { StatusCodes } from 'http-status-codes';
export const createConversionRequestHandler: (control: ConversionControl) => RequestHandler = (
control
) => {
const handle: RequestHandler = async ({ request, getClientAddress }) => {
try {
const body = (await request.json()) as ConversionRequestBody;
// Build user data with IP and user agent
const ip = getClientAddress();
const ua = request.headers.get('user-agent');
const userData = buildConversionUserData({
...body.user,
ip,
ua: ua ?? body.user?.ua ?? undefined
});
// Build custom data with UTM params if applicable
let rawCustomData = body.customData ?? {};
if (body.eventName === 'PageView' && body.utms) {
// For PageView events, automatically include UTM params if provided
rawCustomData = {
...rawCustomData,
...body.utms
};
}
const customData = buildCustomData(rawCustomData);
// Send the event via the control
const response = await control.trackEvent(body.eventName, {
eventID: body.eventID,
eventSourceURL: body.eventSourceURL,
actionSource: 'website',
userData,
customData
});
// Structure the response
const structuredResponse: ConversionResponseBody = {
pixelID: response.id,
fbtrace_id: response.fbtrace_id,
receivedEvents: response.events_received,
processedEvents: response.num_processed_entries,
messages: response.messages
};
return json(structuredResponse, { status: StatusCodes.OK });
} catch (e) {
return json(
{ error: e instanceof Error ? e.message : String(e) } as ConversionErrorResponseBody,
{ status: StatusCodes.INTERNAL_SERVER_ERROR }
);
}
};
return handle;
};

53
src/lib/types/conversion.d.ts vendored Normal file
View File

@@ -0,0 +1,53 @@
import type { StandardEventName } from '../types/fbq.js';
/**
* Supported user data fields for conversion events.
*/
export type ConversionUserData = {
email?: string;
phone?: string;
firstName?: string;
lastName?: string;
ip?: string;
fbp?: string;
fbc?: string;
/** user agent */
ua?: string;
externalId?: string;
};
/**
* Request body for conversion event tracking.
*/
export type ConversionRequestBody = {
consent: boolean;
eventName: StandardEventName;
eventID: string;
eventSourceURL?: string;
utms?: Partial<
Record<'utm_source' | 'utm_medium' | 'utm_campaign' | 'utm_content' | 'utm_term', string>
>;
user?: Omit<ConversionUserData, 'ip'>;
customData?: Record<string, string>;
};
/**
* Response body for conversion event tracking.
*/
export type ConversionResponseBody = {
/** Dataset or Pixel ID to which the event successfully posted. */
pixelID: string;
/** fbtrace_id for debugging purposes. */
fbtrace_id: string;
/** Number of events received that were sent by the request. */
receivedEvents: number;
/** Number of events successfully posted by the request. */
processedEvents: number;
/** Messages returned by the server. */
messages: string[];
};
/** Error response body for conversion event tracking. */
export type ConversionErrorResponseBody = {
error: string;
};