@particle-academy/facebook-lead-ads-js 0.1.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 +92 -0
- package/LICENSE +21 -0
- package/README.md +111 -0
- package/dist/chunk-JVNM4F6J.js +137 -0
- package/dist/chunk-JVNM4F6J.js.map +1 -0
- package/dist/flow.cjs +174 -0
- package/dist/flow.cjs.map +1 -0
- package/dist/flow.d.cts +27 -0
- package/dist/flow.d.ts +27 -0
- package/dist/flow.js +40 -0
- package/dist/flow.js.map +1 -0
- package/dist/index.cjs +151 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +156 -0
- package/dist/index.d.ts +156 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/package.json +88 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to `@particle-academy/facebook-lead-ads-ui`,
|
|
4
|
+
`@particle-academy/facebook-lead-ads-js`,
|
|
5
|
+
`particle-academy/facebook-lead-ads-php` and `fancy-facebook-lead-ads`.
|
|
6
|
+
|
|
7
|
+
The four packages share one version, because they are generated from one
|
|
8
|
+
`provider/` definition and a version that meant something different in each
|
|
9
|
+
would be a version nobody could reason about.
|
|
10
|
+
|
|
11
|
+
## [0.1.0] — 2026-08-24
|
|
12
|
+
|
|
13
|
+
First release.
|
|
14
|
+
|
|
15
|
+
### Added
|
|
16
|
+
|
|
17
|
+
- `leadgen` — a webhook trigger that starts when somebody submits a lead form.
|
|
18
|
+
- `lead_get` — read one lead's answers by id. `GET /{leadId}`.
|
|
19
|
+
- Fakers for both, so a flow can be wired before Meta has ever been contacted.
|
|
20
|
+
|
|
21
|
+
### The delivery carries IDS, and no answers
|
|
22
|
+
|
|
23
|
+
This is the shape worth understanding before wiring anything. Meta's `leadgen`
|
|
24
|
+
webhook sends `leadgen_id`, `page_id`, `form_id`, `ad_id`, `adgroup_id` and
|
|
25
|
+
`created_time` — and **nothing else**. The name, the email and the phone number
|
|
26
|
+
are not in it. Reading them means a second call, which is what `lead_get` is.
|
|
27
|
+
|
|
28
|
+
So the trigger's faker returns ids and nothing else too. A faker that helpfully
|
|
29
|
+
invented an `email` would let somebody wire `{{ $json.email }}`, watch it work on
|
|
30
|
+
the canvas, and discover on the first real lead that the field was never there.
|
|
31
|
+
|
|
32
|
+
### The first `prefixed` signature header
|
|
33
|
+
|
|
34
|
+
Meta signs every delivery with `X-Hub-Signature-256`, quoting its own docs,
|
|
35
|
+
"preceded with `sha256=`". Stripe — the only webhook provider before this —
|
|
36
|
+
packs a timestamp and signature into one compound header instead. So
|
|
37
|
+
`verification.header.format: "prefixed"` had been declared in the vocabulary
|
|
38
|
+
since Stripe and **driven by nothing**, which is exactly the shape this kit keeps
|
|
39
|
+
finding: a declaration that validates clean and does nothing. It is exercised for
|
|
40
|
+
real now, by a suite that runs the generated PHP and Python against it.
|
|
41
|
+
|
|
42
|
+
### There is no timestamp, so there is no replay window
|
|
43
|
+
|
|
44
|
+
Stripe signs `{timestamp}.{body}` and a delivery older than the tolerance is
|
|
45
|
+
refused. Meta signs the body alone. **A captured delivery therefore stays valid
|
|
46
|
+
forever**, and replay protection cannot come from the signature — it has to come
|
|
47
|
+
from de-duplicating on `leadgen_id`. The definition says so rather than leaving
|
|
48
|
+
`tolerance` absent, because an absence looks like an oversight.
|
|
49
|
+
|
|
50
|
+
### One secret, two directions
|
|
51
|
+
|
|
52
|
+
`appSecret` keys the `appsecret_proof` HMAC on **outgoing** calls and is what
|
|
53
|
+
Meta signs **incoming** webhooks with. There is no per-endpoint webhook secret
|
|
54
|
+
here the way there is in Stripe, where a leaked secret exposes one endpoint.
|
|
55
|
+
|
|
56
|
+
The practical consequence: **rotating the app secret invalidates outgoing proofs
|
|
57
|
+
and incoming signatures at the same instant.** A host that updates one credential
|
|
58
|
+
field and not the other has broken the half it did not touch.
|
|
59
|
+
|
|
60
|
+
### `leads_retrieval` fails quietly if you skip it
|
|
61
|
+
|
|
62
|
+
Five scopes, and each is load-bearing:
|
|
63
|
+
|
|
64
|
+
| | |
|
|
65
|
+
|---|---|
|
|
66
|
+
| `leads_retrieval` | reads a lead's `field_data`. **Without it the lead resolves with the personal data omitted rather than failing** — the reassuring failure. Also requires the app to pass Meta's Business Verification. |
|
|
67
|
+
| `pages_manage_ads` | what Meta requires alongside it for lead access. |
|
|
68
|
+
| `pages_manage_metadata` | what lets the app SUBSCRIBE to a Page's `leadgen` webhook. Without it the trigger never fires and nothing reports why. |
|
|
69
|
+
| `pages_show_list`, `pages_read_engagement` | the standard Page pair. |
|
|
70
|
+
|
|
71
|
+
Subscribing the app **per Page** (`POST /{pageId}/subscribed_apps`) is a separate
|
|
72
|
+
step from configuring the webhook. A webhook configured but not subscribed
|
|
73
|
+
delivers nothing, silently.
|
|
74
|
+
|
|
75
|
+
### No sandbox — checked
|
|
76
|
+
|
|
77
|
+
Meta's Lead Ads Testing Tool submits a **test lead against a real form on a real
|
|
78
|
+
Page**, and it fires the real webhook. That is a way to exercise the integration,
|
|
79
|
+
not a test estate: the lead lands where a real one does and is deleted by hand.
|
|
80
|
+
No separate host, no separate account, no mode flag on the request.
|
|
81
|
+
|
|
82
|
+
Everything this reads is real customer data belonging to a real advertiser.
|
|
83
|
+
|
|
84
|
+
### Pinned to v25.0, not the newest
|
|
85
|
+
|
|
86
|
+
Meta supports a Graph API version for roughly two years — v25.0 runs to
|
|
87
|
+
2028-07-29 — and versioned changes land on the **newest** version immediately and
|
|
88
|
+
reach older ones later. The newest buys a few months of runway and takes every
|
|
89
|
+
breaking change first. An unversioned call silently gets the **oldest**
|
|
90
|
+
still-supported version, which is why the segment is pinned at all.
|
|
91
|
+
|
|
92
|
+
[0.1.0]: https://github.com/Fancy-Friends/facebook-lead-ads/releases/tag/v0.1.0
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Particle Academy
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# Facebook Lead Ads
|
|
2
|
+
|
|
3
|
+
Facebook Lead Ads for [fancy-flow][flow] — as **four imported, versioned packages**, one
|
|
4
|
+
per runtime. Not vendored source: a copy cannot be upgraded, and third-party APIs
|
|
5
|
+
change.
|
|
6
|
+
|
|
7
|
+
[flow]: https://github.com/Particle-Academy/fancy-flow
|
|
8
|
+
|
|
9
|
+
| Runtime | Package | Install |
|
|
10
|
+
|---|---|---|
|
|
11
|
+
| Authoring surface (every host) | `@particle-academy/facebook-lead-ads-ui` | `npm install @particle-academy/facebook-lead-ads-ui` |
|
|
12
|
+
| Node | `@particle-academy/facebook-lead-ads-js` | `npm install @particle-academy/facebook-lead-ads-js` |
|
|
13
|
+
| PHP 8.4+ | `particle-academy/facebook-lead-ads-php` | `composer require particle-academy/facebook-lead-ads-php` |
|
|
14
|
+
| Python 3.11+ | `fancy-facebook-lead-ads` | `pip install fancy-facebook-lead-ads` |
|
|
15
|
+
|
|
16
|
+
The `ui` package is the editor surface and is React on every host — a PHP or
|
|
17
|
+
Python project installs it *and* its own runtime package, and never the `js` one.
|
|
18
|
+
|
|
19
|
+
## What it costs you
|
|
20
|
+
|
|
21
|
+
One dependency: `@particle-academy/fancy-connector-core` (or
|
|
22
|
+
`particle-academy/fancy-connector-core` on Composer), which the `js` and `php`
|
|
23
|
+
packages pull in themselves. The Python package has **zero** runtime
|
|
24
|
+
dependencies.
|
|
25
|
+
|
|
26
|
+
**No Facebook Lead Ads SDK.** Plain HTTP, deliberately: a vendor SDK is third-party code
|
|
27
|
+
subject to the kit's full approval bar, and one per provider is hundreds of
|
|
28
|
+
dependencies nobody is tracking.
|
|
29
|
+
|
|
30
|
+
## Setting it up
|
|
31
|
+
|
|
32
|
+
Everything below is generated from `provider/manifest.json`, so it cannot disagree with what the packages do.
|
|
33
|
+
|
|
34
|
+
### Credentials
|
|
35
|
+
|
|
36
|
+
A Facebook Lead Ads connection holds 4 values.
|
|
37
|
+
|
|
38
|
+
**Two kinds of value, and mixing them up matters.** A `provider` credential is ONE value for the whole installation — an OAuth app's client secret serves every connected account. An `account` credential is one per connected account. A host that stores the second where it stores the first lets one account's credentials reach another's.
|
|
39
|
+
|
|
40
|
+
| Field | Scope | Secret | Where it comes from |
|
|
41
|
+
|---|---|---|---|
|
|
42
|
+
| **App ID** | per installation | not secret | From the Meta app dashboard. ONE value for the whole installation. |
|
|
43
|
+
| **App secret (OAuth)** | per installation | **secret** | The same app's client secret, used to exchange the authorization code. |
|
|
44
|
+
| **App secret (proof and webhook signature)** | per installation | **secret** | TWO roles, one value. It keys the appsecret_proof HMAC on outgoing calls, and it is also what Meta signs INCOMING webhook deliveries with -- there is no per-endpoint webhook secret in the Graph API the way there is in Stripe. It is never sent in either direction. For a standard Meta app this is the same value as the OAuth client secret; it is declared separately because they are different roles, and an app that rotates one without the other should not silently break the other. |
|
|
45
|
+
| **Page access token** | per connected account | **secret** | PER PAGE, not per user. Leads belong to a Page's forms, and Meta requires the token to be held by somebody who can advertise on the ad account. A long-lived page token lasts about 60 days and is re-authorised rather than refreshed. |
|
|
46
|
+
|
|
47
|
+
### Authorising
|
|
48
|
+
|
|
49
|
+
Facebook Lead Ads uses OAuth2 (authorization_code). The package DECLARES the exchange; the HOST performs it — a consent screen needs a browser, a redirect URI and somewhere to persist the result, and all three belong to the host.
|
|
50
|
+
|
|
51
|
+
- **Authorize URL** — https://www.facebook.com/v25.0/dialog/oauth
|
|
52
|
+
- **Token URL** — https://graph.facebook.com/v25.0/oauth/access_token
|
|
53
|
+
- **Scopes** — `leads_retrieval`, `pages_manage_ads`, `pages_show_list`, `pages_read_engagement`, `pages_manage_metadata`
|
|
54
|
+
- **Access token lifetime** — 5184000 seconds (60 days). A host that never refreshes works all afternoon and is broken by morning.
|
|
55
|
+
|
|
56
|
+
**This flow issues NO refresh token.** A connection is RE-AUTHORISED rather than refreshed when the access token expires — checked, not assumed.
|
|
57
|
+
|
|
58
|
+
### The estate
|
|
59
|
+
|
|
60
|
+
**Facebook Lead Ads has no test estate, and somebody checked.** Everything this connector does is real. Use the faker to build against it.
|
|
61
|
+
|
|
62
|
+
> Facebook Lead Ads has no sandbox. Meta's Lead Ads Testing Tool creates a test lead on the REAL form, which fires the real webhook and appears alongside real leads until somebody deletes it. Anything this reads is real customer data belonging to a real advertiser.
|
|
63
|
+
|
|
64
|
+
## What it can do
|
|
65
|
+
|
|
66
|
+
### Actions
|
|
67
|
+
|
|
68
|
+
#### `lead_get` — Facebook lead
|
|
69
|
+
|
|
70
|
+
Read one lead's answers by id. Pair this with the `leadgen` trigger, whose delivery carries only the id.
|
|
71
|
+
|
|
72
|
+
`GET /{leadId}` · reads only — safe to replay
|
|
73
|
+
|
|
74
|
+
| Input | Required | What it is |
|
|
75
|
+
|---|---|---|
|
|
76
|
+
| `leadId` | yes | From the `leadgen_id` on a `leadgen` delivery. It is not the form id and not the ad id -- both are also numeric, and passing one of those returns THAT object rather than an error. |
|
|
77
|
+
|
|
78
|
+
### Triggers
|
|
79
|
+
|
|
80
|
+
#### `leadgen` — Facebook lead
|
|
81
|
+
|
|
82
|
+
Start when somebody submits a Facebook lead form. The delivery carries only IDS -- pair this with the `lead_get` action to read the answers.
|
|
83
|
+
|
|
84
|
+
Delivered by webhook, and the signature is verified before anything runs.
|
|
85
|
+
|
|
86
|
+
**You have to set this up with the provider first:**
|
|
87
|
+
|
|
88
|
+
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.
|
|
89
|
+
|
|
90
|
+
## Run it before you have credentials
|
|
91
|
+
|
|
92
|
+
Every operation ships a **faker**, whether or not Facebook Lead Ads has a sandbox. Set a
|
|
93
|
+
node's mode to `fake` and it returns the shape Facebook Lead Ads actually publishes — the
|
|
94
|
+
same field names, deterministically — so you can wire the downstream nodes before
|
|
95
|
+
touching an account, a key, or a network.
|
|
96
|
+
|
|
97
|
+
## This repository is generated
|
|
98
|
+
|
|
99
|
+
`provider/` is the source. Everything under `packages/` is emitted from it and
|
|
100
|
+
**must not be hand-edited** — CI regenerates and diffs on every push, and the
|
|
101
|
+
next protocol sync destroys anything it finds. See [`AGENTS.md`](AGENTS.md).
|
|
102
|
+
|
|
103
|
+
## Two namespaces, which do not match on purpose
|
|
104
|
+
|
|
105
|
+
The repo is `github.com/Fancy-Friends/facebook-lead-ads`; the packages publish under
|
|
106
|
+
`particle-academy`. Nothing derives one from the other — the names come from
|
|
107
|
+
weaver's `friends.json` and nowhere else.
|
|
108
|
+
|
|
109
|
+
## Licence
|
|
110
|
+
|
|
111
|
+
MIT.
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { createHmac } from 'crypto';
|
|
2
|
+
import { callConnector, verifyDelivery } from '@particle-academy/fancy-connector-core';
|
|
3
|
+
|
|
4
|
+
// src/faker.ts
|
|
5
|
+
function fakeLeadGet({ config, fake }) {
|
|
6
|
+
return {
|
|
7
|
+
"id": Array.from({ length: 15 }, () => fake.int(0, 9)).join(""),
|
|
8
|
+
"created_time": "2026-08-24T09:15:00+0000",
|
|
9
|
+
"ad_id": Array.from({ length: 15 }, () => fake.int(0, 9)).join(""),
|
|
10
|
+
"form_id": Array.from({ length: 15 }, () => fake.int(0, 9)).join(""),
|
|
11
|
+
"field_data": [
|
|
12
|
+
{
|
|
13
|
+
"name": "full_name",
|
|
14
|
+
"values": [
|
|
15
|
+
"Ada Lovelace"
|
|
16
|
+
]
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"name": "email",
|
|
20
|
+
"values": [
|
|
21
|
+
"ada@example.test"
|
|
22
|
+
]
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"name": "phone_number",
|
|
26
|
+
"values": [
|
|
27
|
+
"+15550100"
|
|
28
|
+
]
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
"name": "what_are_you_interested_in",
|
|
32
|
+
"values": [
|
|
33
|
+
"Enterprise plan"
|
|
34
|
+
]
|
|
35
|
+
}
|
|
36
|
+
]
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
function fakeLeadgen({ config, fake }) {
|
|
40
|
+
const boundLeadid = Array.from({ length: 15 }, () => fake.int(0, 9)).join("");
|
|
41
|
+
return {
|
|
42
|
+
"leadgen_id": boundLeadid,
|
|
43
|
+
"page_id": Array.from({ length: 15 }, () => fake.int(0, 9)).join(""),
|
|
44
|
+
"form_id": Array.from({ length: 15 }, () => fake.int(0, 9)).join(""),
|
|
45
|
+
"ad_id": Array.from({ length: 15 }, () => fake.int(0, 9)).join(""),
|
|
46
|
+
"adgroup_id": Array.from({ length: 15 }, () => fake.int(0, 9)).join(""),
|
|
47
|
+
"created_time": 1787577300
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
var facebookLeadAdsFaker = (operation, request) => {
|
|
51
|
+
switch (operation) {
|
|
52
|
+
case "lead_get":
|
|
53
|
+
return fakeLeadGet(request);
|
|
54
|
+
case "leadgen":
|
|
55
|
+
return fakeLeadgen(request);
|
|
56
|
+
default:
|
|
57
|
+
throw new Error(
|
|
58
|
+
`facebook_lead_ads: no fake response is defined for "${operation}". Add a fixture under provider/fixtures/ and regenerate \u2014 a connector without a faker cannot be developed against, tested, or demonstrated.`
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
var CONNECTOR_API_VERSION = 1;
|
|
63
|
+
var FACEBOOK_LEAD_ADS_BASE_URLS = {
|
|
64
|
+
"live": "https://graph.facebook.com/v25.0"
|
|
65
|
+
};
|
|
66
|
+
var FACEBOOK_LEAD_ADS_REQUIRES = [
|
|
67
|
+
"pageAccessToken",
|
|
68
|
+
"appSecret",
|
|
69
|
+
"clientId",
|
|
70
|
+
"clientSecret"
|
|
71
|
+
];
|
|
72
|
+
function facebookLeadAdsAuthorize(credentials, request, _mode) {
|
|
73
|
+
const url = new URL(request.url);
|
|
74
|
+
url.searchParams.set("access_token", String(credentials.pageAccessToken ?? ""));
|
|
75
|
+
const proof = createHmac("sha256", String(credentials.appSecret ?? "")).update(String(credentials.pageAccessToken ?? "")).digest("hex");
|
|
76
|
+
url.searchParams.set("appsecret_proof", proof);
|
|
77
|
+
request.url = url.toString();
|
|
78
|
+
}
|
|
79
|
+
var FACEBOOK_LEAD_ADS = {
|
|
80
|
+
service: "facebook_lead_ads",
|
|
81
|
+
title: "Facebook Lead Ads",
|
|
82
|
+
sandbox: "none",
|
|
83
|
+
baseUrls: { ...FACEBOOK_LEAD_ADS_BASE_URLS },
|
|
84
|
+
requires: [...FACEBOOK_LEAD_ADS_REQUIRES],
|
|
85
|
+
authorize: facebookLeadAdsAuthorize,
|
|
86
|
+
faker: facebookLeadAdsFaker
|
|
87
|
+
};
|
|
88
|
+
var LEAD_GET_OPERATION = "lead_get";
|
|
89
|
+
async function facebookLeadAdsLeadGet(options) {
|
|
90
|
+
const config = options.config ?? {};
|
|
91
|
+
if (config.leadId === void 0 || config.leadId === null || config.leadId === "") {
|
|
92
|
+
throw new Error(`lead_get: "leadId" is required (Lead ID).`);
|
|
93
|
+
}
|
|
94
|
+
return callConnector(FACEBOOK_LEAD_ADS, {
|
|
95
|
+
operation: LEAD_GET_OPERATION,
|
|
96
|
+
config,
|
|
97
|
+
input: options.input,
|
|
98
|
+
...options.credentials === void 0 ? {} : { credentials: options.credentials },
|
|
99
|
+
...options.mode === void 0 ? {} : { mode: options.mode },
|
|
100
|
+
...options.connectionId === void 0 ? {} : { connectionId: options.connectionId },
|
|
101
|
+
...options.attempts === void 0 ? {} : { attempts: options.attempts },
|
|
102
|
+
...options.transport === void 0 ? {} : { transport: options.transport },
|
|
103
|
+
request: {
|
|
104
|
+
method: "GET",
|
|
105
|
+
path: `/${encodeURIComponent(String(config.leadId))}`,
|
|
106
|
+
query: {}
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
var FACEBOOK_LEAD_ADS_LEADGEN_SIGNATURE_HEADER = "X-Hub-Signature-256";
|
|
111
|
+
var FACEBOOK_LEAD_ADS_LEADGEN_SCHEME = {
|
|
112
|
+
algorithm: "SHA-256",
|
|
113
|
+
payload: (raw, timestamp) => raw,
|
|
114
|
+
encoding: "hex"
|
|
115
|
+
};
|
|
116
|
+
function parseFacebookLeadAdsSignature(raw) {
|
|
117
|
+
return { signature: raw.startsWith("sha256=") ? raw.slice(7) : raw };
|
|
118
|
+
}
|
|
119
|
+
var FACEBOOK_LEAD_ADS_LEADGEN = {
|
|
120
|
+
service: "facebook_lead_ads",
|
|
121
|
+
operation: "leadgen",
|
|
122
|
+
delivery: "webhook",
|
|
123
|
+
setup: "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.",
|
|
124
|
+
verification: {
|
|
125
|
+
signatureHeader: FACEBOOK_LEAD_ADS_LEADGEN_SIGNATURE_HEADER,
|
|
126
|
+
scheme: FACEBOOK_LEAD_ADS_LEADGEN_SCHEME,
|
|
127
|
+
parse: parseFacebookLeadAdsSignature
|
|
128
|
+
},
|
|
129
|
+
faker: facebookLeadAdsFaker
|
|
130
|
+
};
|
|
131
|
+
function verifyFacebookLeadAdsDelivery(delivery, appSecret, now) {
|
|
132
|
+
return verifyDelivery(FACEBOOK_LEAD_ADS_LEADGEN, delivery, appSecret, now);
|
|
133
|
+
}
|
|
134
|
+
|
|
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
|
|
@@ -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-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"]}
|
package/dist/flow.cjs
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var fancyConnectorCore = require('@particle-academy/fancy-connector-core');
|
|
4
|
+
var crypto = require('crypto');
|
|
5
|
+
var facebookLeadAdsUi = require('@particle-academy/facebook-lead-ads-ui');
|
|
6
|
+
|
|
7
|
+
// src/flow.ts
|
|
8
|
+
|
|
9
|
+
// src/faker.ts
|
|
10
|
+
function fakeLeadGet({ config, fake }) {
|
|
11
|
+
return {
|
|
12
|
+
"id": Array.from({ length: 15 }, () => fake.int(0, 9)).join(""),
|
|
13
|
+
"created_time": "2026-08-24T09:15:00+0000",
|
|
14
|
+
"ad_id": Array.from({ length: 15 }, () => fake.int(0, 9)).join(""),
|
|
15
|
+
"form_id": Array.from({ length: 15 }, () => fake.int(0, 9)).join(""),
|
|
16
|
+
"field_data": [
|
|
17
|
+
{
|
|
18
|
+
"name": "full_name",
|
|
19
|
+
"values": [
|
|
20
|
+
"Ada Lovelace"
|
|
21
|
+
]
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
"name": "email",
|
|
25
|
+
"values": [
|
|
26
|
+
"ada@example.test"
|
|
27
|
+
]
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
"name": "phone_number",
|
|
31
|
+
"values": [
|
|
32
|
+
"+15550100"
|
|
33
|
+
]
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
"name": "what_are_you_interested_in",
|
|
37
|
+
"values": [
|
|
38
|
+
"Enterprise plan"
|
|
39
|
+
]
|
|
40
|
+
}
|
|
41
|
+
]
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
function fakeLeadgen({ config, fake }) {
|
|
45
|
+
const boundLeadid = Array.from({ length: 15 }, () => fake.int(0, 9)).join("");
|
|
46
|
+
return {
|
|
47
|
+
"leadgen_id": boundLeadid,
|
|
48
|
+
"page_id": Array.from({ length: 15 }, () => fake.int(0, 9)).join(""),
|
|
49
|
+
"form_id": Array.from({ length: 15 }, () => fake.int(0, 9)).join(""),
|
|
50
|
+
"ad_id": Array.from({ length: 15 }, () => fake.int(0, 9)).join(""),
|
|
51
|
+
"adgroup_id": Array.from({ length: 15 }, () => fake.int(0, 9)).join(""),
|
|
52
|
+
"created_time": 1787577300
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
var facebookLeadAdsFaker = (operation, request) => {
|
|
56
|
+
switch (operation) {
|
|
57
|
+
case "lead_get":
|
|
58
|
+
return fakeLeadGet(request);
|
|
59
|
+
case "leadgen":
|
|
60
|
+
return fakeLeadgen(request);
|
|
61
|
+
default:
|
|
62
|
+
throw new Error(
|
|
63
|
+
`facebook_lead_ads: no fake response is defined for "${operation}". Add a fixture under provider/fixtures/ and regenerate \u2014 a connector without a faker cannot be developed against, tested, or demonstrated.`
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
var FACEBOOK_LEAD_ADS_BASE_URLS = {
|
|
68
|
+
"live": "https://graph.facebook.com/v25.0"
|
|
69
|
+
};
|
|
70
|
+
var FACEBOOK_LEAD_ADS_REQUIRES = [
|
|
71
|
+
"pageAccessToken",
|
|
72
|
+
"appSecret",
|
|
73
|
+
"clientId",
|
|
74
|
+
"clientSecret"
|
|
75
|
+
];
|
|
76
|
+
function facebookLeadAdsAuthorize(credentials, request, _mode) {
|
|
77
|
+
const url = new URL(request.url);
|
|
78
|
+
url.searchParams.set("access_token", String(credentials.pageAccessToken ?? ""));
|
|
79
|
+
const proof = crypto.createHmac("sha256", String(credentials.appSecret ?? "")).update(String(credentials.pageAccessToken ?? "")).digest("hex");
|
|
80
|
+
url.searchParams.set("appsecret_proof", proof);
|
|
81
|
+
request.url = url.toString();
|
|
82
|
+
}
|
|
83
|
+
var FACEBOOK_LEAD_ADS = {
|
|
84
|
+
service: "facebook_lead_ads",
|
|
85
|
+
title: "Facebook Lead Ads",
|
|
86
|
+
sandbox: "none",
|
|
87
|
+
baseUrls: { ...FACEBOOK_LEAD_ADS_BASE_URLS },
|
|
88
|
+
requires: [...FACEBOOK_LEAD_ADS_REQUIRES],
|
|
89
|
+
authorize: facebookLeadAdsAuthorize,
|
|
90
|
+
faker: facebookLeadAdsFaker
|
|
91
|
+
};
|
|
92
|
+
var LEAD_GET_OPERATION = "lead_get";
|
|
93
|
+
async function facebookLeadAdsLeadGet(options) {
|
|
94
|
+
const config = options.config ?? {};
|
|
95
|
+
if (config.leadId === void 0 || config.leadId === null || config.leadId === "") {
|
|
96
|
+
throw new Error(`lead_get: "leadId" is required (Lead ID).`);
|
|
97
|
+
}
|
|
98
|
+
return fancyConnectorCore.callConnector(FACEBOOK_LEAD_ADS, {
|
|
99
|
+
operation: LEAD_GET_OPERATION,
|
|
100
|
+
config,
|
|
101
|
+
input: options.input,
|
|
102
|
+
...options.credentials === void 0 ? {} : { credentials: options.credentials },
|
|
103
|
+
...options.mode === void 0 ? {} : { mode: options.mode },
|
|
104
|
+
...options.connectionId === void 0 ? {} : { connectionId: options.connectionId },
|
|
105
|
+
...options.attempts === void 0 ? {} : { attempts: options.attempts },
|
|
106
|
+
...options.transport === void 0 ? {} : { transport: options.transport },
|
|
107
|
+
request: {
|
|
108
|
+
method: "GET",
|
|
109
|
+
path: `/${encodeURIComponent(String(config.leadId))}`,
|
|
110
|
+
query: {}
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
var FACEBOOK_LEAD_ADS_LEADGEN_SIGNATURE_HEADER = "X-Hub-Signature-256";
|
|
115
|
+
var FACEBOOK_LEAD_ADS_LEADGEN_SCHEME = {
|
|
116
|
+
algorithm: "SHA-256",
|
|
117
|
+
payload: (raw, timestamp) => raw,
|
|
118
|
+
encoding: "hex"
|
|
119
|
+
};
|
|
120
|
+
function parseFacebookLeadAdsSignature(raw) {
|
|
121
|
+
return { signature: raw.startsWith("sha256=") ? raw.slice(7) : raw };
|
|
122
|
+
}
|
|
123
|
+
var FACEBOOK_LEAD_ADS_LEADGEN = {
|
|
124
|
+
service: "facebook_lead_ads",
|
|
125
|
+
operation: "leadgen",
|
|
126
|
+
delivery: "webhook",
|
|
127
|
+
setup: "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.",
|
|
128
|
+
verification: {
|
|
129
|
+
signatureHeader: FACEBOOK_LEAD_ADS_LEADGEN_SIGNATURE_HEADER,
|
|
130
|
+
scheme: FACEBOOK_LEAD_ADS_LEADGEN_SCHEME,
|
|
131
|
+
parse: parseFacebookLeadAdsSignature
|
|
132
|
+
},
|
|
133
|
+
faker: facebookLeadAdsFaker
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
// src/flow.ts
|
|
137
|
+
var facebookLeadAdsLeadExecutor = async (ctx) => {
|
|
138
|
+
const config = ctx.node.data?.config ?? {};
|
|
139
|
+
const result = await facebookLeadAdsLeadGet({
|
|
140
|
+
config,
|
|
141
|
+
input: ctx.inputs?.in
|
|
142
|
+
});
|
|
143
|
+
ctx.emit({
|
|
144
|
+
type: "log",
|
|
145
|
+
level: "info",
|
|
146
|
+
nodeId: ctx.node.id,
|
|
147
|
+
message: `facebook_lead_ads lead_get ${result.data?.id} (${result.mode})`
|
|
148
|
+
});
|
|
149
|
+
return { __port: "out", value: result };
|
|
150
|
+
};
|
|
151
|
+
var facebookLeadAdsWebhookTriggerExecutor = async (ctx) => {
|
|
152
|
+
const config = ctx.node.data?.config ?? {};
|
|
153
|
+
const connection = fancyConnectorCore.resolveConnection({
|
|
154
|
+
service: FACEBOOK_LEAD_ADS.service,
|
|
155
|
+
operation: "leadgen",
|
|
156
|
+
sandbox: FACEBOOK_LEAD_ADS.sandbox,
|
|
157
|
+
baseUrls: FACEBOOK_LEAD_ADS.baseUrls,
|
|
158
|
+
requires: FACEBOOK_LEAD_ADS.requires,
|
|
159
|
+
connectionId: typeof config.connection === "string" ? config.connection : null,
|
|
160
|
+
requested: typeof config.mode === "string" ? config.mode : null
|
|
161
|
+
});
|
|
162
|
+
const event = fancyConnectorCore.triggerEvent(FACEBOOK_LEAD_ADS_LEADGEN, connection, ctx.inputs?.in, config);
|
|
163
|
+
return { __port: "out", value: event };
|
|
164
|
+
};
|
|
165
|
+
var FACEBOOK_LEAD_ADS_RUNNABLE_KINDS = [
|
|
166
|
+
{ ...facebookLeadAdsUi.facebookLeadAdsLeadKind, executor: facebookLeadAdsLeadExecutor },
|
|
167
|
+
{ ...facebookLeadAdsUi.facebookLeadAdsWebhookTriggerKind, executor: facebookLeadAdsWebhookTriggerExecutor }
|
|
168
|
+
];
|
|
169
|
+
|
|
170
|
+
exports.FACEBOOK_LEAD_ADS_RUNNABLE_KINDS = FACEBOOK_LEAD_ADS_RUNNABLE_KINDS;
|
|
171
|
+
exports.facebookLeadAdsLeadExecutor = facebookLeadAdsLeadExecutor;
|
|
172
|
+
exports.facebookLeadAdsWebhookTriggerExecutor = facebookLeadAdsWebhookTriggerExecutor;
|
|
173
|
+
//# sourceMappingURL=flow.cjs.map
|
|
174
|
+
//# sourceMappingURL=flow.cjs.map
|
|
@@ -0,0 +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"]}
|
package/dist/flow.d.cts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { NodeKindDefinition, NodeExecutor } from '@particle-academy/fancy-flow/engine';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* GENERATED FILE — do not edit.
|
|
5
|
+
*
|
|
6
|
+
* Emitted from provider/actions/ + triggers/ by weaver's generator.
|
|
7
|
+
* A hand-edit here is destroyed by the next protocol sync, which is worse than
|
|
8
|
+
* being rejected, because it works until it silently does not. Fix
|
|
9
|
+
* provider/actions/ + triggers/ (or weaver's template/) and regenerate:
|
|
10
|
+
*
|
|
11
|
+
* npm run provider -- facebook_lead_ads
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* Facebook Lead Ads's node kinds with their TypeScript executors attached —
|
|
15
|
+
* for hosts that EXECUTE on TS.
|
|
16
|
+
*
|
|
17
|
+
* The authoring surface in @particle-academy/facebook-lead-ads-ui carries no
|
|
18
|
+
* executor: the editor is React on every host, so a PHP or Python project
|
|
19
|
+
* installs the ui package and never this one.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
declare const facebookLeadAdsLeadExecutor: NodeExecutor;
|
|
23
|
+
declare const facebookLeadAdsWebhookTriggerExecutor: NodeExecutor;
|
|
24
|
+
/** The kinds a TypeScript host registers. */
|
|
25
|
+
declare const FACEBOOK_LEAD_ADS_RUNNABLE_KINDS: NodeKindDefinition[];
|
|
26
|
+
|
|
27
|
+
export { FACEBOOK_LEAD_ADS_RUNNABLE_KINDS, facebookLeadAdsLeadExecutor, facebookLeadAdsWebhookTriggerExecutor };
|