@linkforty/core 1.5.1 → 1.13.6

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.
Files changed (54) hide show
  1. package/README.md +75 -11
  2. package/dist/index.d.ts +4 -1
  3. package/dist/index.d.ts.map +1 -1
  4. package/dist/index.js +5 -1
  5. package/dist/index.js.map +1 -1
  6. package/dist/lib/client-ip.d.ts +10 -0
  7. package/dist/lib/client-ip.d.ts.map +1 -0
  8. package/dist/lib/client-ip.js +18 -0
  9. package/dist/lib/client-ip.js.map +1 -0
  10. package/dist/lib/client-ip.test.d.ts +7 -0
  11. package/dist/lib/client-ip.test.d.ts.map +1 -0
  12. package/dist/lib/client-ip.test.js +76 -0
  13. package/dist/lib/client-ip.test.js.map +1 -0
  14. package/dist/lib/database.d.ts.map +1 -1
  15. package/dist/lib/database.js +30 -0
  16. package/dist/lib/database.js.map +1 -1
  17. package/dist/lib/event-emitter.test.d.ts +2 -0
  18. package/dist/lib/event-emitter.test.d.ts.map +1 -0
  19. package/dist/lib/event-emitter.test.js +162 -0
  20. package/dist/lib/event-emitter.test.js.map +1 -0
  21. package/dist/lib/fingerprint.js +4 -4
  22. package/dist/lib/fingerprint.js.map +1 -1
  23. package/dist/lib/fingerprint.test.d.ts +2 -0
  24. package/dist/lib/fingerprint.test.d.ts.map +1 -0
  25. package/dist/lib/fingerprint.test.js +227 -0
  26. package/dist/lib/fingerprint.test.js.map +1 -0
  27. package/dist/lib/webhook.js +1 -1
  28. package/dist/lib/webhook.test.d.ts +2 -0
  29. package/dist/lib/webhook.test.d.ts.map +1 -0
  30. package/dist/lib/webhook.test.js +289 -0
  31. package/dist/lib/webhook.test.js.map +1 -0
  32. package/dist/routes/debug.js +6 -6
  33. package/dist/routes/debug.js.map +1 -1
  34. package/dist/routes/index.d.ts +1 -0
  35. package/dist/routes/index.d.ts.map +1 -1
  36. package/dist/routes/index.js +1 -0
  37. package/dist/routes/index.js.map +1 -1
  38. package/dist/routes/links.d.ts.map +1 -1
  39. package/dist/routes/links.js +19 -10
  40. package/dist/routes/links.js.map +1 -1
  41. package/dist/routes/redirect.d.ts.map +1 -1
  42. package/dist/routes/redirect.js +3 -2
  43. package/dist/routes/redirect.js.map +1 -1
  44. package/dist/routes/sdk.d.ts.map +1 -1
  45. package/dist/routes/sdk.js +20 -11
  46. package/dist/routes/sdk.js.map +1 -1
  47. package/dist/routes/templates.d.ts +3 -0
  48. package/dist/routes/templates.d.ts.map +1 -0
  49. package/dist/routes/templates.js +261 -0
  50. package/dist/routes/templates.js.map +1 -0
  51. package/dist/types/index.d.ts +32 -1
  52. package/dist/types/index.d.ts.map +1 -1
  53. package/llms.txt +763 -0
  54. package/package.json +19 -6
@@ -277,8 +277,8 @@ export async function recordInstallEvent(fingerprintData, deviceId, attributionW
277
277
  const linkResult = await db.query(`SELECT
278
278
  short_code,
279
279
  original_url,
280
- ios_url,
281
- android_url,
280
+ ios_app_store_url,
281
+ android_app_store_url,
282
282
  web_fallback_url,
283
283
  utm_parameters,
284
284
  targeting_rules,
@@ -290,8 +290,8 @@ export async function recordInstallEvent(fingerprintData, deviceId, attributionW
290
290
  deepLinkData = {
291
291
  shortCode: link.short_code,
292
292
  originalUrl: link.original_url,
293
- iosUrl: link.ios_url,
294
- androidUrl: link.android_url,
293
+ iosUrl: link.ios_app_store_url,
294
+ androidUrl: link.android_app_store_url,
295
295
  webFallbackUrl: link.web_fallback_url,
296
296
  utmParameters: link.utm_parameters,
297
297
  targetingRules: link.targeting_rules,
@@ -1 +1 @@
1
- {"version":3,"file":"fingerprint.js","sourceRoot":"","sources":["../../src/lib/fingerprint.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,QAAQ,CAAC;AAC5B,OAAO,EAAE,EAAE,EAAE,MAAM,eAAe,CAAC;AA2BnC;;;GAGG;AACH,MAAM,mBAAmB,GAAG;IAC1B,UAAU,EAAE,EAAE;IACd,UAAU,EAAE,EAAE;IACd,QAAQ,EAAE,EAAE;IACZ,QAAQ,EAAE,EAAE;IACZ,iBAAiB,EAAE,EAAE;CACtB,CAAC;AAEF;;GAEG;AACH,MAAM,CAAC,MAAM,gCAAgC,GAAG,GAAG,CAAC;AAEpD;;GAEG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG,EAAE,CAAC;AAEvC;;;GAGG;AACH,MAAM,UAAU,uBAAuB,CAAC,IAAqB;IAC3D,MAAM,UAAU,GAAG;QACjB,IAAI,CAAC,SAAS,IAAI,EAAE;QACpB,IAAI,CAAC,SAAS,IAAI,EAAE;QACpB,IAAI,CAAC,QAAQ,IAAI,EAAE;QACnB,IAAI,CAAC,QAAQ,IAAI,EAAE;QACnB,IAAI,CAAC,WAAW,EAAE,QAAQ,EAAE,IAAI,EAAE;QAClC,IAAI,CAAC,YAAY,EAAE,QAAQ,EAAE,IAAI,EAAE;QACnC,IAAI,CAAC,QAAQ,IAAI,EAAE;QACnB,IAAI,CAAC,eAAe,IAAI,EAAE;KAC3B,CAAC;IAEF,MAAM,YAAY,GAAG,UAAU,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAC1C,OAAO,MAAM,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AACxE,CAAC;AAED;;;GAGG;AACH,SAAS,WAAW,CAAC,EAAU;IAC7B,IAAI,CAAC,EAAE;QAAE,OAAO,EAAE,CAAC;IAEnB,mDAAmD;IACnD,IAAI,EAAE,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;QACrB,MAAM,KAAK,GAAG,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAC5B,OAAO,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACrC,CAAC;IAED,gEAAgE;IAChE,IAAI,EAAE,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;QACrB,MAAM,KAAK,GAAG,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAC5B,OAAO,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACrC,CAAC;IAED,OAAO,EAAE,CAAC;AACZ,CAAC;AAED;;;GAGG;AACH,SAAS,kBAAkB,CAAC,EAAU;IACpC,IAAI,CAAC,EAAE;QAAE,OAAO,EAAE,CAAC;IAEnB,uDAAuD;IACvD,MAAM,aAAa,GAAG,EAAE,CAAC,KAAK,CAAC,gDAAgD,CAAC,CAAC;IACjF,MAAM,QAAQ,GAAG,aAAa,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IAEvD,kDAAkD;IAClD,MAAM,YAAY,GAAG,EAAE,CAAC,KAAK,CAAC,qCAAqC,CAAC,CAAC;IACrE,MAAM,OAAO,GAAG,YAAY,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IAEpD,OAAO,GAAG,QAAQ,IAAI,OAAO,EAAE,CAAC,WAAW,EAAE,CAAC;AAChD,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,wBAAwB,CACtC,YAA6B,EAC7B,YAA6B;IAE7B,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,MAAM,cAAc,GAAa,EAAE,CAAC;IAEpC,2DAA2D;IAC3D,IAAI,YAAY,CAAC,SAAS,IAAI,YAAY,CAAC,SAAS,EAAE,CAAC;QACrD,MAAM,GAAG,GAAG,WAAW,CAAC,YAAY,CAAC,SAAS,CAAC,CAAC;QAChD,MAAM,GAAG,GAAG,WAAW,CAAC,YAAY,CAAC,SAAS,CAAC,CAAC;QAEhD,IAAI,GAAG,KAAK,GAAG,EAAE,CAAC;YAChB,KAAK,IAAI,mBAAmB,CAAC,UAAU,CAAC;YACxC,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC5B,CAAC;IACH,CAAC;IAED,yDAAyD;IACzD,IAAI,YAAY,CAAC,SAAS,IAAI,YAAY,CAAC,SAAS,EAAE,CAAC;QACrD,MAAM,GAAG,GAAG,kBAAkB,CAAC,YAAY,CAAC,SAAS,CAAC,CAAC;QACvD,MAAM,GAAG,GAAG,kBAAkB,CAAC,YAAY,CAAC,SAAS,CAAC,CAAC;QAEvD,IAAI,GAAG,KAAK,GAAG,EAAE,CAAC;YAChB,KAAK,IAAI,mBAAmB,CAAC,UAAU,CAAC;YACxC,cAAc,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;QACpC,CAAC;IACH,CAAC;IAED,mBAAmB;IACnB,IAAI,YAAY,CAAC,QAAQ,IAAI,YAAY,CAAC,QAAQ,EAAE,CAAC;QACnD,IAAI,YAAY,CAAC,QAAQ,KAAK,YAAY,CAAC,QAAQ,EAAE,CAAC;YACpD,KAAK,IAAI,mBAAmB,CAAC,QAAQ,CAAC;YACtC,cAAc,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QAClC,CAAC;IACH,CAAC;IAED,mBAAmB;IACnB,IAAI,YAAY,CAAC,QAAQ,IAAI,YAAY,CAAC,QAAQ,EAAE,CAAC;QACnD,2DAA2D;QAC3D,MAAM,KAAK,GAAG,YAAY,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC;QAClE,MAAM,KAAK,GAAG,YAAY,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC;QAElE,IAAI,KAAK,KAAK,KAAK,EAAE,CAAC;YACpB,KAAK,IAAI,mBAAmB,CAAC,QAAQ,CAAC;YACtC,cAAc,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QAClC,CAAC;IACH,CAAC;IAED,4BAA4B;IAC5B,IACE,YAAY,CAAC,WAAW;QACxB,YAAY,CAAC,YAAY;QACzB,YAAY,CAAC,WAAW;QACxB,YAAY,CAAC,YAAY,EACzB,CAAC;QACD,IACE,YAAY,CAAC,WAAW,KAAK,YAAY,CAAC,WAAW;YACrD,YAAY,CAAC,YAAY,KAAK,YAAY,CAAC,YAAY,EACvD,CAAC;YACD,KAAK,IAAI,mBAAmB,CAAC,iBAAiB,CAAC;YAC/C,cAAc,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QAChC,CAAC;IACH,CAAC;IAED,OAAO,EAAE,KAAK,EAAE,cAAc,EAAE,CAAC;AACnC,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,mBAAmB,CACvC,kBAAmC,EACnC,yBAAiC,gCAAgC;IAEjE,iFAAiF;IACjF,qEAAqE;IACrE,MAAM,cAAc,GAAG,IAAI,CAAC,CAAC,UAAU;IACvC,MAAM,UAAU,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,cAAc,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC;IAE1E,MAAM,YAAY,GAAG,MAAM,EAAE,CAAC,KAAK,CACjC;;;;;;;;;;;;;;;;;;gBAkBY,EACZ,CAAC,UAAU,CAAC,CACb,CAAC;IAEF,IAAI,YAAY,CAAC,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACnC,OAAO,IAAI,CAAC;IACd,CAAC;IAED,MAAM,WAAW,GAAG,IAAI,IAAI,EAAE,CAAC;IAE/B,sDAAsD;IACtD,IAAI,SAAS,GAA4B,IAAI,CAAC;IAC9C,IAAI,YAAY,GAAG,CAAC,CAAC;IAErB,KAAK,MAAM,GAAG,IAAI,YAAY,CAAC,IAAI,EAAE,CAAC;QACpC,kEAAkE;QAClE,MAAM,eAAe,GAAG,GAAG,CAAC,wBAAwB,IAAI,gCAAgC,CAAC;QACzF,MAAM,SAAS,GAAG,IAAI,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;QAC3C,MAAM,aAAa,GAAG,CAAC,WAAW,CAAC,OAAO,EAAE,GAAG,SAAS,CAAC,OAAO,EAAE,CAAC,GAAG,CAAC,IAAI,GAAG,EAAE,GAAG,EAAE,CAAC,CAAC;QAEvF,IAAI,aAAa,GAAG,eAAe,EAAE,CAAC;YACpC,+DAA+D;YAC/D,SAAS;QACX,CAAC;QAED,MAAM,gBAAgB,GAAoB;YACxC,SAAS,EAAE,GAAG,CAAC,UAAU;YACzB,SAAS,EAAE,GAAG,CAAC,UAAU;YACzB,QAAQ,EAAE,GAAG,CAAC,QAAQ;YACtB,QAAQ,EAAE,GAAG,CAAC,QAAQ;YACtB,WAAW,EAAE,GAAG,CAAC,YAAY;YAC7B,YAAY,EAAE,GAAG,CAAC,aAAa;YAC/B,QAAQ,EAAE,GAAG,CAAC,QAAQ;YACtB,eAAe,EAAE,GAAG,CAAC,gBAAgB;SACtC,CAAC;QAEF,MAAM,EAAE,KAAK,EAAE,cAAc,EAAE,GAAG,wBAAwB,CACxD,kBAAkB,EAClB,gBAAgB,CACjB,CAAC;QAEF,uBAAuB;QACvB,IAAI,KAAK,GAAG,YAAY,IAAI,KAAK,IAAI,oBAAoB,EAAE,CAAC;YAC1D,YAAY,GAAG,KAAK,CAAC;YACrB,SAAS,GAAG;gBACV,OAAO,EAAE,GAAG,CAAC,QAAQ;gBACrB,MAAM,EAAE,GAAG,CAAC,OAAO;gBACnB,eAAe,EAAE,KAAK;gBACtB,cAAc;gBACd,SAAS,EAAE,IAAI,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC;aACpC,CAAC;QACJ,CAAC;IACH,CAAC;IAED,OAAO,SAAS,CAAC;AACnB,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,wBAAwB,CAC5C,OAAe,EACf,eAAgC;IAEhC,MAAM,eAAe,GAAG,uBAAuB,CAAC,eAAe,CAAC,CAAC;IAEjE,MAAM,EAAE,CAAC,KAAK,CACZ;;;;;;;;;;;uDAWmD,EACnD;QACE,OAAO;QACP,eAAe;QACf,eAAe,CAAC,SAAS;QACzB,eAAe,CAAC,SAAS;QACzB,eAAe,CAAC,QAAQ,IAAI,IAAI;QAChC,eAAe,CAAC,QAAQ,IAAI,IAAI;QAChC,eAAe,CAAC,WAAW,IAAI,IAAI;QACnC,eAAe,CAAC,YAAY,IAAI,IAAI;QACpC,eAAe,CAAC,QAAQ,IAAI,IAAI;QAChC,eAAe,CAAC,eAAe,IAAI,IAAI;KACxC,CACF,CAAC;AACJ,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB,CACtC,eAAgC,EAChC,QAAiB,EACjB,yBAAiC,gCAAgC;IAMjE,MAAM,eAAe,GAAG,uBAAuB,CAAC,eAAe,CAAC,CAAC;IAEjE,sCAAsC;IACtC,MAAM,KAAK,GAAG,MAAM,mBAAmB,CAAC,eAAe,EAAE,sBAAsB,CAAC,CAAC;IAEjF,uBAAuB;IACvB,MAAM,aAAa,GAAG,MAAM,EAAE,CAAC,KAAK,CAClC;;;;;;;;;;;;;;;;;;;iCAmB6B,EAC7B;QACE,KAAK,EAAE,MAAM,IAAI,IAAI;QACrB,KAAK,EAAE,OAAO,IAAI,IAAI;QACtB,eAAe;QACf,KAAK,EAAE,eAAe,IAAI,IAAI;QAC9B,sBAAsB;QACtB,eAAe,CAAC,SAAS;QACzB,eAAe,CAAC,SAAS;QACzB,eAAe,CAAC,QAAQ,IAAI,IAAI;QAChC,eAAe,CAAC,QAAQ,IAAI,IAAI;QAChC,eAAe,CAAC,WAAW,IAAI,IAAI;QACnC,eAAe,CAAC,YAAY,IAAI,IAAI;QACpC,eAAe,CAAC,QAAQ,IAAI,IAAI;QAChC,eAAe,CAAC,eAAe,IAAI,IAAI;QACvC,QAAQ,IAAI,IAAI;QAChB,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC,EAAE,mCAAmC;KACrF,CACF,CAAC;IAEF,MAAM,SAAS,GAAG,aAAa,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IAC3C,IAAI,YAAY,GAAG,EAAE,CAAC;IAEtB,yEAAyE;IACzE,IAAI,KAAK,EAAE,CAAC;QACV,MAAM,UAAU,GAAG,MAAM,EAAE,CAAC,KAAK,CAC/B;;;;;;;;;;qBAUe,EACf,CAAC,KAAK,CAAC,MAAM,CAAC,CACf,CAAC;QAEF,IAAI,UAAU,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC/B,MAAM,IAAI,GAAG,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YAChC,YAAY,GAAG;gBACb,SAAS,EAAE,IAAI,CAAC,UAAU;gBAC1B,WAAW,EAAE,IAAI,CAAC,YAAY;gBAC9B,MAAM,EAAE,IAAI,CAAC,OAAO;gBACpB,UAAU,EAAE,IAAI,CAAC,WAAW;gBAC5B,cAAc,EAAE,IAAI,CAAC,gBAAgB;gBACrC,aAAa,EAAE,IAAI,CAAC,cAAc;gBAClC,cAAc,EAAE,IAAI,CAAC,eAAe;gBACpC,kBAAkB,EAAE,IAAI,CAAC,oBAAoB;gBAC7C,SAAS,EAAE,KAAK,CAAC,SAAS;gBAC1B,eAAe,EAAE,KAAK,CAAC,eAAe;gBACtC,cAAc,EAAE,KAAK,CAAC,cAAc;aACrC,CAAC;YAEF,+CAA+C;YAC/C,MAAM,EAAE,CAAC,KAAK,CACZ;;;uBAGe,EACf,CAAC,IAAI,CAAC,SAAS,CAAC,YAAY,CAAC,EAAE,SAAS,CAAC,CAC1C,CAAC;YAEF,0DAA0D;YAC1D,IAAI,CAAC;gBACH,4CAA4C;gBAC5C,MAAM,cAAc,GAAG,MAAM,EAAE,CAAC,KAAK,CACnC,yCAAyC,EACzC,CAAC,KAAK,CAAC,MAAM,CAAC,CACf,CAAC;gBAEF,IAAI,cAAc,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;oBACnC,MAAM,MAAM,GAAG,cAAc,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC;oBAE9C,MAAM,cAAc,GAAG,MAAM,EAAE,CAAC,KAAK,CACnC,gEAAgE,EAChE,CAAC,MAAM,CAAC,CACT,CAAC;oBAEF,IAAI,cAAc,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;wBACnC,MAAM,EAAE,eAAe,EAAE,GAAG,MAAM,MAAM,CAAC,cAAc,CAAC,CAAC;wBAEzD,MAAM,gBAAgB,GAAG;4BACvB,EAAE,EAAE,SAAS;4BACb,MAAM,EAAE,KAAK,CAAC,MAAM;4BACpB,eAAe;4BACf,eAAe,EAAE,KAAK,CAAC,eAAe;4BACtC,WAAW,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;4BACrC,YAAY;4BACZ,SAAS,EAAE,eAAe,CAAC,SAAS;4BACpC,SAAS,EAAE,eAAe,CAAC,SAAS;4BACpC,QAAQ,EAAE,eAAe,CAAC,QAAQ;yBACnC,CAAC;wBAEF,4DAA4D;wBAC5D,8DAA8D;wBAC9D,MAAM,eAAe,CACnB,cAAc,CAAC,IAAI,EACnB,eAAe,EACf,SAAS,EACT,gBAAgB,CACjB,CAAC;oBACJ,CAAC;gBACH,CAAC;YACH,CAAC;YAAC,OAAO,YAAY,EAAE,CAAC;gBACtB,OAAO,CAAC,KAAK,CAAC,sCAAsC,YAAY,EAAE,CAAC,CAAC;YACtE,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO;QACL,SAAS;QACT,KAAK;QACL,YAAY;KACb,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"fingerprint.js","sourceRoot":"","sources":["../../src/lib/fingerprint.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=fingerprint.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fingerprint.test.d.ts","sourceRoot":"","sources":["../../src/lib/fingerprint.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,227 @@
1
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
2
+ // Mock the database module so tests don't require a real Postgres connection.
3
+ vi.mock('./database', () => ({
4
+ db: {
5
+ query: vi.fn(),
6
+ },
7
+ }));
8
+ import * as fingerprint from './fingerprint';
9
+ import { db } from './database';
10
+ const mockDbQuery = db.query;
11
+ const baseFingerprint = {
12
+ ipAddress: '192.168.1.100',
13
+ userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.69 Safari/537.36',
14
+ timezone: 'America/Los_Angeles',
15
+ language: 'en-US',
16
+ screenWidth: 1080,
17
+ screenHeight: 1920,
18
+ platform: 'Windows',
19
+ platformVersion: '10',
20
+ };
21
+ describe('generateFingerprintHash', () => {
22
+ it('produces a deterministic 64-character SHA-256 hash', () => {
23
+ const hash1 = fingerprint.generateFingerprintHash(baseFingerprint);
24
+ const hash2 = fingerprint.generateFingerprintHash(baseFingerprint);
25
+ expect(hash1).toHaveLength(64);
26
+ expect(hash1).toBe(hash2);
27
+ });
28
+ it('produces different hashes for different data', () => {
29
+ const other = { ...baseFingerprint, ipAddress: '10.0.0.1' };
30
+ const hash1 = fingerprint.generateFingerprintHash(baseFingerprint);
31
+ const hash2 = fingerprint.generateFingerprintHash(other);
32
+ expect(hash1).not.toBe(hash2);
33
+ });
34
+ });
35
+ describe('calculateConfidenceScore', () => {
36
+ it('returns 0 score when nothing matches', () => {
37
+ const a = { ...baseFingerprint };
38
+ const b = {
39
+ ...baseFingerprint,
40
+ ipAddress: '10.0.0.1',
41
+ userAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) Safari/537.36',
42
+ timezone: 'Asia/Tokyo',
43
+ language: 'ja-JP',
44
+ screenWidth: 800,
45
+ screenHeight: 600,
46
+ platform: 'Macintosh',
47
+ platformVersion: '11.0',
48
+ };
49
+ const { score, matchedFactors } = fingerprint.calculateConfidenceScore(a, b);
50
+ expect(score).toBe(0);
51
+ expect(matchedFactors).toEqual([]);
52
+ });
53
+ it('matches IP within the same /24 subnet and normalizes user agent', () => {
54
+ const click = {
55
+ ...baseFingerprint,
56
+ ipAddress: '192.168.1.250',
57
+ timezone: 'Africa/Cairo',
58
+ language: 'fr-FR',
59
+ screenWidth: 800,
60
+ screenHeight: 600,
61
+ platform: 'Linux',
62
+ };
63
+ const install = {
64
+ ...baseFingerprint,
65
+ ipAddress: '192.168.1.123',
66
+ userAgent: baseFingerprint.userAgent.replace('Chrome/95.0.4638.69', 'Chrome/116.0.0.0'),
67
+ timezone: 'Europe/London',
68
+ language: 'de-DE',
69
+ screenWidth: 1200,
70
+ screenHeight: 900,
71
+ platform: 'Windows',
72
+ };
73
+ const { score, matchedFactors } = fingerprint.calculateConfidenceScore(click, install);
74
+ expect(score).toBe(70);
75
+ expect(matchedFactors).toContain('ip');
76
+ expect(matchedFactors).toContain('user_agent');
77
+ });
78
+ it('matches language by first two characters', () => {
79
+ const a = {
80
+ ...baseFingerprint,
81
+ ipAddress: '10.0.0.1',
82
+ userAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) Safari/537.36',
83
+ timezone: 'Asia/Tokyo',
84
+ screenWidth: 800,
85
+ screenHeight: 600,
86
+ platform: 'Macintosh',
87
+ platformVersion: '11.0',
88
+ language: 'en-US',
89
+ };
90
+ const b = {
91
+ ...a,
92
+ ipAddress: '172.16.0.1',
93
+ userAgent: 'Mozilla/5.0 (Linux; Android 10; SM-G973F) Chrome/91.0.4472.120 Mobile Safari/537.36',
94
+ timezone: 'UTC',
95
+ screenWidth: 1024,
96
+ screenHeight: 768,
97
+ platform: 'Linux',
98
+ platformVersion: '10',
99
+ language: 'en-GB',
100
+ };
101
+ const { score, matchedFactors } = fingerprint.calculateConfidenceScore(a, b);
102
+ expect(score).toBe(10);
103
+ expect(matchedFactors).toEqual(['language']);
104
+ });
105
+ it('matches timezone and resolution', () => {
106
+ const a = {
107
+ ...baseFingerprint,
108
+ ipAddress: '10.0.0.1',
109
+ userAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) Safari/537.36',
110
+ language: 'ja-JP',
111
+ platform: 'Macintosh',
112
+ platformVersion: '11.0',
113
+ timezone: 'UTC',
114
+ screenWidth: 100,
115
+ screenHeight: 200,
116
+ };
117
+ const b = {
118
+ ...a,
119
+ ipAddress: '172.16.0.1',
120
+ userAgent: 'Mozilla/5.0 (Linux; Android 10; SM-G973F) Chrome/91.0.4472.120 Mobile Safari/537.36',
121
+ language: 'fr-FR',
122
+ };
123
+ const { score, matchedFactors } = fingerprint.calculateConfidenceScore(a, b);
124
+ expect(score).toBe(20);
125
+ expect(matchedFactors.sort()).toEqual(['screen', 'timezone'].sort());
126
+ });
127
+ });
128
+ describe('matchInstallToClick', () => {
129
+ beforeEach(() => {
130
+ vi.useFakeTimers();
131
+ vi.setSystemTime(new Date('2025-01-01T00:00:00Z'));
132
+ mockDbQuery.mockReset();
133
+ });
134
+ afterEach(() => {
135
+ vi.useRealTimers();
136
+ });
137
+ it('returns null when there are no click rows', async () => {
138
+ mockDbQuery.mockResolvedValueOnce({ rows: [] });
139
+ const result = await fingerprint.matchInstallToClick(baseFingerprint);
140
+ expect(result).toBeNull();
141
+ });
142
+ it('returns the best match above the confidence threshold', async () => {
143
+ const clickTime = new Date('2024-12-31T23:00:00Z');
144
+ // First row: only IP match (score 40)
145
+ const rowA = {
146
+ click_id: 'click-a',
147
+ link_id: 'link-a',
148
+ clicked_at: clickTime.toISOString(),
149
+ attribution_window_hours: 24,
150
+ ip_address: '192.168.1.200',
151
+ user_agent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) Safari/537.36',
152
+ timezone: 'Asia/Tokyo',
153
+ language: 'ja-JP',
154
+ screen_width: 720,
155
+ screen_height: 1280,
156
+ platform: 'Macintosh',
157
+ platform_version: '11.0',
158
+ };
159
+ // Second row: IP + user agent + timezone + language + screen (score 100)
160
+ const rowB = {
161
+ click_id: 'click-b',
162
+ link_id: 'link-b',
163
+ clicked_at: clickTime.toISOString(),
164
+ attribution_window_hours: 24,
165
+ ip_address: '192.168.1.250',
166
+ user_agent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36',
167
+ timezone: 'America/Los_Angeles',
168
+ language: 'en-US',
169
+ screen_width: 1080,
170
+ screen_height: 1920,
171
+ platform: 'Windows',
172
+ platform_version: '10',
173
+ };
174
+ mockDbQuery.mockResolvedValueOnce({ rows: [rowA, rowB] });
175
+ const installFingerprint = {
176
+ ...baseFingerprint,
177
+ ipAddress: '192.168.1.123',
178
+ userAgent: baseFingerprint.userAgent.replace('Chrome/95.0.4638.69', 'Chrome/116.0.0.0'),
179
+ };
180
+ const result = await fingerprint.matchInstallToClick(installFingerprint);
181
+ expect(result).not.toBeNull();
182
+ expect(result?.clickId).toBe('click-b');
183
+ expect(result?.confidenceScore).toBe(100);
184
+ expect(result?.matchedFactors).toEqual(expect.arrayContaining(['ip', 'user_agent', 'timezone', 'language', 'screen']));
185
+ });
186
+ it('skips clicks that are outside the attribution window', async () => {
187
+ const oldClickTime = new Date('2024-01-01T00:00:00Z');
188
+ mockDbQuery.mockResolvedValueOnce({
189
+ rows: [
190
+ {
191
+ click_id: 'click-old',
192
+ link_id: 'link-old',
193
+ clicked_at: oldClickTime.toISOString(),
194
+ attribution_window_hours: 1,
195
+ ip_address: baseFingerprint.ipAddress,
196
+ user_agent: baseFingerprint.userAgent,
197
+ timezone: baseFingerprint.timezone,
198
+ language: baseFingerprint.language,
199
+ screen_width: baseFingerprint.screenWidth,
200
+ screen_height: baseFingerprint.screenHeight,
201
+ platform: baseFingerprint.platform,
202
+ platform_version: baseFingerprint.platformVersion,
203
+ },
204
+ ],
205
+ });
206
+ const result = await fingerprint.matchInstallToClick(baseFingerprint);
207
+ expect(result).toBeNull();
208
+ });
209
+ });
210
+ describe('recordInstallEvent', () => {
211
+ beforeEach(() => {
212
+ mockDbQuery.mockReset();
213
+ });
214
+ it('inserts an install event and returns the install id when no match is found', async () => {
215
+ // matchInstallToClick is invoked internally by recordInstallEvent.
216
+ // The first db query is used to find click events. Return an empty list to force a null match.
217
+ mockDbQuery.mockResolvedValueOnce({ rows: [] });
218
+ mockDbQuery.mockResolvedValueOnce({ rows: [{ id: 'install-123', deep_link_data: {} }] });
219
+ const result = await fingerprint.recordInstallEvent(baseFingerprint, 'device-1');
220
+ expect(result.installId).toBe('install-123');
221
+ expect(result.match).toBeNull();
222
+ expect(result.deepLinkData).toEqual({});
223
+ expect(mockDbQuery).toHaveBeenCalledTimes(2);
224
+ expect(mockDbQuery).toHaveBeenLastCalledWith(expect.any(String), expect.arrayContaining([null, null, expect.any(String), null, expect.any(String), expect.any(String), null, null, null, null, null, null, null, 'device-1', null]));
225
+ });
226
+ });
227
+ //# sourceMappingURL=fingerprint.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fingerprint.test.js","sourceRoot":"","sources":["../../src/lib/fingerprint.test.ts"],"names":[],"mappings":"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"}
@@ -75,7 +75,7 @@ async function attemptWebhookDelivery(webhook, payload, attemptNumber) {
75
75
  * Deliver webhook with retry logic and exponential backoff
76
76
  */
77
77
  export async function deliverWebhook(webhook, payload, logDelivery) {
78
- const maxRetries = webhook.retry_count || 3;
78
+ const maxRetries = webhook.retry_count ?? 3;
79
79
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
80
80
  const result = await attemptWebhookDelivery(webhook, payload, attempt);
81
81
  // Log delivery attempt if logging function provided
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=webhook.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webhook.test.d.ts","sourceRoot":"","sources":["../../src/lib/webhook.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,289 @@
1
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
2
+ import { generateWebhookSignature, generateWebhookSecret, deliverWebhook, triggerWebhooks, } from './webhook.js';
3
+ // Helper to build a Webhook fixture
4
+ function makeWebhook(overrides = {}) {
5
+ return {
6
+ id: 'wh-1',
7
+ user_id: 'user-1',
8
+ name: 'Test Webhook',
9
+ url: 'https://example.com/hook',
10
+ secret: 'test-secret',
11
+ events: ['click_event'],
12
+ is_active: true,
13
+ retry_count: 1,
14
+ timeout_ms: 5000,
15
+ headers: {},
16
+ created_at: '2024-01-01T00:00:00.000Z',
17
+ updated_at: '2024-01-01T00:00:00.000Z',
18
+ ...overrides,
19
+ };
20
+ }
21
+ // Helper to build a WebhookPayload fixture
22
+ function makePayload(overrides = {}) {
23
+ return {
24
+ event: 'click_event',
25
+ event_id: 'evt-1',
26
+ timestamp: '2024-01-01T00:00:00.000Z',
27
+ data: { id: 'data-1' },
28
+ ...overrides,
29
+ };
30
+ }
31
+ // Helper to make a minimal Response-like mock
32
+ function mockResponse(ok, status, statusText, body = '') {
33
+ return {
34
+ ok,
35
+ status,
36
+ statusText,
37
+ text: () => Promise.resolve(body),
38
+ };
39
+ }
40
+ describe('generateWebhookSignature', () => {
41
+ it('returns a hex string', () => {
42
+ const sig = generateWebhookSignature('payload', 'secret');
43
+ expect(typeof sig).toBe('string');
44
+ expect(sig).toMatch(/^[0-9a-f]{64}$/);
45
+ });
46
+ it('is deterministic for the same inputs', () => {
47
+ const sig1 = generateWebhookSignature('hello', 'key');
48
+ const sig2 = generateWebhookSignature('hello', 'key');
49
+ expect(sig1).toBe(sig2);
50
+ });
51
+ it('produces different signatures for different payloads', () => {
52
+ const sig1 = generateWebhookSignature('payload-a', 'secret');
53
+ const sig2 = generateWebhookSignature('payload-b', 'secret');
54
+ expect(sig1).not.toBe(sig2);
55
+ });
56
+ it('produces different signatures for different secrets', () => {
57
+ const sig1 = generateWebhookSignature('payload', 'secret-a');
58
+ const sig2 = generateWebhookSignature('payload', 'secret-b');
59
+ expect(sig1).not.toBe(sig2);
60
+ });
61
+ });
62
+ describe('generateWebhookSecret', () => {
63
+ it('returns a 64-character hex string (32 random bytes)', () => {
64
+ const secret = generateWebhookSecret();
65
+ expect(typeof secret).toBe('string');
66
+ expect(secret).toMatch(/^[0-9a-f]{64}$/);
67
+ });
68
+ it('returns unique values on each call', () => {
69
+ const s1 = generateWebhookSecret();
70
+ const s2 = generateWebhookSecret();
71
+ expect(s1).not.toBe(s2);
72
+ });
73
+ });
74
+ describe('deliverWebhook', () => {
75
+ beforeEach(() => {
76
+ vi.useFakeTimers();
77
+ });
78
+ afterEach(() => {
79
+ vi.restoreAllMocks();
80
+ vi.unstubAllGlobals();
81
+ vi.useRealTimers();
82
+ });
83
+ it('returns a success result on HTTP 200', async () => {
84
+ vi.stubGlobal('fetch', vi.fn().mockResolvedValue(mockResponse(true, 200, 'OK', 'received')));
85
+ const webhook = makeWebhook();
86
+ const payload = makePayload();
87
+ const result = await deliverWebhook(webhook, payload);
88
+ expect(result.success).toBe(true);
89
+ expect(result.webhookId).toBe(webhook.id);
90
+ expect(result.eventType).toBe(payload.event);
91
+ expect(result.eventId).toBe(payload.event_id);
92
+ expect(result.responseStatus).toBe(200);
93
+ expect(result.responseBody).toBe('received');
94
+ expect(result.attemptNumber).toBe(1);
95
+ expect(result.deliveredAt).toBeDefined();
96
+ expect(result.errorMessage).toBeUndefined();
97
+ });
98
+ it('returns a failure result on HTTP 500', async () => {
99
+ vi.stubGlobal('fetch', vi.fn().mockResolvedValue(mockResponse(false, 500, 'Internal Server Error', 'error body')));
100
+ // Use logDelivery to capture per-attempt details (the final return is a
101
+ // generic "Failed after N attempts" object once retries are exhausted).
102
+ const logDelivery = vi.fn().mockResolvedValue(undefined);
103
+ const webhook = makeWebhook({ retry_count: 1 });
104
+ const payload = makePayload();
105
+ const result = await deliverWebhook(webhook, payload, logDelivery);
106
+ // Per-attempt result captured via logDelivery
107
+ const attemptResult = logDelivery.mock.calls[0][0];
108
+ expect(attemptResult.responseStatus).toBe(500);
109
+ expect(attemptResult.errorMessage).toBe('HTTP 500: Internal Server Error');
110
+ expect(attemptResult.responseBody).toBe('error body');
111
+ // Final returned result after retries exhausted
112
+ expect(result.success).toBe(false);
113
+ expect(result.deliveredAt).toBeUndefined();
114
+ });
115
+ it('sets correct request headers', async () => {
116
+ const mockFetch = vi.fn().mockResolvedValue(mockResponse(true, 200, 'OK'));
117
+ vi.stubGlobal('fetch', mockFetch);
118
+ const webhook = makeWebhook({ headers: { 'X-Custom': 'value' } });
119
+ const payload = makePayload();
120
+ await deliverWebhook(webhook, payload);
121
+ const [url, init] = mockFetch.mock.calls[0];
122
+ expect(url).toBe(webhook.url);
123
+ expect(init.method).toBe('POST');
124
+ const headers = init.headers;
125
+ expect(headers['Content-Type']).toBe('application/json');
126
+ expect(headers['X-LinkForty-Event']).toBe(payload.event);
127
+ expect(headers['X-LinkForty-Event-ID']).toBe(payload.event_id);
128
+ expect(headers['X-LinkForty-Signature']).toMatch(/^sha256=[0-9a-f]{64}$/);
129
+ expect(headers['User-Agent']).toBe('LinkForty-Webhook/1.0');
130
+ expect(headers['X-Custom']).toBe('value');
131
+ });
132
+ it('sends the correct JSON body', async () => {
133
+ const mockFetch = vi.fn().mockResolvedValue(mockResponse(true, 200, 'OK'));
134
+ vi.stubGlobal('fetch', mockFetch);
135
+ const webhook = makeWebhook();
136
+ const payload = makePayload();
137
+ await deliverWebhook(webhook, payload);
138
+ const [, init] = mockFetch.mock.calls[0];
139
+ expect(JSON.parse(init.body)).toEqual(payload);
140
+ });
141
+ it('truncates response body to 1000 characters', async () => {
142
+ const longBody = 'x'.repeat(2000);
143
+ vi.stubGlobal('fetch', vi.fn().mockResolvedValue(mockResponse(true, 200, 'OK', longBody)));
144
+ const result = await deliverWebhook(makeWebhook(), makePayload());
145
+ expect(result.responseBody).toHaveLength(1000);
146
+ });
147
+ it('retries on failure and succeeds on second attempt', async () => {
148
+ const mockFetch = vi.fn()
149
+ .mockResolvedValueOnce(mockResponse(false, 503, 'Service Unavailable'))
150
+ .mockResolvedValueOnce(mockResponse(true, 200, 'OK'));
151
+ vi.stubGlobal('fetch', mockFetch);
152
+ const webhook = makeWebhook({ retry_count: 2 });
153
+ const payload = makePayload();
154
+ const promise = deliverWebhook(webhook, payload);
155
+ await vi.runAllTimersAsync();
156
+ const result = await promise;
157
+ expect(mockFetch).toHaveBeenCalledTimes(2);
158
+ expect(result.success).toBe(true);
159
+ expect(result.attemptNumber).toBe(2);
160
+ });
161
+ it('exhausts all retries and returns final failure', async () => {
162
+ vi.stubGlobal('fetch', vi.fn().mockResolvedValue(mockResponse(false, 500, 'Error')));
163
+ const webhook = makeWebhook({ retry_count: 3 });
164
+ const payload = makePayload();
165
+ const promise = deliverWebhook(webhook, payload);
166
+ await vi.runAllTimersAsync();
167
+ const result = await promise;
168
+ expect(result.success).toBe(false);
169
+ expect(result.errorMessage).toBe('Failed after 3 attempts');
170
+ expect(result.attemptNumber).toBe(3);
171
+ });
172
+ it('calls logDelivery for each attempt', async () => {
173
+ const mockFetch = vi.fn()
174
+ .mockResolvedValueOnce(mockResponse(false, 500, 'Error'))
175
+ .mockResolvedValueOnce(mockResponse(true, 200, 'OK'));
176
+ vi.stubGlobal('fetch', mockFetch);
177
+ const logDelivery = vi.fn().mockResolvedValue(undefined);
178
+ const webhook = makeWebhook({ retry_count: 2 });
179
+ const payload = makePayload();
180
+ const promise = deliverWebhook(webhook, payload, logDelivery);
181
+ await vi.runAllTimersAsync();
182
+ await promise;
183
+ expect(logDelivery).toHaveBeenCalledTimes(2);
184
+ expect(logDelivery.mock.calls[0][0].success).toBe(false);
185
+ expect(logDelivery.mock.calls[1][0].success).toBe(true);
186
+ });
187
+ it('continues even if logDelivery throws', async () => {
188
+ vi.stubGlobal('fetch', vi.fn().mockResolvedValue(mockResponse(true, 200, 'OK')));
189
+ const logDelivery = vi.fn().mockRejectedValue(new Error('log failed'));
190
+ const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => { });
191
+ const result = await deliverWebhook(makeWebhook(), makePayload(), logDelivery);
192
+ expect(result.success).toBe(true);
193
+ consoleSpy.mockRestore();
194
+ });
195
+ it('captures timeout error in logDelivery when fetch exceeds timeout_ms', async () => {
196
+ const abortError = new Error('The operation was aborted');
197
+ abortError.name = 'AbortError';
198
+ vi.stubGlobal('fetch', vi.fn().mockRejectedValue(abortError));
199
+ const logDelivery = vi.fn().mockResolvedValue(undefined);
200
+ const webhook = makeWebhook({ timeout_ms: 1000, retry_count: 1 });
201
+ const result = await deliverWebhook(webhook, makePayload(), logDelivery);
202
+ // Per-attempt error is exposed via logDelivery
203
+ const attemptResult = logDelivery.mock.calls[0][0];
204
+ expect(attemptResult.success).toBe(false);
205
+ expect(attemptResult.errorMessage).toBe('Timeout after 1000ms');
206
+ // Final result reflects exhausted retries
207
+ expect(result.success).toBe(false);
208
+ });
209
+ it('captures network error message in logDelivery', async () => {
210
+ vi.stubGlobal('fetch', vi.fn().mockRejectedValue(new Error('ECONNREFUSED')));
211
+ const logDelivery = vi.fn().mockResolvedValue(undefined);
212
+ const webhook = makeWebhook({ retry_count: 1 });
213
+ const result = await deliverWebhook(webhook, makePayload(), logDelivery);
214
+ const attemptResult = logDelivery.mock.calls[0][0];
215
+ expect(attemptResult.success).toBe(false);
216
+ expect(attemptResult.errorMessage).toBe('ECONNREFUSED');
217
+ expect(result.success).toBe(false);
218
+ });
219
+ });
220
+ describe('triggerWebhooks', () => {
221
+ beforeEach(() => {
222
+ vi.useFakeTimers();
223
+ });
224
+ afterEach(() => {
225
+ vi.restoreAllMocks();
226
+ vi.unstubAllGlobals();
227
+ vi.useRealTimers();
228
+ });
229
+ it('does nothing when no webhooks provided', async () => {
230
+ const mockFetch = vi.fn();
231
+ vi.stubGlobal('fetch', mockFetch);
232
+ await triggerWebhooks([], 'click_event', 'evt-1', {});
233
+ expect(mockFetch).not.toHaveBeenCalled();
234
+ });
235
+ it('skips inactive webhooks', async () => {
236
+ const mockFetch = vi.fn();
237
+ vi.stubGlobal('fetch', mockFetch);
238
+ const inactive = makeWebhook({ is_active: false });
239
+ await triggerWebhooks([inactive], 'click_event', 'evt-1', {});
240
+ expect(mockFetch).not.toHaveBeenCalled();
241
+ });
242
+ it('skips webhooks not subscribed to the event', async () => {
243
+ const mockFetch = vi.fn();
244
+ vi.stubGlobal('fetch', mockFetch);
245
+ const webhook = makeWebhook({ events: ['install_event'] });
246
+ await triggerWebhooks([webhook], 'click_event', 'evt-1', {});
247
+ expect(mockFetch).not.toHaveBeenCalled();
248
+ });
249
+ it('delivers to matching active webhooks', async () => {
250
+ const mockFetch = vi.fn().mockResolvedValue(mockResponse(true, 200, 'OK'));
251
+ vi.stubGlobal('fetch', mockFetch);
252
+ const webhook = makeWebhook({ events: ['click_event', 'install_event'] });
253
+ triggerWebhooks([webhook], 'click_event', 'evt-1', { foo: 'bar' });
254
+ await vi.runAllTimersAsync();
255
+ expect(mockFetch).toHaveBeenCalledOnce();
256
+ const body = JSON.parse(mockFetch.mock.calls[0][1].body);
257
+ expect(body.event).toBe('click_event');
258
+ expect(body.event_id).toBe('evt-1');
259
+ expect(body.data).toEqual({ foo: 'bar' });
260
+ });
261
+ it('delivers to multiple matching webhooks', async () => {
262
+ const mockFetch = vi.fn().mockResolvedValue(mockResponse(true, 200, 'OK'));
263
+ vi.stubGlobal('fetch', mockFetch);
264
+ const wh1 = makeWebhook({ id: 'wh-1', url: 'https://a.com/hook' });
265
+ const wh2 = makeWebhook({ id: 'wh-2', url: 'https://b.com/hook' });
266
+ triggerWebhooks([wh1, wh2], 'click_event', 'evt-2', {});
267
+ await vi.runAllTimersAsync();
268
+ expect(mockFetch).toHaveBeenCalledTimes(2);
269
+ });
270
+ it('calls logDelivery with webhookId and result', async () => {
271
+ const mockFetch = vi.fn().mockResolvedValue(mockResponse(true, 200, 'OK'));
272
+ vi.stubGlobal('fetch', mockFetch);
273
+ const logDelivery = vi.fn().mockResolvedValue(undefined);
274
+ const webhook = makeWebhook({ id: 'wh-log' });
275
+ triggerWebhooks([webhook], 'click_event', 'evt-3', {}, logDelivery);
276
+ await vi.runAllTimersAsync();
277
+ expect(logDelivery).toHaveBeenCalledWith('wh-log', expect.objectContaining({ success: true }));
278
+ });
279
+ it('includes the timestamp in the payload', async () => {
280
+ const mockFetch = vi.fn().mockResolvedValue(mockResponse(true, 200, 'OK'));
281
+ vi.stubGlobal('fetch', mockFetch);
282
+ triggerWebhooks([makeWebhook()], 'click_event', 'evt-4', {});
283
+ await vi.runAllTimersAsync();
284
+ const body = JSON.parse(mockFetch.mock.calls[0][1].body);
285
+ expect(body.timestamp).toBeDefined();
286
+ expect(new Date(body.timestamp).toISOString()).toBe(body.timestamp);
287
+ });
288
+ });
289
+ //# sourceMappingURL=webhook.test.js.map