@particle-academy/facebook-lead-ads-js 0.1.3 → 0.2.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/CHANGELOG.md CHANGED
@@ -8,6 +8,40 @@ The four packages share one version, because they are generated from one
8
8
  `provider/` definition and a version that meant something different in each
9
9
  would be a version nobody could reason about.
10
10
 
11
+ ## [0.2.0] — 2026-11-08
12
+
13
+ ### Added
14
+
15
+ - **Laravel integration, in this package** (`src/Laravel/`), auto-discovered
16
+ through composer's `extra.laravel`. Laravel is SUGGESTED, never required:
17
+ outside a Laravel app none of it loads.
18
+ - a service provider that merges `config/facebook-lead-ads.php` under
19
+ `connectors.facebook-lead-ads` (credentials from the environment, or per call for a
20
+ multi-account app);
21
+ - the `FacebookLeadAds` facade: `FacebookLeadAds::call('<operation>', [...])` runs any action;
22
+ - `FacebookLeadAds::fake()`, answered from the connector's faker, with
23
+ `assertSent` / `assertNotSent` / `assertNothingSent` / `assertSentCount`;
24
+ - a webhook ROUTE per trigger (`Route::facebookLeadAdsLeadgen('/path')`) that VERIFIES the raw delivery with the trigger's own scheme before the app sees it, answers 401 with the reason when it does not, then dispatches an event (`Events\\…Received`);
25
+ - its own Pest test of all of it against a booted Laravel app.
26
+
27
+ ### Changed
28
+
29
+ - **Tests: Pest `^5`**, and `laravel/framework` `^13` as a DEV-ONLY test
30
+ dependency (the framework this package integrates with; nothing ships in
31
+ `require`).
32
+
33
+ ## [0.1.4] — 2026-10-31
34
+
35
+ ### Fixed
36
+
37
+ - **An `X-Hub-Signature-256` header without its `sha256=` prefix is now
38
+ REFUSED**, in all three runtimes. The parsers stripped the prefix when it
39
+ was present and used the bare value otherwise, so the right HMAC digest with
40
+ no prefix verified. It was never a forgery path, because the digest still
41
+ had to match, but a header not in Meta's format is not Meta's header. Meta
42
+ always sends the prefix, so no genuine delivery is affected. Now driven in
43
+ TypeScript as well as PHP and Python.
44
+
11
45
  ## [0.1.3] — 2026-09-12
12
46
 
13
47
  ### Changed
package/README.md CHANGED
@@ -1,5 +1,7 @@
1
1
  # Facebook Lead Ads
2
2
 
3
+ [![Fancified](art/fancified.svg)](https://particle.academy)
4
+
3
5
  Facebook Lead Ads for [fancy-flow][flow] — as **four imported, versioned packages**, one
4
6
  per runtime. Not vendored source: a copy cannot be upgraded, and third-party APIs
5
7
  change.
@@ -0,0 +1,26 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="128" height="28" role="img" aria-label="Fancified — built with Fancy UI">
2
+ <title>Fancified · built with Fancy UI</title>
3
+ <defs>
4
+ <linearGradient id="fcdBg" x1="0" y1="0" x2="1" y2="1">
5
+ <stop offset="0" stop-color="#6d28d9"/>
6
+ <stop offset=".5" stop-color="#9333ea"/>
7
+ <stop offset="1" stop-color="#d946ef"/>
8
+ </linearGradient>
9
+ <linearGradient id="fcdGloss" x1="0" y1="0" x2="0" y2="1">
10
+ <stop offset="0" stop-color="#ffffff" stop-opacity=".30"/>
11
+ <stop offset=".55" stop-color="#ffffff" stop-opacity="0"/>
12
+ </linearGradient>
13
+ <clipPath id="fcdClip"><rect width="128" height="28" rx="7"/></clipPath>
14
+ </defs>
15
+ <g clip-path="url(#fcdClip)">
16
+ <rect width="128" height="28" fill="url(#fcdBg)"/>
17
+ <rect width="34" height="28" fill="#3b0764" fill-opacity=".5"/>
18
+ <rect width="128" height="13" fill="url(#fcdGloss)"/>
19
+ <g fill="#fff">
20
+ <path transform="translate(17 14)" d="M0 -7 C.6 -2.2 2.2 -.6 7 0 C2.2 .6 .6 2.2 0 7 C-.6 2.2 -2.2 .6 -7 0 C-2.2 -.6 -.6 -2.2 0 -7 Z"/>
21
+ <path transform="translate(25.5 7.5) scale(.34)" fill-opacity=".85" d="M0 -7 C.6 -2.2 2.2 -.6 7 0 C2.2 .6 .6 2.2 0 7 C-.6 2.2 -2.2 .6 -7 0 C-2.2 -.6 -.6 -2.2 0 -7 Z"/>
22
+ </g>
23
+ <text x="81" y="18.5" fill="#ffffff" text-anchor="middle" font-family="'Segoe UI',Verdana,'DejaVu Sans',sans-serif" font-size="12" font-weight="700" letter-spacing=".3">Fancified</text>
24
+ </g>
25
+ <rect x=".5" y=".5" width="127" height="27" rx="6.5" fill="none" stroke="#ffffff" stroke-opacity=".22"/>
26
+ </svg>
@@ -114,7 +114,7 @@ var FACEBOOK_LEAD_ADS_LEADGEN_SCHEME = {
114
114
  encoding: "hex"
115
115
  };
116
116
  function parseFacebookLeadAdsSignature(raw) {
117
- return { signature: raw.startsWith("sha256=") ? raw.slice(7) : raw };
117
+ return raw.startsWith("sha256=") ? { signature: raw.slice(7) } : {};
118
118
  }
119
119
  var FACEBOOK_LEAD_ADS_LEADGEN = {
120
120
  service: "facebook_lead_ads",
@@ -133,5 +133,5 @@ function verifyFacebookLeadAdsDelivery(delivery, appSecret, now) {
133
133
  }
134
134
 
135
135
  export { CONNECTOR_API_VERSION, FACEBOOK_LEAD_ADS, FACEBOOK_LEAD_ADS_BASE_URLS, FACEBOOK_LEAD_ADS_LEADGEN, FACEBOOK_LEAD_ADS_LEADGEN_SCHEME, FACEBOOK_LEAD_ADS_LEADGEN_SIGNATURE_HEADER, FACEBOOK_LEAD_ADS_REQUIRES, LEAD_GET_OPERATION, facebookLeadAdsAuthorize, facebookLeadAdsFaker, facebookLeadAdsLeadGet, parseFacebookLeadAdsSignature, verifyFacebookLeadAdsDelivery };
136
- //# sourceMappingURL=chunk-JVNM4F6J.js.map
137
- //# sourceMappingURL=chunk-JVNM4F6J.js.map
136
+ //# sourceMappingURL=chunk-XJVRJJTY.js.map
137
+ //# sourceMappingURL=chunk-XJVRJJTY.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/faker.ts","../src/service.ts","../src/actions/lead-get.ts","../src/triggers/leadgen.ts"],"names":[],"mappings":"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","file":"chunk-XJVRJJTY.js","sourcesContent":["/**\n * GENERATED FILE — do not edit.\n *\n * Emitted from provider/fixtures/ by weaver's generator.\n * A hand-edit here is destroyed by the next protocol sync, which is worse than\n * being rejected, because it works until it silently does not. Fix\n * provider/fixtures/ (or weaver's template/) and regenerate:\n *\n * npm run provider -- facebook_lead_ads\n */\n\n/**\n * The Facebook Lead Ads faker.\n *\n * Shapes, not behaviour: the goal is that a downstream node sees the field\n * NAMES Facebook Lead Ads actually publishes, so an author can wire {{\n * $json.data.id }} against a fake and have it keep working against the real\n * thing.\n *\n * Deterministic — same inputs, same output. A faker returning a fresh uuid\n * every call cannot be asserted on, so its fixtures degrade to \"it did not\n * throw\", which is the assertion that catches nothing.\n */\n\nimport type { ConnectorFaker, FakeRequest } from \"@particle-academy/fancy-connector-core\";\n\nfunction fakeLeadGet({ config, fake }: FakeRequest): unknown {\n return {\n \"id\": Array.from({ length: 15 }, () => fake.int(0, 9)).join(\"\"),\n \"created_time\": \"2026-08-24T09:15:00+0000\",\n \"ad_id\": Array.from({ length: 15 }, () => fake.int(0, 9)).join(\"\"),\n \"form_id\": Array.from({ length: 15 }, () => fake.int(0, 9)).join(\"\"),\n \"field_data\": [\n {\n \"name\": \"full_name\",\n \"values\": [\n \"Ada Lovelace\",\n ],\n },\n {\n \"name\": \"email\",\n \"values\": [\n \"ada@example.test\",\n ],\n },\n {\n \"name\": \"phone_number\",\n \"values\": [\n \"+15550100\",\n ],\n },\n {\n \"name\": \"what_are_you_interested_in\",\n \"values\": [\n \"Enterprise plan\",\n ],\n },\n ],\n };\n}\n\nfunction fakeLeadgen({ config, fake }: FakeRequest): unknown {\n const boundLeadid = Array.from({ length: 15 }, () => fake.int(0, 9)).join(\"\");\n\n return {\n \"leadgen_id\": boundLeadid,\n \"page_id\": Array.from({ length: 15 }, () => fake.int(0, 9)).join(\"\"),\n \"form_id\": Array.from({ length: 15 }, () => fake.int(0, 9)).join(\"\"),\n \"ad_id\": Array.from({ length: 15 }, () => fake.int(0, 9)).join(\"\"),\n \"adgroup_id\": Array.from({ length: 15 }, () => fake.int(0, 9)).join(\"\"),\n \"created_time\": 1787577300,\n };\n}\n\nexport const facebookLeadAdsFaker: ConnectorFaker = (operation, request) => {\n switch (operation) {\n case \"lead_get\":\n return fakeLeadGet(request);\n\n case \"leadgen\":\n return fakeLeadgen(request);\n\n default:\n // A faker asked for an operation it has no shape for must SAY so. Making\n // something up would produce a green run whose output silently has none\n // of the fields the author is about to reference.\n throw new Error(\n `facebook_lead_ads: no fake response is defined for \"${operation}\". ` +\n \"Add a fixture under provider/fixtures/ and regenerate — a connector without a faker \" +\n \"cannot be developed against, tested, or demonstrated.\",\n );\n }\n};\n","/**\n * GENERATED FILE — do not edit.\n *\n * Emitted from provider/manifest.json by weaver's generator.\n * A hand-edit here is destroyed by the next protocol sync, which is worse than\n * being rejected, because it works until it silently does not. Fix\n * provider/manifest.json (or weaver's template/) and regenerate:\n *\n * npm run provider -- facebook_lead_ads\n */\n\n/**\n * Facebook Lead Ads, as one service descriptor shared by every Facebook Lead\n * Ads operation.\n *\n * @particle-academy/fancy-connector-core carries what is true of ALL\n * connectors. This carries what is true of Facebook Lead Ads: its base URL,\n * its auth scheme, its idempotency header, and its faker.\n *\n * ## The sandbox trap, written down where it is used\n *\n * Facebook Lead Ads has no sandbox. Meta's Lead Ads Testing Tool creates a\n * test lead on the REAL form, which fires the real webhook and appears\n * alongside real leads until somebody deletes it. Anything this reads is real\n * customer data belonging to a real advertiser.\n */\n\nimport type { ConnectorMode, PreparedRequest, ServiceDescriptor } from \"@particle-academy/fancy-connector-core\";\n\nimport { facebookLeadAdsFaker } from \"./faker.js\";\nimport { createHmac } from \"node:crypto\";\n\n/**\n * The connector API version this package was GENERATED against.\n *\n * A literal, never imported. An imported constant lets an upgrade rewrite the\n * very claim it exists to detect, after which the copy agrees with itself\n * forever.\n */\nexport const CONNECTOR_API_VERSION = 1;\n\nexport const FACEBOOK_LEAD_ADS_BASE_URLS = {\n \"live\": \"https://graph.facebook.com/v25.0\"\n} as const;\n\n/** Credential keys a remote call cannot proceed without. */\nexport const FACEBOOK_LEAD_ADS_REQUIRES = [\n \"pageAccessToken\",\n \"appSecret\",\n \"clientId\",\n \"clientSecret\"\n] as const;\n\n/**\n * Apply Facebook Lead Ads's auth scheme to an outgoing request.\n *\n * Same as facebook-pages: Meta takes the token as a QUERY PARAMETER, and\n * appsecret_proof has to go in the query regardless, so both live in one place\n * and a request stays readable in a log.\n *\n * The mode is passed in because for some providers auth and estate are the\n * same decision expressed in the URL; here it is unused, and saying so is\n * cheaper than wondering later whether it was forgotten.\n */\nexport function facebookLeadAdsAuthorize(\n credentials: Record<string, string | undefined>,\n request: PreparedRequest,\n _mode: ConnectorMode,\n): void {\n const url = new URL(request.url);\n\n url.searchParams.set(\"access_token\", String(credentials.pageAccessToken ?? \"\"));\n\n const proof = createHmac(\"sha256\", String(credentials.appSecret ?? \"\"))\n .update(String(credentials.pageAccessToken ?? \"\"))\n .digest(\"hex\");\n\n url.searchParams.set(\"appsecret_proof\", proof);\n request.url = url.toString();\n}\n\n/** The Facebook Lead Ads service, for the TypeScript runtime. */\nexport const FACEBOOK_LEAD_ADS: ServiceDescriptor = {\n service: \"facebook_lead_ads\",\n title: \"Facebook Lead Ads\",\n sandbox: \"none\",\n baseUrls: { ...FACEBOOK_LEAD_ADS_BASE_URLS },\n requires: [...FACEBOOK_LEAD_ADS_REQUIRES],\n authorize: facebookLeadAdsAuthorize,\n faker: facebookLeadAdsFaker,\n};\n","/**\n * GENERATED FILE — do not edit.\n *\n * Emitted from provider/actions/lead-get.json by weaver's generator.\n * A hand-edit here is destroyed by the next protocol sync, which is worse than\n * being rejected, because it works until it silently does not. Fix\n * provider/actions/lead-get.json (or weaver's template/) and regenerate:\n *\n * npm run provider -- facebook_lead_ads\n */\n\n/**\n * Read one lead's answers by id. Pair this with the `leadgen` trigger, whose\n * delivery carries only the id.\n *\n * GET /{leadId} —\n * https://developers.facebook.com/docs/marketing-api/guides/lead-ads/retrieving\n *\n * Notice what is NOT here: no key, no base URL, no mode check, no retry loop,\n * no fake/real branch. This describes the request; callConnector resolves the\n * connection, picks the estate, and either calls Facebook Lead Ads or calls\n * the faker.\n */\n\nimport {\n callConnector,\n type ConnectorResult,\n type RequestedMode,\n type Transport,\n} from \"@particle-academy/fancy-connector-core\";\nimport { FACEBOOK_LEAD_ADS } from \"../service.js\";\n\nexport const LEAD_GET_OPERATION = \"lead_get\";\n\nexport type LeadGetOptions = {\n /** The node's resolved config. Keys: leadId. */\n config: Record<string, unknown>;\n credentials?: Record<string, string | undefined>;\n mode?: RequestedMode;\n connectionId?: string | null;\n input?: unknown;\n attempts?: number;\n /** Override the transport. The only way to exercise this without a network. */\n transport?: Transport;\n};\n\nexport async function facebookLeadAdsLeadGet(options: LeadGetOptions): Promise<ConnectorResult> {\n const config = options.config ?? {};\n\n if (config.leadId === undefined || config.leadId === null || config.leadId === \"\") {\n throw new Error(`lead_get: \"leadId\" is required (Lead ID).`);\n }\n\n return callConnector(FACEBOOK_LEAD_ADS, {\n operation: LEAD_GET_OPERATION,\n config,\n input: options.input,\n ...(options.credentials === undefined ? {} : { credentials: options.credentials }),\n ...(options.mode === undefined ? {} : { mode: options.mode }),\n ...(options.connectionId === undefined ? {} : { connectionId: options.connectionId }),\n ...(options.attempts === undefined ? {} : { attempts: options.attempts }),\n ...(options.transport === undefined ? {} : { transport: options.transport }),\n request: {\n method: \"GET\",\n path: `/${encodeURIComponent(String(config.leadId))}`,\n query: {},\n },\n });\n}\n","/**\n * GENERATED FILE — do not edit.\n *\n * Emitted from provider/triggers/leadgen.json by weaver's generator.\n * A hand-edit here is destroyed by the next protocol sync, which is worse than\n * being rejected, because it works until it silently does not. Fix\n * provider/triggers/leadgen.json (or weaver's template/) and regenerate:\n *\n * npm run provider -- facebook_lead_ads\n */\n\n/**\n * Facebook Lead Ads's webhook trigger — the delivery contract.\n *\n * Kept beside the service descriptor rather than inside a node, because a\n * signature scheme is a fact about FACEBOOK LEAD ADS. Two Facebook Lead Ads\n * triggers must not be able to disagree about how a delivery is verified.\n */\n\nimport { verifyDelivery, type HmacScheme, type InboundDelivery, type TriggerDescriptor, type WebhookVerification } from \"@particle-academy/fancy-connector-core\";\nimport { facebookLeadAdsFaker } from \"../faker.js\";\n\nexport const FACEBOOK_LEAD_ADS_LEADGEN_SIGNATURE_HEADER = \"X-Hub-Signature-256\";\n\nexport const FACEBOOK_LEAD_ADS_LEADGEN_SCHEME: HmacScheme = {\n algorithm: \"SHA-256\",\n payload: (raw, timestamp) => raw,\n encoding: \"hex\",\n};\n\n/** Strip the `sha256=` prefix the provider sends. Without it, there is no signature. */\nexport function parseFacebookLeadAdsSignature(raw: string): { signature?: string } {\n return raw.startsWith(\"sha256=\") ? { signature: raw.slice(7) } : {};\n}\n\nexport const FACEBOOK_LEAD_ADS_LEADGEN: TriggerDescriptor = {\n service: \"facebook_lead_ads\",\n operation: \"leadgen\",\n delivery: \"webhook\",\n setup:\n \"In the Meta app dashboard, add a Webhooks product, subscribe to the PAGE object and tick the `leadgen` field, and point the callback URL at the route your host mounts for this trigger. Meta first sends a GET carrying `hub.mode=subscribe`, `hub.verify_token` and `hub.challenge` -- the host must echo `hub.challenge` back as the body and check `hub.verify_token` against the value set in the dashboard, or the subscription is never activated. Then subscribe the app to each Page (`POST /{pageId}/subscribed_apps` with `subscribed_fields=leadgen`), which is a SEPARATE step: a webhook configured but not subscribed per Page delivers nothing and reports nothing.\",\n verification: {\n signatureHeader: FACEBOOK_LEAD_ADS_LEADGEN_SIGNATURE_HEADER,\n scheme: FACEBOOK_LEAD_ADS_LEADGEN_SCHEME,\n parse: parseFacebookLeadAdsSignature,\n },\n faker: facebookLeadAdsFaker,\n};\n\n/**\n * Verify one inbound Facebook Lead Ads delivery.\n *\n * The host calls this BEFORE starting a run, with the body exactly as\n * received. Re-serialised JSON changes key order and whitespace, and produces\n * a mismatch that looks precisely like a wrong secret — hours of debugging the\n * wrong thing.\n *\n * The secret is the connection's `appSecret`.\n */\nexport function verifyFacebookLeadAdsDelivery(\n delivery: InboundDelivery,\n appSecret: string | undefined,\n now?: number,\n): Promise<WebhookVerification> {\n return verifyDelivery(FACEBOOK_LEAD_ADS_LEADGEN, delivery, appSecret, now);\n}\n"]}
package/dist/flow.cjs CHANGED
@@ -118,7 +118,7 @@ var FACEBOOK_LEAD_ADS_LEADGEN_SCHEME = {
118
118
  encoding: "hex"
119
119
  };
120
120
  function parseFacebookLeadAdsSignature(raw) {
121
- return { signature: raw.startsWith("sha256=") ? raw.slice(7) : raw };
121
+ return raw.startsWith("sha256=") ? { signature: raw.slice(7) } : {};
122
122
  }
123
123
  var FACEBOOK_LEAD_ADS_LEADGEN = {
124
124
  service: "facebook_lead_ads",
package/dist/flow.cjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/faker.ts","../src/service.ts","../src/actions/lead-get.ts","../src/triggers/leadgen.ts","../src/flow.ts"],"names":["createHmac","callConnector","resolveConnection","triggerEvent","facebookLeadAdsLeadKind","facebookLeadAdsWebhookTriggerKind"],"mappings":"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","file":"flow.cjs","sourcesContent":["/**\n * GENERATED FILE — do not edit.\n *\n * Emitted from provider/fixtures/ by weaver's generator.\n * A hand-edit here is destroyed by the next protocol sync, which is worse than\n * being rejected, because it works until it silently does not. Fix\n * provider/fixtures/ (or weaver's template/) and regenerate:\n *\n * npm run provider -- facebook_lead_ads\n */\n\n/**\n * The Facebook Lead Ads faker.\n *\n * Shapes, not behaviour: the goal is that a downstream node sees the field\n * NAMES Facebook Lead Ads actually publishes, so an author can wire {{\n * $json.data.id }} against a fake and have it keep working against the real\n * thing.\n *\n * Deterministic — same inputs, same output. A faker returning a fresh uuid\n * every call cannot be asserted on, so its fixtures degrade to \"it did not\n * throw\", which is the assertion that catches nothing.\n */\n\nimport type { ConnectorFaker, FakeRequest } from \"@particle-academy/fancy-connector-core\";\n\nfunction fakeLeadGet({ config, fake }: FakeRequest): unknown {\n return {\n \"id\": Array.from({ length: 15 }, () => fake.int(0, 9)).join(\"\"),\n \"created_time\": \"2026-08-24T09:15:00+0000\",\n \"ad_id\": Array.from({ length: 15 }, () => fake.int(0, 9)).join(\"\"),\n \"form_id\": Array.from({ length: 15 }, () => fake.int(0, 9)).join(\"\"),\n \"field_data\": [\n {\n \"name\": \"full_name\",\n \"values\": [\n \"Ada Lovelace\",\n ],\n },\n {\n \"name\": \"email\",\n \"values\": [\n \"ada@example.test\",\n ],\n },\n {\n \"name\": \"phone_number\",\n \"values\": [\n \"+15550100\",\n ],\n },\n {\n \"name\": \"what_are_you_interested_in\",\n \"values\": [\n \"Enterprise plan\",\n ],\n },\n ],\n };\n}\n\nfunction fakeLeadgen({ config, fake }: FakeRequest): unknown {\n const boundLeadid = Array.from({ length: 15 }, () => fake.int(0, 9)).join(\"\");\n\n return {\n \"leadgen_id\": boundLeadid,\n \"page_id\": Array.from({ length: 15 }, () => fake.int(0, 9)).join(\"\"),\n \"form_id\": Array.from({ length: 15 }, () => fake.int(0, 9)).join(\"\"),\n \"ad_id\": Array.from({ length: 15 }, () => fake.int(0, 9)).join(\"\"),\n \"adgroup_id\": Array.from({ length: 15 }, () => fake.int(0, 9)).join(\"\"),\n \"created_time\": 1787577300,\n };\n}\n\nexport const facebookLeadAdsFaker: ConnectorFaker = (operation, request) => {\n switch (operation) {\n case \"lead_get\":\n return fakeLeadGet(request);\n\n case \"leadgen\":\n return fakeLeadgen(request);\n\n default:\n // A faker asked for an operation it has no shape for must SAY so. Making\n // something up would produce a green run whose output silently has none\n // of the fields the author is about to reference.\n throw new Error(\n `facebook_lead_ads: no fake response is defined for \"${operation}\". ` +\n \"Add a fixture under provider/fixtures/ and regenerate — a connector without a faker \" +\n \"cannot be developed against, tested, or demonstrated.\",\n );\n }\n};\n","/**\n * GENERATED FILE — do not edit.\n *\n * Emitted from provider/manifest.json by weaver's generator.\n * A hand-edit here is destroyed by the next protocol sync, which is worse than\n * being rejected, because it works until it silently does not. Fix\n * provider/manifest.json (or weaver's template/) and regenerate:\n *\n * npm run provider -- facebook_lead_ads\n */\n\n/**\n * Facebook Lead Ads, as one service descriptor shared by every Facebook Lead\n * Ads operation.\n *\n * @particle-academy/fancy-connector-core carries what is true of ALL\n * connectors. This carries what is true of Facebook Lead Ads: its base URL,\n * its auth scheme, its idempotency header, and its faker.\n *\n * ## The sandbox trap, written down where it is used\n *\n * Facebook Lead Ads has no sandbox. Meta's Lead Ads Testing Tool creates a\n * test lead on the REAL form, which fires the real webhook and appears\n * alongside real leads until somebody deletes it. Anything this reads is real\n * customer data belonging to a real advertiser.\n */\n\nimport type { ConnectorMode, PreparedRequest, ServiceDescriptor } from \"@particle-academy/fancy-connector-core\";\n\nimport { facebookLeadAdsFaker } from \"./faker.js\";\nimport { createHmac } from \"node:crypto\";\n\n/**\n * The connector API version this package was GENERATED against.\n *\n * A literal, never imported. An imported constant lets an upgrade rewrite the\n * very claim it exists to detect, after which the copy agrees with itself\n * forever.\n */\nexport const CONNECTOR_API_VERSION = 1;\n\nexport const FACEBOOK_LEAD_ADS_BASE_URLS = {\n \"live\": \"https://graph.facebook.com/v25.0\"\n} as const;\n\n/** Credential keys a remote call cannot proceed without. */\nexport const FACEBOOK_LEAD_ADS_REQUIRES = [\n \"pageAccessToken\",\n \"appSecret\",\n \"clientId\",\n \"clientSecret\"\n] as const;\n\n/**\n * Apply Facebook Lead Ads's auth scheme to an outgoing request.\n *\n * Same as facebook-pages: Meta takes the token as a QUERY PARAMETER, and\n * appsecret_proof has to go in the query regardless, so both live in one place\n * and a request stays readable in a log.\n *\n * The mode is passed in because for some providers auth and estate are the\n * same decision expressed in the URL; here it is unused, and saying so is\n * cheaper than wondering later whether it was forgotten.\n */\nexport function facebookLeadAdsAuthorize(\n credentials: Record<string, string | undefined>,\n request: PreparedRequest,\n _mode: ConnectorMode,\n): void {\n const url = new URL(request.url);\n\n url.searchParams.set(\"access_token\", String(credentials.pageAccessToken ?? \"\"));\n\n const proof = createHmac(\"sha256\", String(credentials.appSecret ?? \"\"))\n .update(String(credentials.pageAccessToken ?? \"\"))\n .digest(\"hex\");\n\n url.searchParams.set(\"appsecret_proof\", proof);\n request.url = url.toString();\n}\n\n/** The Facebook Lead Ads service, for the TypeScript runtime. */\nexport const FACEBOOK_LEAD_ADS: ServiceDescriptor = {\n service: \"facebook_lead_ads\",\n title: \"Facebook Lead Ads\",\n sandbox: \"none\",\n baseUrls: { ...FACEBOOK_LEAD_ADS_BASE_URLS },\n requires: [...FACEBOOK_LEAD_ADS_REQUIRES],\n authorize: facebookLeadAdsAuthorize,\n faker: facebookLeadAdsFaker,\n};\n","/**\n * GENERATED FILE — do not edit.\n *\n * Emitted from provider/actions/lead-get.json by weaver's generator.\n * A hand-edit here is destroyed by the next protocol sync, which is worse than\n * being rejected, because it works until it silently does not. Fix\n * provider/actions/lead-get.json (or weaver's template/) and regenerate:\n *\n * npm run provider -- facebook_lead_ads\n */\n\n/**\n * Read one lead's answers by id. Pair this with the `leadgen` trigger, whose\n * delivery carries only the id.\n *\n * GET /{leadId} —\n * https://developers.facebook.com/docs/marketing-api/guides/lead-ads/retrieving\n *\n * Notice what is NOT here: no key, no base URL, no mode check, no retry loop,\n * no fake/real branch. This describes the request; callConnector resolves the\n * connection, picks the estate, and either calls Facebook Lead Ads or calls\n * the faker.\n */\n\nimport {\n callConnector,\n type ConnectorResult,\n type RequestedMode,\n type Transport,\n} from \"@particle-academy/fancy-connector-core\";\nimport { FACEBOOK_LEAD_ADS } from \"../service.js\";\n\nexport const LEAD_GET_OPERATION = \"lead_get\";\n\nexport type LeadGetOptions = {\n /** The node's resolved config. Keys: leadId. */\n config: Record<string, unknown>;\n credentials?: Record<string, string | undefined>;\n mode?: RequestedMode;\n connectionId?: string | null;\n input?: unknown;\n attempts?: number;\n /** Override the transport. The only way to exercise this without a network. */\n transport?: Transport;\n};\n\nexport async function facebookLeadAdsLeadGet(options: LeadGetOptions): Promise<ConnectorResult> {\n const config = options.config ?? {};\n\n if (config.leadId === undefined || config.leadId === null || config.leadId === \"\") {\n throw new Error(`lead_get: \"leadId\" is required (Lead ID).`);\n }\n\n return callConnector(FACEBOOK_LEAD_ADS, {\n operation: LEAD_GET_OPERATION,\n config,\n input: options.input,\n ...(options.credentials === undefined ? {} : { credentials: options.credentials }),\n ...(options.mode === undefined ? {} : { mode: options.mode }),\n ...(options.connectionId === undefined ? {} : { connectionId: options.connectionId }),\n ...(options.attempts === undefined ? {} : { attempts: options.attempts }),\n ...(options.transport === undefined ? {} : { transport: options.transport }),\n request: {\n method: \"GET\",\n path: `/${encodeURIComponent(String(config.leadId))}`,\n query: {},\n },\n });\n}\n","/**\n * GENERATED FILE — do not edit.\n *\n * Emitted from provider/triggers/leadgen.json by weaver's generator.\n * A hand-edit here is destroyed by the next protocol sync, which is worse than\n * being rejected, because it works until it silently does not. Fix\n * provider/triggers/leadgen.json (or weaver's template/) and regenerate:\n *\n * npm run provider -- facebook_lead_ads\n */\n\n/**\n * Facebook Lead Ads's webhook trigger — the delivery contract.\n *\n * Kept beside the service descriptor rather than inside a node, because a\n * signature scheme is a fact about FACEBOOK LEAD ADS. Two Facebook Lead Ads\n * triggers must not be able to disagree about how a delivery is verified.\n */\n\nimport { verifyDelivery, type HmacScheme, type InboundDelivery, type TriggerDescriptor, type WebhookVerification } from \"@particle-academy/fancy-connector-core\";\nimport { facebookLeadAdsFaker } from \"../faker.js\";\n\nexport const FACEBOOK_LEAD_ADS_LEADGEN_SIGNATURE_HEADER = \"X-Hub-Signature-256\";\n\nexport const FACEBOOK_LEAD_ADS_LEADGEN_SCHEME: HmacScheme = {\n algorithm: \"SHA-256\",\n payload: (raw, timestamp) => raw,\n encoding: \"hex\",\n};\n\n/** Strip the `sha256=` prefix the provider sends. */\nexport function parseFacebookLeadAdsSignature(raw: string): { signature?: string } {\n return { signature: raw.startsWith(\"sha256=\") ? raw.slice(7) : raw };\n}\n\nexport const FACEBOOK_LEAD_ADS_LEADGEN: TriggerDescriptor = {\n service: \"facebook_lead_ads\",\n operation: \"leadgen\",\n delivery: \"webhook\",\n setup:\n \"In the Meta app dashboard, add a Webhooks product, subscribe to the PAGE object and tick the `leadgen` field, and point the callback URL at the route your host mounts for this trigger. Meta first sends a GET carrying `hub.mode=subscribe`, `hub.verify_token` and `hub.challenge` -- the host must echo `hub.challenge` back as the body and check `hub.verify_token` against the value set in the dashboard, or the subscription is never activated. Then subscribe the app to each Page (`POST /{pageId}/subscribed_apps` with `subscribed_fields=leadgen`), which is a SEPARATE step: a webhook configured but not subscribed per Page delivers nothing and reports nothing.\",\n verification: {\n signatureHeader: FACEBOOK_LEAD_ADS_LEADGEN_SIGNATURE_HEADER,\n scheme: FACEBOOK_LEAD_ADS_LEADGEN_SCHEME,\n parse: parseFacebookLeadAdsSignature,\n },\n faker: facebookLeadAdsFaker,\n};\n\n/**\n * Verify one inbound Facebook Lead Ads delivery.\n *\n * The host calls this BEFORE starting a run, with the body exactly as\n * received. Re-serialised JSON changes key order and whitespace, and produces\n * a mismatch that looks precisely like a wrong secret — hours of debugging the\n * wrong thing.\n *\n * The secret is the connection's `appSecret`.\n */\nexport function verifyFacebookLeadAdsDelivery(\n delivery: InboundDelivery,\n appSecret: string | undefined,\n now?: number,\n): Promise<WebhookVerification> {\n return verifyDelivery(FACEBOOK_LEAD_ADS_LEADGEN, delivery, appSecret, now);\n}\n","/**\n * GENERATED FILE — do not edit.\n *\n * Emitted from provider/actions/ + triggers/ by weaver's generator.\n * A hand-edit here is destroyed by the next protocol sync, which is worse than\n * being rejected, because it works until it silently does not. Fix\n * provider/actions/ + triggers/ (or weaver's template/) and regenerate:\n *\n * npm run provider -- facebook_lead_ads\n */\n\n/**\n * Facebook Lead Ads's node kinds with their TypeScript executors attached —\n * for hosts that EXECUTE on TS.\n *\n * The authoring surface in @particle-academy/facebook-lead-ads-ui carries no\n * executor: the editor is React on every host, so a PHP or Python project\n * installs the ui package and never this one.\n */\n\nimport type { NodeExecutor, NodeKindDefinition } from \"@particle-academy/fancy-flow/engine\";\nimport {\n idempotencyKeyFor,\n NO_IDEMPOTENCY_KEY_WARNING,\n resolveConnection,\n triggerEvent,\n type RequestedMode,\n} from \"@particle-academy/fancy-connector-core\";\nimport { FACEBOOK_LEAD_ADS } from \"./service.js\";\n\nimport {\n facebookLeadAdsLeadKind,\n facebookLeadAdsWebhookTriggerKind,\n} from \"@particle-academy/facebook-lead-ads-ui\";\n\nimport { facebookLeadAdsLeadGet } from \"./actions/lead-get.js\";\nimport { FACEBOOK_LEAD_ADS_LEADGEN } from \"./triggers/leadgen.js\";\n\nexport const facebookLeadAdsLeadExecutor: NodeExecutor = async (ctx) => {\n const config = ((ctx.node.data as { config?: Record<string, unknown> })?.config ?? {});\n\n const result = await facebookLeadAdsLeadGet({\n config,\n input: ctx.inputs?.in,\n });\n\n ctx.emit({\n type: \"log\",\n level: \"info\",\n nodeId: ctx.node.id,\n message: `facebook_lead_ads lead_get ${(result.data as { id?: string })?.id} (${result.mode})`,\n });\n\n return { __port: \"out\", value: result };\n};\n\nexport const facebookLeadAdsWebhookTriggerExecutor: NodeExecutor = async (ctx) => {\n const config = ((ctx.node.data as { config?: Record<string, unknown> })?.config ?? {});\n const connection = resolveConnection({\n service: FACEBOOK_LEAD_ADS.service,\n operation: \"leadgen\",\n sandbox: FACEBOOK_LEAD_ADS.sandbox,\n baseUrls: FACEBOOK_LEAD_ADS.baseUrls,\n requires: FACEBOOK_LEAD_ADS.requires,\n connectionId: typeof config.connection === \"string\" ? config.connection : null,\n requested: typeof config.mode === \"string\" ? (config.mode as RequestedMode) : null,\n });\n\n const event = triggerEvent(FACEBOOK_LEAD_ADS_LEADGEN, connection, ctx.inputs?.in, config);\n\n return { __port: \"out\", value: event };\n};\n\n/** The kinds a TypeScript host registers. */\nexport const FACEBOOK_LEAD_ADS_RUNNABLE_KINDS: NodeKindDefinition[] = [\n { ...facebookLeadAdsLeadKind, executor: facebookLeadAdsLeadExecutor },\n { ...facebookLeadAdsWebhookTriggerKind, executor: facebookLeadAdsWebhookTriggerExecutor },\n];\n"]}
1
+ {"version":3,"sources":["../src/faker.ts","../src/service.ts","../src/actions/lead-get.ts","../src/triggers/leadgen.ts","../src/flow.ts"],"names":["createHmac","callConnector","resolveConnection","triggerEvent","facebookLeadAdsLeadKind","facebookLeadAdsWebhookTriggerKind"],"mappings":"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","file":"flow.cjs","sourcesContent":["/**\n * GENERATED FILE — do not edit.\n *\n * Emitted from provider/fixtures/ by weaver's generator.\n * A hand-edit here is destroyed by the next protocol sync, which is worse than\n * being rejected, because it works until it silently does not. Fix\n * provider/fixtures/ (or weaver's template/) and regenerate:\n *\n * npm run provider -- facebook_lead_ads\n */\n\n/**\n * The Facebook Lead Ads faker.\n *\n * Shapes, not behaviour: the goal is that a downstream node sees the field\n * NAMES Facebook Lead Ads actually publishes, so an author can wire {{\n * $json.data.id }} against a fake and have it keep working against the real\n * thing.\n *\n * Deterministic — same inputs, same output. A faker returning a fresh uuid\n * every call cannot be asserted on, so its fixtures degrade to \"it did not\n * throw\", which is the assertion that catches nothing.\n */\n\nimport type { ConnectorFaker, FakeRequest } from \"@particle-academy/fancy-connector-core\";\n\nfunction fakeLeadGet({ config, fake }: FakeRequest): unknown {\n return {\n \"id\": Array.from({ length: 15 }, () => fake.int(0, 9)).join(\"\"),\n \"created_time\": \"2026-08-24T09:15:00+0000\",\n \"ad_id\": Array.from({ length: 15 }, () => fake.int(0, 9)).join(\"\"),\n \"form_id\": Array.from({ length: 15 }, () => fake.int(0, 9)).join(\"\"),\n \"field_data\": [\n {\n \"name\": \"full_name\",\n \"values\": [\n \"Ada Lovelace\",\n ],\n },\n {\n \"name\": \"email\",\n \"values\": [\n \"ada@example.test\",\n ],\n },\n {\n \"name\": \"phone_number\",\n \"values\": [\n \"+15550100\",\n ],\n },\n {\n \"name\": \"what_are_you_interested_in\",\n \"values\": [\n \"Enterprise plan\",\n ],\n },\n ],\n };\n}\n\nfunction fakeLeadgen({ config, fake }: FakeRequest): unknown {\n const boundLeadid = Array.from({ length: 15 }, () => fake.int(0, 9)).join(\"\");\n\n return {\n \"leadgen_id\": boundLeadid,\n \"page_id\": Array.from({ length: 15 }, () => fake.int(0, 9)).join(\"\"),\n \"form_id\": Array.from({ length: 15 }, () => fake.int(0, 9)).join(\"\"),\n \"ad_id\": Array.from({ length: 15 }, () => fake.int(0, 9)).join(\"\"),\n \"adgroup_id\": Array.from({ length: 15 }, () => fake.int(0, 9)).join(\"\"),\n \"created_time\": 1787577300,\n };\n}\n\nexport const facebookLeadAdsFaker: ConnectorFaker = (operation, request) => {\n switch (operation) {\n case \"lead_get\":\n return fakeLeadGet(request);\n\n case \"leadgen\":\n return fakeLeadgen(request);\n\n default:\n // A faker asked for an operation it has no shape for must SAY so. Making\n // something up would produce a green run whose output silently has none\n // of the fields the author is about to reference.\n throw new Error(\n `facebook_lead_ads: no fake response is defined for \"${operation}\". ` +\n \"Add a fixture under provider/fixtures/ and regenerate — a connector without a faker \" +\n \"cannot be developed against, tested, or demonstrated.\",\n );\n }\n};\n","/**\n * GENERATED FILE — do not edit.\n *\n * Emitted from provider/manifest.json by weaver's generator.\n * A hand-edit here is destroyed by the next protocol sync, which is worse than\n * being rejected, because it works until it silently does not. Fix\n * provider/manifest.json (or weaver's template/) and regenerate:\n *\n * npm run provider -- facebook_lead_ads\n */\n\n/**\n * Facebook Lead Ads, as one service descriptor shared by every Facebook Lead\n * Ads operation.\n *\n * @particle-academy/fancy-connector-core carries what is true of ALL\n * connectors. This carries what is true of Facebook Lead Ads: its base URL,\n * its auth scheme, its idempotency header, and its faker.\n *\n * ## The sandbox trap, written down where it is used\n *\n * Facebook Lead Ads has no sandbox. Meta's Lead Ads Testing Tool creates a\n * test lead on the REAL form, which fires the real webhook and appears\n * alongside real leads until somebody deletes it. Anything this reads is real\n * customer data belonging to a real advertiser.\n */\n\nimport type { ConnectorMode, PreparedRequest, ServiceDescriptor } from \"@particle-academy/fancy-connector-core\";\n\nimport { facebookLeadAdsFaker } from \"./faker.js\";\nimport { createHmac } from \"node:crypto\";\n\n/**\n * The connector API version this package was GENERATED against.\n *\n * A literal, never imported. An imported constant lets an upgrade rewrite the\n * very claim it exists to detect, after which the copy agrees with itself\n * forever.\n */\nexport const CONNECTOR_API_VERSION = 1;\n\nexport const FACEBOOK_LEAD_ADS_BASE_URLS = {\n \"live\": \"https://graph.facebook.com/v25.0\"\n} as const;\n\n/** Credential keys a remote call cannot proceed without. */\nexport const FACEBOOK_LEAD_ADS_REQUIRES = [\n \"pageAccessToken\",\n \"appSecret\",\n \"clientId\",\n \"clientSecret\"\n] as const;\n\n/**\n * Apply Facebook Lead Ads's auth scheme to an outgoing request.\n *\n * Same as facebook-pages: Meta takes the token as a QUERY PARAMETER, and\n * appsecret_proof has to go in the query regardless, so both live in one place\n * and a request stays readable in a log.\n *\n * The mode is passed in because for some providers auth and estate are the\n * same decision expressed in the URL; here it is unused, and saying so is\n * cheaper than wondering later whether it was forgotten.\n */\nexport function facebookLeadAdsAuthorize(\n credentials: Record<string, string | undefined>,\n request: PreparedRequest,\n _mode: ConnectorMode,\n): void {\n const url = new URL(request.url);\n\n url.searchParams.set(\"access_token\", String(credentials.pageAccessToken ?? \"\"));\n\n const proof = createHmac(\"sha256\", String(credentials.appSecret ?? \"\"))\n .update(String(credentials.pageAccessToken ?? \"\"))\n .digest(\"hex\");\n\n url.searchParams.set(\"appsecret_proof\", proof);\n request.url = url.toString();\n}\n\n/** The Facebook Lead Ads service, for the TypeScript runtime. */\nexport const FACEBOOK_LEAD_ADS: ServiceDescriptor = {\n service: \"facebook_lead_ads\",\n title: \"Facebook Lead Ads\",\n sandbox: \"none\",\n baseUrls: { ...FACEBOOK_LEAD_ADS_BASE_URLS },\n requires: [...FACEBOOK_LEAD_ADS_REQUIRES],\n authorize: facebookLeadAdsAuthorize,\n faker: facebookLeadAdsFaker,\n};\n","/**\n * GENERATED FILE — do not edit.\n *\n * Emitted from provider/actions/lead-get.json by weaver's generator.\n * A hand-edit here is destroyed by the next protocol sync, which is worse than\n * being rejected, because it works until it silently does not. Fix\n * provider/actions/lead-get.json (or weaver's template/) and regenerate:\n *\n * npm run provider -- facebook_lead_ads\n */\n\n/**\n * Read one lead's answers by id. Pair this with the `leadgen` trigger, whose\n * delivery carries only the id.\n *\n * GET /{leadId} —\n * https://developers.facebook.com/docs/marketing-api/guides/lead-ads/retrieving\n *\n * Notice what is NOT here: no key, no base URL, no mode check, no retry loop,\n * no fake/real branch. This describes the request; callConnector resolves the\n * connection, picks the estate, and either calls Facebook Lead Ads or calls\n * the faker.\n */\n\nimport {\n callConnector,\n type ConnectorResult,\n type RequestedMode,\n type Transport,\n} from \"@particle-academy/fancy-connector-core\";\nimport { FACEBOOK_LEAD_ADS } from \"../service.js\";\n\nexport const LEAD_GET_OPERATION = \"lead_get\";\n\nexport type LeadGetOptions = {\n /** The node's resolved config. Keys: leadId. */\n config: Record<string, unknown>;\n credentials?: Record<string, string | undefined>;\n mode?: RequestedMode;\n connectionId?: string | null;\n input?: unknown;\n attempts?: number;\n /** Override the transport. The only way to exercise this without a network. */\n transport?: Transport;\n};\n\nexport async function facebookLeadAdsLeadGet(options: LeadGetOptions): Promise<ConnectorResult> {\n const config = options.config ?? {};\n\n if (config.leadId === undefined || config.leadId === null || config.leadId === \"\") {\n throw new Error(`lead_get: \"leadId\" is required (Lead ID).`);\n }\n\n return callConnector(FACEBOOK_LEAD_ADS, {\n operation: LEAD_GET_OPERATION,\n config,\n input: options.input,\n ...(options.credentials === undefined ? {} : { credentials: options.credentials }),\n ...(options.mode === undefined ? {} : { mode: options.mode }),\n ...(options.connectionId === undefined ? {} : { connectionId: options.connectionId }),\n ...(options.attempts === undefined ? {} : { attempts: options.attempts }),\n ...(options.transport === undefined ? {} : { transport: options.transport }),\n request: {\n method: \"GET\",\n path: `/${encodeURIComponent(String(config.leadId))}`,\n query: {},\n },\n });\n}\n","/**\n * GENERATED FILE — do not edit.\n *\n * Emitted from provider/triggers/leadgen.json by weaver's generator.\n * A hand-edit here is destroyed by the next protocol sync, which is worse than\n * being rejected, because it works until it silently does not. Fix\n * provider/triggers/leadgen.json (or weaver's template/) and regenerate:\n *\n * npm run provider -- facebook_lead_ads\n */\n\n/**\n * Facebook Lead Ads's webhook trigger — the delivery contract.\n *\n * Kept beside the service descriptor rather than inside a node, because a\n * signature scheme is a fact about FACEBOOK LEAD ADS. Two Facebook Lead Ads\n * triggers must not be able to disagree about how a delivery is verified.\n */\n\nimport { verifyDelivery, type HmacScheme, type InboundDelivery, type TriggerDescriptor, type WebhookVerification } from \"@particle-academy/fancy-connector-core\";\nimport { facebookLeadAdsFaker } from \"../faker.js\";\n\nexport const FACEBOOK_LEAD_ADS_LEADGEN_SIGNATURE_HEADER = \"X-Hub-Signature-256\";\n\nexport const FACEBOOK_LEAD_ADS_LEADGEN_SCHEME: HmacScheme = {\n algorithm: \"SHA-256\",\n payload: (raw, timestamp) => raw,\n encoding: \"hex\",\n};\n\n/** Strip the `sha256=` prefix the provider sends. Without it, there is no signature. */\nexport function parseFacebookLeadAdsSignature(raw: string): { signature?: string } {\n return raw.startsWith(\"sha256=\") ? { signature: raw.slice(7) } : {};\n}\n\nexport const FACEBOOK_LEAD_ADS_LEADGEN: TriggerDescriptor = {\n service: \"facebook_lead_ads\",\n operation: \"leadgen\",\n delivery: \"webhook\",\n setup:\n \"In the Meta app dashboard, add a Webhooks product, subscribe to the PAGE object and tick the `leadgen` field, and point the callback URL at the route your host mounts for this trigger. Meta first sends a GET carrying `hub.mode=subscribe`, `hub.verify_token` and `hub.challenge` -- the host must echo `hub.challenge` back as the body and check `hub.verify_token` against the value set in the dashboard, or the subscription is never activated. Then subscribe the app to each Page (`POST /{pageId}/subscribed_apps` with `subscribed_fields=leadgen`), which is a SEPARATE step: a webhook configured but not subscribed per Page delivers nothing and reports nothing.\",\n verification: {\n signatureHeader: FACEBOOK_LEAD_ADS_LEADGEN_SIGNATURE_HEADER,\n scheme: FACEBOOK_LEAD_ADS_LEADGEN_SCHEME,\n parse: parseFacebookLeadAdsSignature,\n },\n faker: facebookLeadAdsFaker,\n};\n\n/**\n * Verify one inbound Facebook Lead Ads delivery.\n *\n * The host calls this BEFORE starting a run, with the body exactly as\n * received. Re-serialised JSON changes key order and whitespace, and produces\n * a mismatch that looks precisely like a wrong secret — hours of debugging the\n * wrong thing.\n *\n * The secret is the connection's `appSecret`.\n */\nexport function verifyFacebookLeadAdsDelivery(\n delivery: InboundDelivery,\n appSecret: string | undefined,\n now?: number,\n): Promise<WebhookVerification> {\n return verifyDelivery(FACEBOOK_LEAD_ADS_LEADGEN, delivery, appSecret, now);\n}\n","/**\n * GENERATED FILE — do not edit.\n *\n * Emitted from provider/actions/ + triggers/ by weaver's generator.\n * A hand-edit here is destroyed by the next protocol sync, which is worse than\n * being rejected, because it works until it silently does not. Fix\n * provider/actions/ + triggers/ (or weaver's template/) and regenerate:\n *\n * npm run provider -- facebook_lead_ads\n */\n\n/**\n * Facebook Lead Ads's node kinds with their TypeScript executors attached —\n * for hosts that EXECUTE on TS.\n *\n * The authoring surface in @particle-academy/facebook-lead-ads-ui carries no\n * executor: the editor is React on every host, so a PHP or Python project\n * installs the ui package and never this one.\n */\n\nimport type { NodeExecutor, NodeKindDefinition } from \"@particle-academy/fancy-flow/engine\";\nimport {\n idempotencyKeyFor,\n NO_IDEMPOTENCY_KEY_WARNING,\n resolveConnection,\n triggerEvent,\n type RequestedMode,\n} from \"@particle-academy/fancy-connector-core\";\nimport { FACEBOOK_LEAD_ADS } from \"./service.js\";\n\nimport {\n facebookLeadAdsLeadKind,\n facebookLeadAdsWebhookTriggerKind,\n} from \"@particle-academy/facebook-lead-ads-ui\";\n\nimport { facebookLeadAdsLeadGet } from \"./actions/lead-get.js\";\nimport { FACEBOOK_LEAD_ADS_LEADGEN } from \"./triggers/leadgen.js\";\n\nexport const facebookLeadAdsLeadExecutor: NodeExecutor = async (ctx) => {\n const config = ((ctx.node.data as { config?: Record<string, unknown> })?.config ?? {});\n\n const result = await facebookLeadAdsLeadGet({\n config,\n input: ctx.inputs?.in,\n });\n\n ctx.emit({\n type: \"log\",\n level: \"info\",\n nodeId: ctx.node.id,\n message: `facebook_lead_ads lead_get ${(result.data as { id?: string })?.id} (${result.mode})`,\n });\n\n return { __port: \"out\", value: result };\n};\n\nexport const facebookLeadAdsWebhookTriggerExecutor: NodeExecutor = async (ctx) => {\n const config = ((ctx.node.data as { config?: Record<string, unknown> })?.config ?? {});\n const connection = resolveConnection({\n service: FACEBOOK_LEAD_ADS.service,\n operation: \"leadgen\",\n sandbox: FACEBOOK_LEAD_ADS.sandbox,\n baseUrls: FACEBOOK_LEAD_ADS.baseUrls,\n requires: FACEBOOK_LEAD_ADS.requires,\n connectionId: typeof config.connection === \"string\" ? config.connection : null,\n requested: typeof config.mode === \"string\" ? (config.mode as RequestedMode) : null,\n });\n\n const event = triggerEvent(FACEBOOK_LEAD_ADS_LEADGEN, connection, ctx.inputs?.in, config);\n\n return { __port: \"out\", value: event };\n};\n\n/** The kinds a TypeScript host registers. */\nexport const FACEBOOK_LEAD_ADS_RUNNABLE_KINDS: NodeKindDefinition[] = [\n { ...facebookLeadAdsLeadKind, executor: facebookLeadAdsLeadExecutor },\n { ...facebookLeadAdsWebhookTriggerKind, executor: facebookLeadAdsWebhookTriggerExecutor },\n];\n"]}
package/dist/flow.js CHANGED
@@ -1,4 +1,4 @@
1
- import { facebookLeadAdsLeadGet, FACEBOOK_LEAD_ADS, FACEBOOK_LEAD_ADS_LEADGEN } from './chunk-JVNM4F6J.js';
1
+ import { facebookLeadAdsLeadGet, FACEBOOK_LEAD_ADS, FACEBOOK_LEAD_ADS_LEADGEN } from './chunk-XJVRJJTY.js';
2
2
  import { resolveConnection, triggerEvent } from '@particle-academy/fancy-connector-core';
3
3
  import { facebookLeadAdsLeadKind, facebookLeadAdsWebhookTriggerKind } from '@particle-academy/facebook-lead-ads-ui';
4
4
 
package/dist/index.cjs CHANGED
@@ -116,7 +116,7 @@ var FACEBOOK_LEAD_ADS_LEADGEN_SCHEME = {
116
116
  encoding: "hex"
117
117
  };
118
118
  function parseFacebookLeadAdsSignature(raw) {
119
- return { signature: raw.startsWith("sha256=") ? raw.slice(7) : raw };
119
+ return raw.startsWith("sha256=") ? { signature: raw.slice(7) } : {};
120
120
  }
121
121
  var FACEBOOK_LEAD_ADS_LEADGEN = {
122
122
  service: "facebook_lead_ads",
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/faker.ts","../src/service.ts","../src/actions/lead-get.ts","../src/triggers/leadgen.ts"],"names":["createHmac","callConnector","verifyDelivery"],"mappings":";;;;;;AA0BA,SAAS,WAAA,CAAY,EAAE,MAAA,EAAQ,IAAA,EAAK,EAAyB;AAC3D,EAAA,OAAO;AAAA,IACL,IAAA,EAAM,KAAA,CAAM,IAAA,CAAK,EAAE,QAAQ,EAAA,EAAG,EAAG,MAAM,IAAA,CAAK,IAAI,CAAA,EAAG,CAAC,CAAC,CAAA,CAAE,KAAK,EAAE,CAAA;AAAA,IAC9D,cAAA,EAAgB,0BAAA;AAAA,IAChB,OAAA,EAAS,KAAA,CAAM,IAAA,CAAK,EAAE,QAAQ,EAAA,EAAG,EAAG,MAAM,IAAA,CAAK,IAAI,CAAA,EAAG,CAAC,CAAC,CAAA,CAAE,KAAK,EAAE,CAAA;AAAA,IACjE,SAAA,EAAW,KAAA,CAAM,IAAA,CAAK,EAAE,QAAQ,EAAA,EAAG,EAAG,MAAM,IAAA,CAAK,IAAI,CAAA,EAAG,CAAC,CAAC,CAAA,CAAE,KAAK,EAAE,CAAA;AAAA,IACnE,YAAA,EAAc;AAAA,MACZ;AAAA,QACE,MAAA,EAAQ,WAAA;AAAA,QACR,QAAA,EAAU;AAAA,UACR;AAAA;AACF,OACF;AAAA,MACA;AAAA,QACE,MAAA,EAAQ,OAAA;AAAA,QACR,QAAA,EAAU;AAAA,UACR;AAAA;AACF,OACF;AAAA,MACA;AAAA,QACE,MAAA,EAAQ,cAAA;AAAA,QACR,QAAA,EAAU;AAAA,UACR;AAAA;AACF,OACF;AAAA,MACA;AAAA,QACE,MAAA,EAAQ,4BAAA;AAAA,QACR,QAAA,EAAU;AAAA,UACR;AAAA;AACF;AACF;AACF,GACF;AACF;AAEA,SAAS,WAAA,CAAY,EAAE,MAAA,EAAQ,IAAA,EAAK,EAAyB;AAC3D,EAAA,MAAM,WAAA,GAAc,KAAA,CAAM,IAAA,CAAK,EAAE,QAAQ,EAAA,EAAG,EAAG,MAAM,IAAA,CAAK,IAAI,CAAA,EAAG,CAAC,CAAC,CAAA,CAAE,KAAK,EAAE,CAAA;AAE5E,EAAA,OAAO;AAAA,IACL,YAAA,EAAc,WAAA;AAAA,IACd,SAAA,EAAW,KAAA,CAAM,IAAA,CAAK,EAAE,QAAQ,EAAA,EAAG,EAAG,MAAM,IAAA,CAAK,IAAI,CAAA,EAAG,CAAC,CAAC,CAAA,CAAE,KAAK,EAAE,CAAA;AAAA,IACnE,SAAA,EAAW,KAAA,CAAM,IAAA,CAAK,EAAE,QAAQ,EAAA,EAAG,EAAG,MAAM,IAAA,CAAK,IAAI,CAAA,EAAG,CAAC,CAAC,CAAA,CAAE,KAAK,EAAE,CAAA;AAAA,IACnE,OAAA,EAAS,KAAA,CAAM,IAAA,CAAK,EAAE,QAAQ,EAAA,EAAG,EAAG,MAAM,IAAA,CAAK,IAAI,CAAA,EAAG,CAAC,CAAC,CAAA,CAAE,KAAK,EAAE,CAAA;AAAA,IACjE,YAAA,EAAc,KAAA,CAAM,IAAA,CAAK,EAAE,QAAQ,EAAA,EAAG,EAAG,MAAM,IAAA,CAAK,IAAI,CAAA,EAAG,CAAC,CAAC,CAAA,CAAE,KAAK,EAAE,CAAA;AAAA,IACtE,cAAA,EAAgB;AAAA,GAClB;AACF;AAEO,IAAM,oBAAA,GAAuC,CAAC,SAAA,EAAW,OAAA,KAAY;AAC1E,EAAA,QAAQ,SAAA;AAAW,IACjB,KAAK,UAAA;AACH,MAAA,OAAO,YAAY,OAAO,CAAA;AAAA,IAE5B,KAAK,SAAA;AACH,MAAA,OAAO,YAAY,OAAO,CAAA;AAAA,IAE5B;AAIE,MAAA,MAAM,IAAI,KAAA;AAAA,QACR,uDAAuD,SAAS,CAAA,iJAAA;AAAA,OAGlE;AAAA;AAEN;ACrDO,IAAM,qBAAA,GAAwB;AAE9B,IAAM,2BAAA,GAA8B;AAAA,EACzC,MAAA,EAAQ;AACV;AAGO,IAAM,0BAAA,GAA6B;AAAA,EACxC,iBAAA;AAAA,EACA,WAAA;AAAA,EACA,UAAA;AAAA,EACA;AACF;AAaO,SAAS,wBAAA,CACd,WAAA,EACA,OAAA,EACA,KAAA,EACM;AACN,EAAA,MAAM,GAAA,GAAM,IAAI,GAAA,CAAI,OAAA,CAAQ,GAAG,CAAA;AAE/B,EAAA,GAAA,CAAI,aAAa,GAAA,CAAI,cAAA,EAAgB,OAAO,WAAA,CAAY,eAAA,IAAmB,EAAE,CAAC,CAAA;AAE9E,EAAA,MAAM,QAAQA,iBAAA,CAAW,QAAA,EAAU,MAAA,CAAO,WAAA,CAAY,aAAa,EAAE,CAAC,CAAA,CACnE,MAAA,CAAO,OAAO,WAAA,CAAY,eAAA,IAAmB,EAAE,CAAC,CAAA,CAChD,OAAO,KAAK,CAAA;AAEf,EAAA,GAAA,CAAI,YAAA,CAAa,GAAA,CAAI,iBAAA,EAAmB,KAAK,CAAA;AAC7C,EAAA,OAAA,CAAQ,GAAA,GAAM,IAAI,QAAA,EAAS;AAC7B;AAGO,IAAM,iBAAA,GAAuC;AAAA,EAClD,OAAA,EAAS,mBAAA;AAAA,EACT,KAAA,EAAO,mBAAA;AAAA,EACP,OAAA,EAAS,MAAA;AAAA,EACT,QAAA,EAAU,EAAE,GAAG,2BAAA,EAA4B;AAAA,EAC3C,QAAA,EAAU,CAAC,GAAG,0BAA0B,CAAA;AAAA,EACxC,SAAA,EAAW,wBAAA;AAAA,EACX,KAAA,EAAO;AACT;AC1DO,IAAM,kBAAA,GAAqB;AAclC,eAAsB,uBAAuB,OAAA,EAAmD;AAC9F,EAAA,MAAM,MAAA,GAAS,OAAA,CAAQ,MAAA,IAAU,EAAC;AAElC,EAAA,IAAI,MAAA,CAAO,WAAW,MAAA,IAAa,MAAA,CAAO,WAAW,IAAA,IAAQ,MAAA,CAAO,WAAW,EAAA,EAAI;AACjF,IAAA,MAAM,IAAI,MAAM,CAAA,yCAAA,CAA2C,CAAA;AAAA,EAC7D;AAEA,EAAA,OAAOC,iCAAc,iBAAA,EAAmB;AAAA,IACtC,SAAA,EAAW,kBAAA;AAAA,IACX,MAAA;AAAA,IACA,OAAO,OAAA,CAAQ,KAAA;AAAA,IACf,GAAI,QAAQ,WAAA,KAAgB,MAAA,GAAY,EAAC,GAAI,EAAE,WAAA,EAAa,OAAA,CAAQ,WAAA,EAAY;AAAA,IAChF,GAAI,QAAQ,IAAA,KAAS,MAAA,GAAY,EAAC,GAAI,EAAE,IAAA,EAAM,OAAA,CAAQ,IAAA,EAAK;AAAA,IAC3D,GAAI,QAAQ,YAAA,KAAiB,MAAA,GAAY,EAAC,GAAI,EAAE,YAAA,EAAc,OAAA,CAAQ,YAAA,EAAa;AAAA,IACnF,GAAI,QAAQ,QAAA,KAAa,MAAA,GAAY,EAAC,GAAI,EAAE,QAAA,EAAU,OAAA,CAAQ,QAAA,EAAS;AAAA,IACvE,GAAI,QAAQ,SAAA,KAAc,MAAA,GAAY,EAAC,GAAI,EAAE,SAAA,EAAW,OAAA,CAAQ,SAAA,EAAU;AAAA,IAC1E,OAAA,EAAS;AAAA,MACP,MAAA,EAAQ,KAAA;AAAA,MACR,MAAM,CAAA,CAAA,EAAI,kBAAA,CAAmB,OAAO,MAAA,CAAO,MAAM,CAAC,CAAC,CAAA,CAAA;AAAA,MACnD,OAAO;AAAC;AACV,GACD,CAAA;AACH;AC9CO,IAAM,0CAAA,GAA6C;AAEnD,IAAM,gCAAA,GAA+C;AAAA,EAC1D,SAAA,EAAW,SAAA;AAAA,EACX,OAAA,EAAS,CAAC,GAAA,EAAK,SAAA,KAAc,GAAA;AAAA,EAC7B,QAAA,EAAU;AACZ;AAGO,SAAS,8BAA8B,GAAA,EAAqC;AACjF,EAAA,OAAO,EAAE,SAAA,EAAW,GAAA,CAAI,UAAA,CAAW,SAAS,IAAI,GAAA,CAAI,KAAA,CAAM,CAAC,CAAA,GAAI,GAAA,EAAI;AACrE;AAEO,IAAM,yBAAA,GAA+C;AAAA,EAC1D,OAAA,EAAS,mBAAA;AAAA,EACT,SAAA,EAAW,SAAA;AAAA,EACX,QAAA,EAAU,SAAA;AAAA,EACV,KAAA,EACE,qpBAAA;AAAA,EACF,YAAA,EAAc;AAAA,IACZ,eAAA,EAAiB,0CAAA;AAAA,IACjB,MAAA,EAAQ,gCAAA;AAAA,IACR,KAAA,EAAO;AAAA,GACT;AAAA,EACA,KAAA,EAAO;AACT;AAYO,SAAS,6BAAA,CACd,QAAA,EACA,SAAA,EACA,GAAA,EAC8B;AAC9B,EAAA,OAAOC,iCAAA,CAAe,yBAAA,EAA2B,QAAA,EAAU,SAAA,EAAW,GAAG,CAAA;AAC3E","file":"index.cjs","sourcesContent":["/**\n * GENERATED FILE — do not edit.\n *\n * Emitted from provider/fixtures/ by weaver's generator.\n * A hand-edit here is destroyed by the next protocol sync, which is worse than\n * being rejected, because it works until it silently does not. Fix\n * provider/fixtures/ (or weaver's template/) and regenerate:\n *\n * npm run provider -- facebook_lead_ads\n */\n\n/**\n * The Facebook Lead Ads faker.\n *\n * Shapes, not behaviour: the goal is that a downstream node sees the field\n * NAMES Facebook Lead Ads actually publishes, so an author can wire {{\n * $json.data.id }} against a fake and have it keep working against the real\n * thing.\n *\n * Deterministic — same inputs, same output. A faker returning a fresh uuid\n * every call cannot be asserted on, so its fixtures degrade to \"it did not\n * throw\", which is the assertion that catches nothing.\n */\n\nimport type { ConnectorFaker, FakeRequest } from \"@particle-academy/fancy-connector-core\";\n\nfunction fakeLeadGet({ config, fake }: FakeRequest): unknown {\n return {\n \"id\": Array.from({ length: 15 }, () => fake.int(0, 9)).join(\"\"),\n \"created_time\": \"2026-08-24T09:15:00+0000\",\n \"ad_id\": Array.from({ length: 15 }, () => fake.int(0, 9)).join(\"\"),\n \"form_id\": Array.from({ length: 15 }, () => fake.int(0, 9)).join(\"\"),\n \"field_data\": [\n {\n \"name\": \"full_name\",\n \"values\": [\n \"Ada Lovelace\",\n ],\n },\n {\n \"name\": \"email\",\n \"values\": [\n \"ada@example.test\",\n ],\n },\n {\n \"name\": \"phone_number\",\n \"values\": [\n \"+15550100\",\n ],\n },\n {\n \"name\": \"what_are_you_interested_in\",\n \"values\": [\n \"Enterprise plan\",\n ],\n },\n ],\n };\n}\n\nfunction fakeLeadgen({ config, fake }: FakeRequest): unknown {\n const boundLeadid = Array.from({ length: 15 }, () => fake.int(0, 9)).join(\"\");\n\n return {\n \"leadgen_id\": boundLeadid,\n \"page_id\": Array.from({ length: 15 }, () => fake.int(0, 9)).join(\"\"),\n \"form_id\": Array.from({ length: 15 }, () => fake.int(0, 9)).join(\"\"),\n \"ad_id\": Array.from({ length: 15 }, () => fake.int(0, 9)).join(\"\"),\n \"adgroup_id\": Array.from({ length: 15 }, () => fake.int(0, 9)).join(\"\"),\n \"created_time\": 1787577300,\n };\n}\n\nexport const facebookLeadAdsFaker: ConnectorFaker = (operation, request) => {\n switch (operation) {\n case \"lead_get\":\n return fakeLeadGet(request);\n\n case \"leadgen\":\n return fakeLeadgen(request);\n\n default:\n // A faker asked for an operation it has no shape for must SAY so. Making\n // something up would produce a green run whose output silently has none\n // of the fields the author is about to reference.\n throw new Error(\n `facebook_lead_ads: no fake response is defined for \"${operation}\". ` +\n \"Add a fixture under provider/fixtures/ and regenerate — a connector without a faker \" +\n \"cannot be developed against, tested, or demonstrated.\",\n );\n }\n};\n","/**\n * GENERATED FILE — do not edit.\n *\n * Emitted from provider/manifest.json by weaver's generator.\n * A hand-edit here is destroyed by the next protocol sync, which is worse than\n * being rejected, because it works until it silently does not. Fix\n * provider/manifest.json (or weaver's template/) and regenerate:\n *\n * npm run provider -- facebook_lead_ads\n */\n\n/**\n * Facebook Lead Ads, as one service descriptor shared by every Facebook Lead\n * Ads operation.\n *\n * @particle-academy/fancy-connector-core carries what is true of ALL\n * connectors. This carries what is true of Facebook Lead Ads: its base URL,\n * its auth scheme, its idempotency header, and its faker.\n *\n * ## The sandbox trap, written down where it is used\n *\n * Facebook Lead Ads has no sandbox. Meta's Lead Ads Testing Tool creates a\n * test lead on the REAL form, which fires the real webhook and appears\n * alongside real leads until somebody deletes it. Anything this reads is real\n * customer data belonging to a real advertiser.\n */\n\nimport type { ConnectorMode, PreparedRequest, ServiceDescriptor } from \"@particle-academy/fancy-connector-core\";\n\nimport { facebookLeadAdsFaker } from \"./faker.js\";\nimport { createHmac } from \"node:crypto\";\n\n/**\n * The connector API version this package was GENERATED against.\n *\n * A literal, never imported. An imported constant lets an upgrade rewrite the\n * very claim it exists to detect, after which the copy agrees with itself\n * forever.\n */\nexport const CONNECTOR_API_VERSION = 1;\n\nexport const FACEBOOK_LEAD_ADS_BASE_URLS = {\n \"live\": \"https://graph.facebook.com/v25.0\"\n} as const;\n\n/** Credential keys a remote call cannot proceed without. */\nexport const FACEBOOK_LEAD_ADS_REQUIRES = [\n \"pageAccessToken\",\n \"appSecret\",\n \"clientId\",\n \"clientSecret\"\n] as const;\n\n/**\n * Apply Facebook Lead Ads's auth scheme to an outgoing request.\n *\n * Same as facebook-pages: Meta takes the token as a QUERY PARAMETER, and\n * appsecret_proof has to go in the query regardless, so both live in one place\n * and a request stays readable in a log.\n *\n * The mode is passed in because for some providers auth and estate are the\n * same decision expressed in the URL; here it is unused, and saying so is\n * cheaper than wondering later whether it was forgotten.\n */\nexport function facebookLeadAdsAuthorize(\n credentials: Record<string, string | undefined>,\n request: PreparedRequest,\n _mode: ConnectorMode,\n): void {\n const url = new URL(request.url);\n\n url.searchParams.set(\"access_token\", String(credentials.pageAccessToken ?? \"\"));\n\n const proof = createHmac(\"sha256\", String(credentials.appSecret ?? \"\"))\n .update(String(credentials.pageAccessToken ?? \"\"))\n .digest(\"hex\");\n\n url.searchParams.set(\"appsecret_proof\", proof);\n request.url = url.toString();\n}\n\n/** The Facebook Lead Ads service, for the TypeScript runtime. */\nexport const FACEBOOK_LEAD_ADS: ServiceDescriptor = {\n service: \"facebook_lead_ads\",\n title: \"Facebook Lead Ads\",\n sandbox: \"none\",\n baseUrls: { ...FACEBOOK_LEAD_ADS_BASE_URLS },\n requires: [...FACEBOOK_LEAD_ADS_REQUIRES],\n authorize: facebookLeadAdsAuthorize,\n faker: facebookLeadAdsFaker,\n};\n","/**\n * GENERATED FILE — do not edit.\n *\n * Emitted from provider/actions/lead-get.json by weaver's generator.\n * A hand-edit here is destroyed by the next protocol sync, which is worse than\n * being rejected, because it works until it silently does not. Fix\n * provider/actions/lead-get.json (or weaver's template/) and regenerate:\n *\n * npm run provider -- facebook_lead_ads\n */\n\n/**\n * Read one lead's answers by id. Pair this with the `leadgen` trigger, whose\n * delivery carries only the id.\n *\n * GET /{leadId} —\n * https://developers.facebook.com/docs/marketing-api/guides/lead-ads/retrieving\n *\n * Notice what is NOT here: no key, no base URL, no mode check, no retry loop,\n * no fake/real branch. This describes the request; callConnector resolves the\n * connection, picks the estate, and either calls Facebook Lead Ads or calls\n * the faker.\n */\n\nimport {\n callConnector,\n type ConnectorResult,\n type RequestedMode,\n type Transport,\n} from \"@particle-academy/fancy-connector-core\";\nimport { FACEBOOK_LEAD_ADS } from \"../service.js\";\n\nexport const LEAD_GET_OPERATION = \"lead_get\";\n\nexport type LeadGetOptions = {\n /** The node's resolved config. Keys: leadId. */\n config: Record<string, unknown>;\n credentials?: Record<string, string | undefined>;\n mode?: RequestedMode;\n connectionId?: string | null;\n input?: unknown;\n attempts?: number;\n /** Override the transport. The only way to exercise this without a network. */\n transport?: Transport;\n};\n\nexport async function facebookLeadAdsLeadGet(options: LeadGetOptions): Promise<ConnectorResult> {\n const config = options.config ?? {};\n\n if (config.leadId === undefined || config.leadId === null || config.leadId === \"\") {\n throw new Error(`lead_get: \"leadId\" is required (Lead ID).`);\n }\n\n return callConnector(FACEBOOK_LEAD_ADS, {\n operation: LEAD_GET_OPERATION,\n config,\n input: options.input,\n ...(options.credentials === undefined ? {} : { credentials: options.credentials }),\n ...(options.mode === undefined ? {} : { mode: options.mode }),\n ...(options.connectionId === undefined ? {} : { connectionId: options.connectionId }),\n ...(options.attempts === undefined ? {} : { attempts: options.attempts }),\n ...(options.transport === undefined ? {} : { transport: options.transport }),\n request: {\n method: \"GET\",\n path: `/${encodeURIComponent(String(config.leadId))}`,\n query: {},\n },\n });\n}\n","/**\n * GENERATED FILE — do not edit.\n *\n * Emitted from provider/triggers/leadgen.json by weaver's generator.\n * A hand-edit here is destroyed by the next protocol sync, which is worse than\n * being rejected, because it works until it silently does not. Fix\n * provider/triggers/leadgen.json (or weaver's template/) and regenerate:\n *\n * npm run provider -- facebook_lead_ads\n */\n\n/**\n * Facebook Lead Ads's webhook trigger — the delivery contract.\n *\n * Kept beside the service descriptor rather than inside a node, because a\n * signature scheme is a fact about FACEBOOK LEAD ADS. Two Facebook Lead Ads\n * triggers must not be able to disagree about how a delivery is verified.\n */\n\nimport { verifyDelivery, type HmacScheme, type InboundDelivery, type TriggerDescriptor, type WebhookVerification } from \"@particle-academy/fancy-connector-core\";\nimport { facebookLeadAdsFaker } from \"../faker.js\";\n\nexport const FACEBOOK_LEAD_ADS_LEADGEN_SIGNATURE_HEADER = \"X-Hub-Signature-256\";\n\nexport const FACEBOOK_LEAD_ADS_LEADGEN_SCHEME: HmacScheme = {\n algorithm: \"SHA-256\",\n payload: (raw, timestamp) => raw,\n encoding: \"hex\",\n};\n\n/** Strip the `sha256=` prefix the provider sends. */\nexport function parseFacebookLeadAdsSignature(raw: string): { signature?: string } {\n return { signature: raw.startsWith(\"sha256=\") ? raw.slice(7) : raw };\n}\n\nexport const FACEBOOK_LEAD_ADS_LEADGEN: TriggerDescriptor = {\n service: \"facebook_lead_ads\",\n operation: \"leadgen\",\n delivery: \"webhook\",\n setup:\n \"In the Meta app dashboard, add a Webhooks product, subscribe to the PAGE object and tick the `leadgen` field, and point the callback URL at the route your host mounts for this trigger. Meta first sends a GET carrying `hub.mode=subscribe`, `hub.verify_token` and `hub.challenge` -- the host must echo `hub.challenge` back as the body and check `hub.verify_token` against the value set in the dashboard, or the subscription is never activated. Then subscribe the app to each Page (`POST /{pageId}/subscribed_apps` with `subscribed_fields=leadgen`), which is a SEPARATE step: a webhook configured but not subscribed per Page delivers nothing and reports nothing.\",\n verification: {\n signatureHeader: FACEBOOK_LEAD_ADS_LEADGEN_SIGNATURE_HEADER,\n scheme: FACEBOOK_LEAD_ADS_LEADGEN_SCHEME,\n parse: parseFacebookLeadAdsSignature,\n },\n faker: facebookLeadAdsFaker,\n};\n\n/**\n * Verify one inbound Facebook Lead Ads delivery.\n *\n * The host calls this BEFORE starting a run, with the body exactly as\n * received. Re-serialised JSON changes key order and whitespace, and produces\n * a mismatch that looks precisely like a wrong secret — hours of debugging the\n * wrong thing.\n *\n * The secret is the connection's `appSecret`.\n */\nexport function verifyFacebookLeadAdsDelivery(\n delivery: InboundDelivery,\n appSecret: string | undefined,\n now?: number,\n): Promise<WebhookVerification> {\n return verifyDelivery(FACEBOOK_LEAD_ADS_LEADGEN, delivery, appSecret, now);\n}\n"]}
1
+ {"version":3,"sources":["../src/faker.ts","../src/service.ts","../src/actions/lead-get.ts","../src/triggers/leadgen.ts"],"names":["createHmac","callConnector","verifyDelivery"],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * GENERATED FILE — do not edit.\n *\n * Emitted from provider/fixtures/ by weaver's generator.\n * A hand-edit here is destroyed by the next protocol sync, which is worse than\n * being rejected, because it works until it silently does not. Fix\n * provider/fixtures/ (or weaver's template/) and regenerate:\n *\n * npm run provider -- facebook_lead_ads\n */\n\n/**\n * The Facebook Lead Ads faker.\n *\n * Shapes, not behaviour: the goal is that a downstream node sees the field\n * NAMES Facebook Lead Ads actually publishes, so an author can wire {{\n * $json.data.id }} against a fake and have it keep working against the real\n * thing.\n *\n * Deterministic — same inputs, same output. A faker returning a fresh uuid\n * every call cannot be asserted on, so its fixtures degrade to \"it did not\n * throw\", which is the assertion that catches nothing.\n */\n\nimport type { ConnectorFaker, FakeRequest } from \"@particle-academy/fancy-connector-core\";\n\nfunction fakeLeadGet({ config, fake }: FakeRequest): unknown {\n return {\n \"id\": Array.from({ length: 15 }, () => fake.int(0, 9)).join(\"\"),\n \"created_time\": \"2026-08-24T09:15:00+0000\",\n \"ad_id\": Array.from({ length: 15 }, () => fake.int(0, 9)).join(\"\"),\n \"form_id\": Array.from({ length: 15 }, () => fake.int(0, 9)).join(\"\"),\n \"field_data\": [\n {\n \"name\": \"full_name\",\n \"values\": [\n \"Ada Lovelace\",\n ],\n },\n {\n \"name\": \"email\",\n \"values\": [\n \"ada@example.test\",\n ],\n },\n {\n \"name\": \"phone_number\",\n \"values\": [\n \"+15550100\",\n ],\n },\n {\n \"name\": \"what_are_you_interested_in\",\n \"values\": [\n \"Enterprise plan\",\n ],\n },\n ],\n };\n}\n\nfunction fakeLeadgen({ config, fake }: FakeRequest): unknown {\n const boundLeadid = Array.from({ length: 15 }, () => fake.int(0, 9)).join(\"\");\n\n return {\n \"leadgen_id\": boundLeadid,\n \"page_id\": Array.from({ length: 15 }, () => fake.int(0, 9)).join(\"\"),\n \"form_id\": Array.from({ length: 15 }, () => fake.int(0, 9)).join(\"\"),\n \"ad_id\": Array.from({ length: 15 }, () => fake.int(0, 9)).join(\"\"),\n \"adgroup_id\": Array.from({ length: 15 }, () => fake.int(0, 9)).join(\"\"),\n \"created_time\": 1787577300,\n };\n}\n\nexport const facebookLeadAdsFaker: ConnectorFaker = (operation, request) => {\n switch (operation) {\n case \"lead_get\":\n return fakeLeadGet(request);\n\n case \"leadgen\":\n return fakeLeadgen(request);\n\n default:\n // A faker asked for an operation it has no shape for must SAY so. Making\n // something up would produce a green run whose output silently has none\n // of the fields the author is about to reference.\n throw new Error(\n `facebook_lead_ads: no fake response is defined for \"${operation}\". ` +\n \"Add a fixture under provider/fixtures/ and regenerate — a connector without a faker \" +\n \"cannot be developed against, tested, or demonstrated.\",\n );\n }\n};\n","/**\n * GENERATED FILE — do not edit.\n *\n * Emitted from provider/manifest.json by weaver's generator.\n * A hand-edit here is destroyed by the next protocol sync, which is worse than\n * being rejected, because it works until it silently does not. Fix\n * provider/manifest.json (or weaver's template/) and regenerate:\n *\n * npm run provider -- facebook_lead_ads\n */\n\n/**\n * Facebook Lead Ads, as one service descriptor shared by every Facebook Lead\n * Ads operation.\n *\n * @particle-academy/fancy-connector-core carries what is true of ALL\n * connectors. This carries what is true of Facebook Lead Ads: its base URL,\n * its auth scheme, its idempotency header, and its faker.\n *\n * ## The sandbox trap, written down where it is used\n *\n * Facebook Lead Ads has no sandbox. Meta's Lead Ads Testing Tool creates a\n * test lead on the REAL form, which fires the real webhook and appears\n * alongside real leads until somebody deletes it. Anything this reads is real\n * customer data belonging to a real advertiser.\n */\n\nimport type { ConnectorMode, PreparedRequest, ServiceDescriptor } from \"@particle-academy/fancy-connector-core\";\n\nimport { facebookLeadAdsFaker } from \"./faker.js\";\nimport { createHmac } from \"node:crypto\";\n\n/**\n * The connector API version this package was GENERATED against.\n *\n * A literal, never imported. An imported constant lets an upgrade rewrite the\n * very claim it exists to detect, after which the copy agrees with itself\n * forever.\n */\nexport const CONNECTOR_API_VERSION = 1;\n\nexport const FACEBOOK_LEAD_ADS_BASE_URLS = {\n \"live\": \"https://graph.facebook.com/v25.0\"\n} as const;\n\n/** Credential keys a remote call cannot proceed without. */\nexport const FACEBOOK_LEAD_ADS_REQUIRES = [\n \"pageAccessToken\",\n \"appSecret\",\n \"clientId\",\n \"clientSecret\"\n] as const;\n\n/**\n * Apply Facebook Lead Ads's auth scheme to an outgoing request.\n *\n * Same as facebook-pages: Meta takes the token as a QUERY PARAMETER, and\n * appsecret_proof has to go in the query regardless, so both live in one place\n * and a request stays readable in a log.\n *\n * The mode is passed in because for some providers auth and estate are the\n * same decision expressed in the URL; here it is unused, and saying so is\n * cheaper than wondering later whether it was forgotten.\n */\nexport function facebookLeadAdsAuthorize(\n credentials: Record<string, string | undefined>,\n request: PreparedRequest,\n _mode: ConnectorMode,\n): void {\n const url = new URL(request.url);\n\n url.searchParams.set(\"access_token\", String(credentials.pageAccessToken ?? \"\"));\n\n const proof = createHmac(\"sha256\", String(credentials.appSecret ?? \"\"))\n .update(String(credentials.pageAccessToken ?? \"\"))\n .digest(\"hex\");\n\n url.searchParams.set(\"appsecret_proof\", proof);\n request.url = url.toString();\n}\n\n/** The Facebook Lead Ads service, for the TypeScript runtime. */\nexport const FACEBOOK_LEAD_ADS: ServiceDescriptor = {\n service: \"facebook_lead_ads\",\n title: \"Facebook Lead Ads\",\n sandbox: \"none\",\n baseUrls: { ...FACEBOOK_LEAD_ADS_BASE_URLS },\n requires: [...FACEBOOK_LEAD_ADS_REQUIRES],\n authorize: facebookLeadAdsAuthorize,\n faker: facebookLeadAdsFaker,\n};\n","/**\n * GENERATED FILE — do not edit.\n *\n * Emitted from provider/actions/lead-get.json by weaver's generator.\n * A hand-edit here is destroyed by the next protocol sync, which is worse than\n * being rejected, because it works until it silently does not. Fix\n * provider/actions/lead-get.json (or weaver's template/) and regenerate:\n *\n * npm run provider -- facebook_lead_ads\n */\n\n/**\n * Read one lead's answers by id. Pair this with the `leadgen` trigger, whose\n * delivery carries only the id.\n *\n * GET /{leadId} —\n * https://developers.facebook.com/docs/marketing-api/guides/lead-ads/retrieving\n *\n * Notice what is NOT here: no key, no base URL, no mode check, no retry loop,\n * no fake/real branch. This describes the request; callConnector resolves the\n * connection, picks the estate, and either calls Facebook Lead Ads or calls\n * the faker.\n */\n\nimport {\n callConnector,\n type ConnectorResult,\n type RequestedMode,\n type Transport,\n} from \"@particle-academy/fancy-connector-core\";\nimport { FACEBOOK_LEAD_ADS } from \"../service.js\";\n\nexport const LEAD_GET_OPERATION = \"lead_get\";\n\nexport type LeadGetOptions = {\n /** The node's resolved config. Keys: leadId. */\n config: Record<string, unknown>;\n credentials?: Record<string, string | undefined>;\n mode?: RequestedMode;\n connectionId?: string | null;\n input?: unknown;\n attempts?: number;\n /** Override the transport. The only way to exercise this without a network. */\n transport?: Transport;\n};\n\nexport async function facebookLeadAdsLeadGet(options: LeadGetOptions): Promise<ConnectorResult> {\n const config = options.config ?? {};\n\n if (config.leadId === undefined || config.leadId === null || config.leadId === \"\") {\n throw new Error(`lead_get: \"leadId\" is required (Lead ID).`);\n }\n\n return callConnector(FACEBOOK_LEAD_ADS, {\n operation: LEAD_GET_OPERATION,\n config,\n input: options.input,\n ...(options.credentials === undefined ? {} : { credentials: options.credentials }),\n ...(options.mode === undefined ? {} : { mode: options.mode }),\n ...(options.connectionId === undefined ? {} : { connectionId: options.connectionId }),\n ...(options.attempts === undefined ? {} : { attempts: options.attempts }),\n ...(options.transport === undefined ? {} : { transport: options.transport }),\n request: {\n method: \"GET\",\n path: `/${encodeURIComponent(String(config.leadId))}`,\n query: {},\n },\n });\n}\n","/**\n * GENERATED FILE — do not edit.\n *\n * Emitted from provider/triggers/leadgen.json by weaver's generator.\n * A hand-edit here is destroyed by the next protocol sync, which is worse than\n * being rejected, because it works until it silently does not. Fix\n * provider/triggers/leadgen.json (or weaver's template/) and regenerate:\n *\n * npm run provider -- facebook_lead_ads\n */\n\n/**\n * Facebook Lead Ads's webhook trigger — the delivery contract.\n *\n * Kept beside the service descriptor rather than inside a node, because a\n * signature scheme is a fact about FACEBOOK LEAD ADS. Two Facebook Lead Ads\n * triggers must not be able to disagree about how a delivery is verified.\n */\n\nimport { verifyDelivery, type HmacScheme, type InboundDelivery, type TriggerDescriptor, type WebhookVerification } from \"@particle-academy/fancy-connector-core\";\nimport { facebookLeadAdsFaker } from \"../faker.js\";\n\nexport const FACEBOOK_LEAD_ADS_LEADGEN_SIGNATURE_HEADER = \"X-Hub-Signature-256\";\n\nexport const FACEBOOK_LEAD_ADS_LEADGEN_SCHEME: HmacScheme = {\n algorithm: \"SHA-256\",\n payload: (raw, timestamp) => raw,\n encoding: \"hex\",\n};\n\n/** Strip the `sha256=` prefix the provider sends. Without it, there is no signature. */\nexport function parseFacebookLeadAdsSignature(raw: string): { signature?: string } {\n return raw.startsWith(\"sha256=\") ? { signature: raw.slice(7) } : {};\n}\n\nexport const FACEBOOK_LEAD_ADS_LEADGEN: TriggerDescriptor = {\n service: \"facebook_lead_ads\",\n operation: \"leadgen\",\n delivery: \"webhook\",\n setup:\n \"In the Meta app dashboard, add a Webhooks product, subscribe to the PAGE object and tick the `leadgen` field, and point the callback URL at the route your host mounts for this trigger. Meta first sends a GET carrying `hub.mode=subscribe`, `hub.verify_token` and `hub.challenge` -- the host must echo `hub.challenge` back as the body and check `hub.verify_token` against the value set in the dashboard, or the subscription is never activated. Then subscribe the app to each Page (`POST /{pageId}/subscribed_apps` with `subscribed_fields=leadgen`), which is a SEPARATE step: a webhook configured but not subscribed per Page delivers nothing and reports nothing.\",\n verification: {\n signatureHeader: FACEBOOK_LEAD_ADS_LEADGEN_SIGNATURE_HEADER,\n scheme: FACEBOOK_LEAD_ADS_LEADGEN_SCHEME,\n parse: parseFacebookLeadAdsSignature,\n },\n faker: facebookLeadAdsFaker,\n};\n\n/**\n * Verify one inbound Facebook Lead Ads delivery.\n *\n * The host calls this BEFORE starting a run, with the body exactly as\n * received. Re-serialised JSON changes key order and whitespace, and produces\n * a mismatch that looks precisely like a wrong secret — hours of debugging the\n * wrong thing.\n *\n * The secret is the connection's `appSecret`.\n */\nexport function verifyFacebookLeadAdsDelivery(\n delivery: InboundDelivery,\n appSecret: string | undefined,\n now?: number,\n): Promise<WebhookVerification> {\n return verifyDelivery(FACEBOOK_LEAD_ADS_LEADGEN, delivery, appSecret, now);\n}\n"]}
package/dist/index.d.cts CHANGED
@@ -136,7 +136,7 @@ declare function facebookLeadAdsLeadGet(options: LeadGetOptions): Promise<Connec
136
136
 
137
137
  declare const FACEBOOK_LEAD_ADS_LEADGEN_SIGNATURE_HEADER = "X-Hub-Signature-256";
138
138
  declare const FACEBOOK_LEAD_ADS_LEADGEN_SCHEME: HmacScheme;
139
- /** Strip the `sha256=` prefix the provider sends. */
139
+ /** Strip the `sha256=` prefix the provider sends. Without it, there is no signature. */
140
140
  declare function parseFacebookLeadAdsSignature(raw: string): {
141
141
  signature?: string;
142
142
  };
package/dist/index.d.ts CHANGED
@@ -136,7 +136,7 @@ declare function facebookLeadAdsLeadGet(options: LeadGetOptions): Promise<Connec
136
136
 
137
137
  declare const FACEBOOK_LEAD_ADS_LEADGEN_SIGNATURE_HEADER = "X-Hub-Signature-256";
138
138
  declare const FACEBOOK_LEAD_ADS_LEADGEN_SCHEME: HmacScheme;
139
- /** Strip the `sha256=` prefix the provider sends. */
139
+ /** Strip the `sha256=` prefix the provider sends. Without it, there is no signature. */
140
140
  declare function parseFacebookLeadAdsSignature(raw: string): {
141
141
  signature?: string;
142
142
  };
package/dist/index.js CHANGED
@@ -1,3 +1,3 @@
1
- export { CONNECTOR_API_VERSION, FACEBOOK_LEAD_ADS, FACEBOOK_LEAD_ADS_BASE_URLS, FACEBOOK_LEAD_ADS_LEADGEN, FACEBOOK_LEAD_ADS_LEADGEN_SCHEME, FACEBOOK_LEAD_ADS_LEADGEN_SIGNATURE_HEADER, FACEBOOK_LEAD_ADS_REQUIRES, LEAD_GET_OPERATION, facebookLeadAdsAuthorize, facebookLeadAdsFaker, facebookLeadAdsLeadGet, parseFacebookLeadAdsSignature, verifyFacebookLeadAdsDelivery } from './chunk-JVNM4F6J.js';
1
+ export { CONNECTOR_API_VERSION, FACEBOOK_LEAD_ADS, FACEBOOK_LEAD_ADS_BASE_URLS, FACEBOOK_LEAD_ADS_LEADGEN, FACEBOOK_LEAD_ADS_LEADGEN_SCHEME, FACEBOOK_LEAD_ADS_LEADGEN_SIGNATURE_HEADER, FACEBOOK_LEAD_ADS_REQUIRES, LEAD_GET_OPERATION, facebookLeadAdsAuthorize, facebookLeadAdsFaker, facebookLeadAdsLeadGet, parseFacebookLeadAdsSignature, verifyFacebookLeadAdsDelivery } from './chunk-XJVRJJTY.js';
2
2
  //# sourceMappingURL=index.js.map
3
3
  //# sourceMappingURL=index.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@particle-academy/facebook-lead-ads-js",
3
- "version": "0.1.3",
3
+ "version": "0.2.0",
4
4
  "description": "Facebook Lead Ads for Node — the service descriptor, its faker, its webhook verification, and one function per operation. Plain HTTP on @particle-academy/fancy-connector-core; no vendor SDK.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -38,6 +38,7 @@
38
38
  },
39
39
  "files": [
40
40
  "dist",
41
+ "art",
41
42
  "README.md",
42
43
  "CHANGELOG.md",
43
44
  "LICENSE"
@@ -53,7 +54,7 @@
53
54
  },
54
55
  "peerDependencies": {
55
56
  "@particle-academy/fancy-flow": ">=0.47.1 <2.0",
56
- "@particle-academy/facebook-lead-ads-ui": ">=0.1.0 <0.2.0"
57
+ "@particle-academy/facebook-lead-ads-ui": ">=0.2.0 <0.3.0"
57
58
  },
58
59
  "peerDependenciesMeta": {
59
60
  "@particle-academy/fancy-flow": {
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/faker.ts","../src/service.ts","../src/actions/lead-get.ts","../src/triggers/leadgen.ts"],"names":[],"mappings":"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","file":"chunk-JVNM4F6J.js","sourcesContent":["/**\n * GENERATED FILE — do not edit.\n *\n * Emitted from provider/fixtures/ by weaver's generator.\n * A hand-edit here is destroyed by the next protocol sync, which is worse than\n * being rejected, because it works until it silently does not. Fix\n * provider/fixtures/ (or weaver's template/) and regenerate:\n *\n * npm run provider -- facebook_lead_ads\n */\n\n/**\n * The Facebook Lead Ads faker.\n *\n * Shapes, not behaviour: the goal is that a downstream node sees the field\n * NAMES Facebook Lead Ads actually publishes, so an author can wire {{\n * $json.data.id }} against a fake and have it keep working against the real\n * thing.\n *\n * Deterministic — same inputs, same output. A faker returning a fresh uuid\n * every call cannot be asserted on, so its fixtures degrade to \"it did not\n * throw\", which is the assertion that catches nothing.\n */\n\nimport type { ConnectorFaker, FakeRequest } from \"@particle-academy/fancy-connector-core\";\n\nfunction fakeLeadGet({ config, fake }: FakeRequest): unknown {\n return {\n \"id\": Array.from({ length: 15 }, () => fake.int(0, 9)).join(\"\"),\n \"created_time\": \"2026-08-24T09:15:00+0000\",\n \"ad_id\": Array.from({ length: 15 }, () => fake.int(0, 9)).join(\"\"),\n \"form_id\": Array.from({ length: 15 }, () => fake.int(0, 9)).join(\"\"),\n \"field_data\": [\n {\n \"name\": \"full_name\",\n \"values\": [\n \"Ada Lovelace\",\n ],\n },\n {\n \"name\": \"email\",\n \"values\": [\n \"ada@example.test\",\n ],\n },\n {\n \"name\": \"phone_number\",\n \"values\": [\n \"+15550100\",\n ],\n },\n {\n \"name\": \"what_are_you_interested_in\",\n \"values\": [\n \"Enterprise plan\",\n ],\n },\n ],\n };\n}\n\nfunction fakeLeadgen({ config, fake }: FakeRequest): unknown {\n const boundLeadid = Array.from({ length: 15 }, () => fake.int(0, 9)).join(\"\");\n\n return {\n \"leadgen_id\": boundLeadid,\n \"page_id\": Array.from({ length: 15 }, () => fake.int(0, 9)).join(\"\"),\n \"form_id\": Array.from({ length: 15 }, () => fake.int(0, 9)).join(\"\"),\n \"ad_id\": Array.from({ length: 15 }, () => fake.int(0, 9)).join(\"\"),\n \"adgroup_id\": Array.from({ length: 15 }, () => fake.int(0, 9)).join(\"\"),\n \"created_time\": 1787577300,\n };\n}\n\nexport const facebookLeadAdsFaker: ConnectorFaker = (operation, request) => {\n switch (operation) {\n case \"lead_get\":\n return fakeLeadGet(request);\n\n case \"leadgen\":\n return fakeLeadgen(request);\n\n default:\n // A faker asked for an operation it has no shape for must SAY so. Making\n // something up would produce a green run whose output silently has none\n // of the fields the author is about to reference.\n throw new Error(\n `facebook_lead_ads: no fake response is defined for \"${operation}\". ` +\n \"Add a fixture under provider/fixtures/ and regenerate — a connector without a faker \" +\n \"cannot be developed against, tested, or demonstrated.\",\n );\n }\n};\n","/**\n * GENERATED FILE — do not edit.\n *\n * Emitted from provider/manifest.json by weaver's generator.\n * A hand-edit here is destroyed by the next protocol sync, which is worse than\n * being rejected, because it works until it silently does not. Fix\n * provider/manifest.json (or weaver's template/) and regenerate:\n *\n * npm run provider -- facebook_lead_ads\n */\n\n/**\n * Facebook Lead Ads, as one service descriptor shared by every Facebook Lead\n * Ads operation.\n *\n * @particle-academy/fancy-connector-core carries what is true of ALL\n * connectors. This carries what is true of Facebook Lead Ads: its base URL,\n * its auth scheme, its idempotency header, and its faker.\n *\n * ## The sandbox trap, written down where it is used\n *\n * Facebook Lead Ads has no sandbox. Meta's Lead Ads Testing Tool creates a\n * test lead on the REAL form, which fires the real webhook and appears\n * alongside real leads until somebody deletes it. Anything this reads is real\n * customer data belonging to a real advertiser.\n */\n\nimport type { ConnectorMode, PreparedRequest, ServiceDescriptor } from \"@particle-academy/fancy-connector-core\";\n\nimport { facebookLeadAdsFaker } from \"./faker.js\";\nimport { createHmac } from \"node:crypto\";\n\n/**\n * The connector API version this package was GENERATED against.\n *\n * A literal, never imported. An imported constant lets an upgrade rewrite the\n * very claim it exists to detect, after which the copy agrees with itself\n * forever.\n */\nexport const CONNECTOR_API_VERSION = 1;\n\nexport const FACEBOOK_LEAD_ADS_BASE_URLS = {\n \"live\": \"https://graph.facebook.com/v25.0\"\n} as const;\n\n/** Credential keys a remote call cannot proceed without. */\nexport const FACEBOOK_LEAD_ADS_REQUIRES = [\n \"pageAccessToken\",\n \"appSecret\",\n \"clientId\",\n \"clientSecret\"\n] as const;\n\n/**\n * Apply Facebook Lead Ads's auth scheme to an outgoing request.\n *\n * Same as facebook-pages: Meta takes the token as a QUERY PARAMETER, and\n * appsecret_proof has to go in the query regardless, so both live in one place\n * and a request stays readable in a log.\n *\n * The mode is passed in because for some providers auth and estate are the\n * same decision expressed in the URL; here it is unused, and saying so is\n * cheaper than wondering later whether it was forgotten.\n */\nexport function facebookLeadAdsAuthorize(\n credentials: Record<string, string | undefined>,\n request: PreparedRequest,\n _mode: ConnectorMode,\n): void {\n const url = new URL(request.url);\n\n url.searchParams.set(\"access_token\", String(credentials.pageAccessToken ?? \"\"));\n\n const proof = createHmac(\"sha256\", String(credentials.appSecret ?? \"\"))\n .update(String(credentials.pageAccessToken ?? \"\"))\n .digest(\"hex\");\n\n url.searchParams.set(\"appsecret_proof\", proof);\n request.url = url.toString();\n}\n\n/** The Facebook Lead Ads service, for the TypeScript runtime. */\nexport const FACEBOOK_LEAD_ADS: ServiceDescriptor = {\n service: \"facebook_lead_ads\",\n title: \"Facebook Lead Ads\",\n sandbox: \"none\",\n baseUrls: { ...FACEBOOK_LEAD_ADS_BASE_URLS },\n requires: [...FACEBOOK_LEAD_ADS_REQUIRES],\n authorize: facebookLeadAdsAuthorize,\n faker: facebookLeadAdsFaker,\n};\n","/**\n * GENERATED FILE — do not edit.\n *\n * Emitted from provider/actions/lead-get.json by weaver's generator.\n * A hand-edit here is destroyed by the next protocol sync, which is worse than\n * being rejected, because it works until it silently does not. Fix\n * provider/actions/lead-get.json (or weaver's template/) and regenerate:\n *\n * npm run provider -- facebook_lead_ads\n */\n\n/**\n * Read one lead's answers by id. Pair this with the `leadgen` trigger, whose\n * delivery carries only the id.\n *\n * GET /{leadId} —\n * https://developers.facebook.com/docs/marketing-api/guides/lead-ads/retrieving\n *\n * Notice what is NOT here: no key, no base URL, no mode check, no retry loop,\n * no fake/real branch. This describes the request; callConnector resolves the\n * connection, picks the estate, and either calls Facebook Lead Ads or calls\n * the faker.\n */\n\nimport {\n callConnector,\n type ConnectorResult,\n type RequestedMode,\n type Transport,\n} from \"@particle-academy/fancy-connector-core\";\nimport { FACEBOOK_LEAD_ADS } from \"../service.js\";\n\nexport const LEAD_GET_OPERATION = \"lead_get\";\n\nexport type LeadGetOptions = {\n /** The node's resolved config. Keys: leadId. */\n config: Record<string, unknown>;\n credentials?: Record<string, string | undefined>;\n mode?: RequestedMode;\n connectionId?: string | null;\n input?: unknown;\n attempts?: number;\n /** Override the transport. The only way to exercise this without a network. */\n transport?: Transport;\n};\n\nexport async function facebookLeadAdsLeadGet(options: LeadGetOptions): Promise<ConnectorResult> {\n const config = options.config ?? {};\n\n if (config.leadId === undefined || config.leadId === null || config.leadId === \"\") {\n throw new Error(`lead_get: \"leadId\" is required (Lead ID).`);\n }\n\n return callConnector(FACEBOOK_LEAD_ADS, {\n operation: LEAD_GET_OPERATION,\n config,\n input: options.input,\n ...(options.credentials === undefined ? {} : { credentials: options.credentials }),\n ...(options.mode === undefined ? {} : { mode: options.mode }),\n ...(options.connectionId === undefined ? {} : { connectionId: options.connectionId }),\n ...(options.attempts === undefined ? {} : { attempts: options.attempts }),\n ...(options.transport === undefined ? {} : { transport: options.transport }),\n request: {\n method: \"GET\",\n path: `/${encodeURIComponent(String(config.leadId))}`,\n query: {},\n },\n });\n}\n","/**\n * GENERATED FILE — do not edit.\n *\n * Emitted from provider/triggers/leadgen.json by weaver's generator.\n * A hand-edit here is destroyed by the next protocol sync, which is worse than\n * being rejected, because it works until it silently does not. Fix\n * provider/triggers/leadgen.json (or weaver's template/) and regenerate:\n *\n * npm run provider -- facebook_lead_ads\n */\n\n/**\n * Facebook Lead Ads's webhook trigger — the delivery contract.\n *\n * Kept beside the service descriptor rather than inside a node, because a\n * signature scheme is a fact about FACEBOOK LEAD ADS. Two Facebook Lead Ads\n * triggers must not be able to disagree about how a delivery is verified.\n */\n\nimport { verifyDelivery, type HmacScheme, type InboundDelivery, type TriggerDescriptor, type WebhookVerification } from \"@particle-academy/fancy-connector-core\";\nimport { facebookLeadAdsFaker } from \"../faker.js\";\n\nexport const FACEBOOK_LEAD_ADS_LEADGEN_SIGNATURE_HEADER = \"X-Hub-Signature-256\";\n\nexport const FACEBOOK_LEAD_ADS_LEADGEN_SCHEME: HmacScheme = {\n algorithm: \"SHA-256\",\n payload: (raw, timestamp) => raw,\n encoding: \"hex\",\n};\n\n/** Strip the `sha256=` prefix the provider sends. */\nexport function parseFacebookLeadAdsSignature(raw: string): { signature?: string } {\n return { signature: raw.startsWith(\"sha256=\") ? raw.slice(7) : raw };\n}\n\nexport const FACEBOOK_LEAD_ADS_LEADGEN: TriggerDescriptor = {\n service: \"facebook_lead_ads\",\n operation: \"leadgen\",\n delivery: \"webhook\",\n setup:\n \"In the Meta app dashboard, add a Webhooks product, subscribe to the PAGE object and tick the `leadgen` field, and point the callback URL at the route your host mounts for this trigger. Meta first sends a GET carrying `hub.mode=subscribe`, `hub.verify_token` and `hub.challenge` -- the host must echo `hub.challenge` back as the body and check `hub.verify_token` against the value set in the dashboard, or the subscription is never activated. Then subscribe the app to each Page (`POST /{pageId}/subscribed_apps` with `subscribed_fields=leadgen`), which is a SEPARATE step: a webhook configured but not subscribed per Page delivers nothing and reports nothing.\",\n verification: {\n signatureHeader: FACEBOOK_LEAD_ADS_LEADGEN_SIGNATURE_HEADER,\n scheme: FACEBOOK_LEAD_ADS_LEADGEN_SCHEME,\n parse: parseFacebookLeadAdsSignature,\n },\n faker: facebookLeadAdsFaker,\n};\n\n/**\n * Verify one inbound Facebook Lead Ads delivery.\n *\n * The host calls this BEFORE starting a run, with the body exactly as\n * received. Re-serialised JSON changes key order and whitespace, and produces\n * a mismatch that looks precisely like a wrong secret — hours of debugging the\n * wrong thing.\n *\n * The secret is the connection's `appSecret`.\n */\nexport function verifyFacebookLeadAdsDelivery(\n delivery: InboundDelivery,\n appSecret: string | undefined,\n now?: number,\n): Promise<WebhookVerification> {\n return verifyDelivery(FACEBOOK_LEAD_ADS_LEADGEN, delivery, appSecret, now);\n}\n"]}