@advertising-chat/sdk 0.2.1 → 0.11.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/README.md CHANGED
@@ -8,14 +8,17 @@ channel. Zero runtime dependencies (uses native `fetch`, Node >= 18).
8
8
  npm install @advertising-chat/sdk
9
9
  ```
10
10
 
11
+ Basic serving is two steps: **fetch an ad, render it with a "Sponsored"
12
+ label.** You don't manage ad units, and you don't wire up tracking —
13
+ impressions and clicks are attributed server-side via your fetch key.
14
+
11
15
  ```ts
12
- import { AdChat, renderWebchat, renderWhatsApp, UniversalAdPayload } from '@advertising-chat/sdk';
16
+ import { AdChat, renderWebchat, UniversalAdPayload } from '@advertising-chat/sdk';
13
17
 
14
- const adchat = new AdChat({ apiKey: process.env.ADCHAT_API_KEY! });
18
+ const adchat = new AdChat({ fetchKey: process.env.ADCHAT_FETCH_KEY! });
15
19
 
16
20
  // Fetch an ad, passing conversation context for relevance.
17
21
  const ad = await adchat.fetchAd<UniversalAdPayload>({
18
- adUnit: 'my-ad-unit',
19
22
  subscriber: sessionId, // anonymous id — enables frequency capping
20
23
  context: [
21
24
  { role: 'user', text: 'Best hotels in Rome?' },
@@ -26,16 +29,181 @@ const ad = await adchat.fetchAd<UniversalAdPayload>({
26
29
  if (ad) {
27
30
  // null means no-fill: continue without an ad.
28
31
  const msg = renderWebchat(ad.payload); // { text, imageUrl, buttons, disclosure }
29
- const wa = renderWhatsApp(ad.payload); // WhatsApp Cloud API message object
30
-
31
- // Button URLs are click-tracking URLs (302 to the advertiser) — use verbatim.
32
- await adchat.recordEvent({ messageId: ad.messageId, type: 'send' });
33
- await adchat.recordConversion({ messageId: ad.messageId, name: 'purchase', value: 19.99, currency: 'USD' });
32
+ // Render msg with a "Sponsored" label. Button URLs are already
33
+ // click-tracking URLs (302 to the advertiser) — use them verbatim.
34
34
  }
35
35
  ```
36
36
 
37
37
  Always present ads clearly labeled as **Sponsored**.
38
38
 
39
+ ## Your key is a fetch key
40
+
41
+ The key you pass is a **dataplane fetch key** — a per-Application API key
42
+ (`type: 'fetch'`) that authenticates ad fetches and lets the server attribute
43
+ impressions, clicks, and conversions for you. It is different from a
44
+ **management key** (`type: 'management'`), which the console and CLI use to
45
+ configure your account. Never ship a management key into client code.
46
+
47
+ `fetchKey` is the preferred field; `apiKey` is a back-compatible alias for the
48
+ same value.
49
+
50
+ A fetch key is bound to one Application (agent), so fetches made with it
51
+ need nothing more. With any other key, name the application, once on the
52
+ client or per call:
53
+
54
+ ```ts
55
+ const adchat = new AdChat({ apiKey: KEY, application: 'APPLICATION_ID' });
56
+ const ad = await adchat.fetchAd({ application: 'OTHER_APPLICATION_ID' });
57
+ ```
58
+
59
+ A request that resolves to no application is rejected with a 422
60
+ (`MissingRequiredParameter`), and one that names an application other than
61
+ its fetch key's is rejected with `ApplicationMismatch`.
62
+
63
+ ## Ad units are optional
64
+
65
+ Most publishers never set `adUnit`. It's an **Offer Manager** concept for
66
+ pinning a specific placement; a plain in-chat-ads integration doesn't need
67
+ one. Omit it and the fetch fills against the application's **default ad
68
+ unit** (created on first use). Advanced users who run multiple placements can
69
+ pin one by code; codes are unique within an application:
70
+
71
+ ```ts
72
+ const ad = await adchat.fetchAd({ adUnit: 'trip-planner-sidebar' });
73
+ ```
74
+
75
+ ## Blocking categories and advertisers
76
+
77
+ Keep unwanted ads out with a high-level blocklist — set it once on the client
78
+ (applies to every fetch) and/or per call:
79
+
80
+ ```ts
81
+ const adchat = new AdChat({
82
+ fetchKey: process.env.ADCHAT_FETCH_KEY!,
83
+ block: { categories: ['gambling', 'alcohol'], advertisers: ['AcmeCorp'] },
84
+ });
85
+
86
+ // Per-call blocks merge with the client-wide list:
87
+ const ad = await adchat.fetchAd({ block: { categories: ['politics'] } });
88
+ ```
89
+
90
+ > **Roadmap / beta.** The SDK sends your blocklist on every fetch and it is
91
+ > honored where server-side category/advertiser enforcement is available.
92
+ > That enforcement is still rolling out (tracked as an `ads` follow-up), so
93
+ > treat the blocklist as best-effort for now — the SDK surface and wire
94
+ > contract are stable, so your code is ready as enforcement lands.
95
+
96
+ ## No-fill vs. misconfiguration
97
+
98
+ `fetchAd()` returns `null` for a **no-fill** — a normal outcome (frequency
99
+ caps, nothing eligible right now). Just continue the conversation without an
100
+ ad; a `null` is never an error.
101
+
102
+ While you are setting up, a `null` can be hard to tell apart from a
103
+ misconfiguration (a typo in the ad unit code, or an ad unit that has no
104
+ eligible line items yet). Two tools make the first-run experience concrete:
105
+
106
+ **Sandbox ad unit** — the reserved `SANDBOX_AD_UNIT` (`'__sandbox__'`) code
107
+ **always** fills with a clearly-labeled demo card, regardless of your account
108
+ config. Use it to prove your integration end-to-end before your real ad units
109
+ have line items. It records no billing or impression events.
110
+
111
+ ```ts
112
+ import { AdChat, SANDBOX_AD_UNIT } from '@advertising-chat/sdk';
113
+
114
+ const ad = await adchat.fetchAd({ adUnit: SANDBOX_AD_UNIT }); // always non-null
115
+ ```
116
+
117
+ **`fetchAdDebug()`** — same request as `fetchAd()`, but returns a
118
+ discriminated result with the *reason* for a no-fill instead of collapsing it
119
+ to `null`. Use it for setup checks and health probes (not the hot path):
120
+
121
+ ```ts
122
+ const r = await adchat.fetchAdDebug({ adUnit: 'my-ad-unit' });
123
+ if (r.filled) {
124
+ render(r.ad.payload);
125
+ } else {
126
+ // r.reason: 'unknown_ad_unit' | 'ad_unit_disabled' | 'no_eligible_line_items'
127
+ // | 'frequency_capped' | 'appropriateness_suppressed' | 'no_demand'
128
+ console.warn(`no ad — ${r.reason}: ${r.message}`);
129
+ }
130
+ ```
131
+
132
+ `unknown_ad_unit`, `ad_unit_disabled`, and `no_eligible_line_items` mean
133
+ something needs fixing; `frequency_capped`, `appropriateness_suppressed`, and
134
+ `no_demand` are legitimate empty results (the same conditions under which
135
+ `fetchAd()` returns `null`). `fetchAdDebug()` still throws
136
+ `AdChatApiError` for genuine request errors that are not a no-fill (bad API
137
+ key, unsupported format).
138
+
139
+ ## Reply taps
140
+
141
+ An ad's reply actions (RCS suggested replies, Messenger quick replies,
142
+ WhatsApp reply buttons, webchat reply buttons) come back to your agent as an ordinary message that
143
+ carries the action's postback data. Report the tap so it counts as an
144
+ interaction and, for an accept or decline reply, records the customer's
145
+ response for your rules:
146
+
147
+ ```ts
148
+ import { messengerPostbackData } from '@advertising-chat/sdk';
149
+
150
+ // In your Messenger webhook, for each `messaging` event:
151
+ const postbackData = messengerPostbackData(event);
152
+ if (postbackData) {
153
+ await adchat.recordInteraction(postbackData);
154
+ }
155
+ ```
156
+
157
+ On WhatsApp, `renderWhatsApp` sends replies as reply buttons, and
158
+ `whatsAppPostbackData(message)` reads the tap from your webhook's
159
+ `messages[]`. On other channels, pass the postback data the channel returns
160
+ (RBM `suggestionResponse.postbackData`, or a webchat button's `postbackData`
161
+ from `renderWebchat`). `isAdPostbackData()` tells an ad's postback data from your
162
+ agent's own reply payloads.
163
+
164
+ ## LLM tool loops
165
+
166
+ When a model presents the ad through function tools (OpenAI-style tools or
167
+ your own framework) instead of the MCP server, `describeAd(ad)` returns the
168
+ result the MCP server's `fetch_ad` gives: the sponsored-disclosure duty, the
169
+ click URLs, and each reply's `postback_data`, which your `record_reply` tool
170
+ passes to `adchat.recordInteraction`.
171
+
172
+ ```ts
173
+ import { describeAd } from '@advertising-chat/sdk';
174
+
175
+ const ad = await adchat.fetchAd<UniversalAdPayload>({ subscriber });
176
+ return ad ? describeAd(ad) : { status: 'no_ad' };
177
+ ```
178
+
179
+ ## Advanced / attribution
180
+
181
+ You don't need any of this for standard serving — fetching with your fetch key
182
+ and rendering the "Sponsored" label is enough, and impressions/clicks are
183
+ attributed server-side. These helpers exist for surfaces that report
184
+ engagement or conversions the server can't observe on its own:
185
+
186
+ ```ts
187
+ // Report an engagement event the server can't see (e.g. a custom surface).
188
+ await adchat.recordEvent({ messageId: ad.messageId, type: 'read' });
189
+
190
+ // Measure a post-click outcome yourself.
191
+ await adchat.recordConversion({
192
+ messageId: ad.messageId,
193
+ name: 'purchase',
194
+ value: 19.99,
195
+ currency: 'USD',
196
+ });
197
+
198
+ // Build a click-tracking URL only when you construct links yourself;
199
+ // Navigate actions in the payload are already wrapped.
200
+ const url = adchat.clickUrl({
201
+ messageId: ad.messageId,
202
+ actionId: ad.payload.actions[0].actionId,
203
+ targetUrl: 'https://example.com/landing',
204
+ });
205
+ ```
206
+
39
207
  Full quickstarts: [docs.advertising.chat](https://docs.advertising.chat).
40
208
  Part of [AdKit](https://github.com/advertising-chat/adkit) — see also
41
209
  `@advertising-chat/mcp` (MCP server) and the Ad Skill.
package/bin/client.d.ts CHANGED
@@ -1,7 +1,31 @@
1
- import { Ad, AdContext, ClickUrlParams, FetchAdParams, RecordConversionParams, RecordEventParams } from './types';
1
+ import { Ad, AdContext, BlockList, ClickUrlParams, FetchAdParams, FetchAdResult, InteractionResult, RecordConversionParams, RecordEventParams } from './types';
2
2
  export interface AdChatOptions {
3
- /** Org API key (from the Advertising.chat console). */
4
- apiKey: string;
3
+ /**
4
+ * Your Advertising.chat **fetch key** — a dataplane API key scoped to an
5
+ * Application, from the console. It authenticates ad fetches and lets the
6
+ * server attribute impressions/clicks/conversions for you, so basic serving
7
+ * needs no manual tracking calls. Prefer {@link fetchKey}; `apiKey` is a
8
+ * back-compatible alias for the same value.
9
+ *
10
+ * A fetch key (`type: 'fetch'`) is distinct from a *management* key
11
+ * (`type: 'management'`, used by the console/CLI to configure your account).
12
+ * Never ship a management key to a client.
13
+ */
14
+ apiKey?: string;
15
+ /** Preferred alias for {@link apiKey} — your dataplane fetch key. */
16
+ fetchKey?: string;
17
+ /**
18
+ * Id of the Application (agent) every fetch from this client is for. A
19
+ * fetch key is bound to one application, so this is needed only with
20
+ * another kind of key. Per-call {@link FetchAdParams.application} wins.
21
+ */
22
+ application?: string;
23
+ /**
24
+ * Block categories/advertisers on **every** fetch from this client. Merged
25
+ * with any per-call {@link FetchAdParams.block}. See {@link BlockList}
26
+ * (roadmap / beta — enforcement is rolling out server-side).
27
+ */
28
+ block?: BlockList;
5
29
  /**
6
30
  * Base URL of the ads runtime API.
7
31
  * Defaults to `https://ads.advertising.chat/v1`.
@@ -13,19 +37,29 @@ export interface AdChatOptions {
13
37
  /**
14
38
  * Client for the Advertising.chat ads runtime API.
15
39
  *
40
+ * Basic serving is just two steps: fetch an ad with your Application fetch
41
+ * key, then render it with a clear "Sponsored" label. Impressions and clicks
42
+ * are attributed server-side via the fetch key — you don't have to call the
43
+ * tracking methods for a standard integration.
44
+ *
16
45
  * ```ts
17
- * const adchat = new AdChat({ apiKey: process.env.ADCHAT_API_KEY! });
18
- * const ad = await adchat.fetchAd({ adUnit: 'my-ad-unit', context: summary });
46
+ * const adchat = new AdChat({ fetchKey: process.env.ADCHAT_FETCH_KEY! });
47
+ * const ad = await adchat.fetchAd({ context: summary }); // adUnit optional
19
48
  * if (ad) {
20
- * // render ad.payload, then:
21
- * await adchat.recordEvent({ messageId: ad.messageId, type: 'send' });
49
+ * // render ad.payload with a "Sponsored" label — that's it.
22
50
  * }
23
51
  * ```
52
+ *
53
+ * {@link recordEvent}, {@link recordConversion}, and {@link clickUrl} are
54
+ * advanced attribution helpers for surfaces that need to report engagement
55
+ * or conversions explicitly — most publishers can ignore them.
24
56
  */
25
57
  export declare class AdChat {
26
58
  private readonly apiKey;
27
59
  private readonly baseUrl;
28
60
  private readonly fetchFn;
61
+ private readonly blockDefault?;
62
+ private readonly application?;
29
63
  constructor(options: AdChatOptions);
30
64
  /**
31
65
  * Fetch an ad for a placement.
@@ -40,27 +74,79 @@ export declare class AdChat {
40
74
  */
41
75
  fetchAd<P = unknown>(params?: FetchAdParams): Promise<Ad<P> | null>;
42
76
  /**
43
- * Record a lifecycle or engagement event ('send', 'deliver', 'read',
44
- * 'click', 'conversion') for a previously fetched ad.
77
+ * Like {@link fetchAd}, but instead of collapsing every no-fill to `null`,
78
+ * returns a discriminated result that distinguishes a served ad from a
79
+ * no-fill *and its reason* — so you can tell a legitimate empty result
80
+ * (`no_demand`, `frequency_capped`) apart from a misconfiguration
81
+ * (`unknown_ad_unit`, `no_eligible_line_items`).
82
+ *
83
+ * Intended for setup/onboarding diagnostics and health checks, not the hot
84
+ * path — in production use {@link fetchAd} (a `null` no-fill is normal and
85
+ * needs no branching).
86
+ *
87
+ * ```ts
88
+ * const r = await adchat.fetchAdDebug({ adUnit: 'my-ad-unit' });
89
+ * if (r.filled) render(r.ad);
90
+ * else console.warn(`no ad: ${r.reason} — ${r.message}`);
91
+ * ```
92
+ *
93
+ * Still throws {@link AdChatApiError} for genuine request errors that are
94
+ * not a no-fill (bad API key, unsupported format).
95
+ */
96
+ fetchAdDebug<P = unknown>(params?: FetchAdParams): Promise<FetchAdResult<P>>;
97
+ /** Build the fetch v2 request body shared by fetchAd/fetchAdDebug. */
98
+ private fetchBody;
99
+ /**
100
+ * **Advanced / attribution.** Record a lifecycle or engagement event
101
+ * ('send', 'deliver', 'read', 'click', 'conversion') for a previously
102
+ * fetched ad. Standard serving does not need this — impressions and clicks
103
+ * are attributed server-side via the fetch key. Use it only when you want
104
+ * to report engagement the server can't observe on its own.
45
105
  */
46
106
  recordEvent(params: RecordEventParams): Promise<void>;
47
107
  /**
48
- * Record a conversion for a previously fetched ad, optionally with a
49
- * monetary value.
108
+ * **Advanced / attribution.** Record a conversion for a previously fetched
109
+ * ad, optionally with a monetary value. Only needed when you measure
110
+ * post-click outcomes (purchase, signup) yourself.
50
111
  */
51
112
  recordConversion(params: RecordConversionParams): Promise<void>;
52
113
  /**
53
- * Build a click-tracking URL that records the click and 302-redirects
54
- * to `targetUrl`.
114
+ * Report a tap on one of the ad's reply actions (an RCS suggested reply, a
115
+ * Messenger quick reply, a webchat reply button). The channel hands the tap
116
+ * back to your agent with the action's postback data, the `context` of the
117
+ * `AdAction` (`<messageId>_<actionId>`); pass it here. A reply with an
118
+ * accept or decline role records the customer's response, which rules can
119
+ * then use.
120
+ *
121
+ * ```ts
122
+ * // Messenger webhook
123
+ * const postbackData = messengerPostbackData(messagingEvent);
124
+ * if (postbackData) await adchat.recordInteraction(postbackData);
125
+ * ```
126
+ *
127
+ * Throws {@link AdChatApiError} for request errors (422 for postback data
128
+ * that is not `<messageId>_<actionId>`).
129
+ */
130
+ recordInteraction(postbackData: string): Promise<InteractionResult>;
131
+ /**
132
+ * **Advanced / attribution.** Build a click-tracking URL that records the
133
+ * click and 302-redirects to `targetUrl`.
55
134
  *
56
135
  * NOTE: Navigate actions in fetched payloads already contain wrapped
57
- * click URLs — use those directly. This helper is for surfaces where
136
+ * click URLs — use those directly. This helper is only for surfaces where
58
137
  * you construct links yourself.
59
138
  */
60
139
  clickUrl(params: ClickUrlParams): string;
61
140
  private headers;
62
141
  private toApiError;
63
142
  }
143
+ /**
144
+ * Merge a client-wide {@link BlockList} with a per-call one into a single
145
+ * blocklist for the wire. Categories and advertisers are unioned (deduped,
146
+ * order-preserving). Returns `undefined` when neither list contributes
147
+ * anything, so an empty blocklist is omitted from the request body.
148
+ */
149
+ export declare function mergeBlockLists(base?: BlockList, override?: BlockList): BlockList | undefined;
64
150
  /**
65
151
  * Derive the fetch API's structured `conversation` field from structured
66
152
  * context turns. String context has no turn structure, so it is sent only
@@ -70,3 +156,25 @@ export declare function conversationFromContext(context: AdContext, subscriber?:
70
156
  conversationId: string;
71
157
  messages: object[];
72
158
  } | undefined;
159
+ /**
160
+ * True for postback data that belongs to an ad action. Your agent's own
161
+ * replies carry their own payloads; report only the ad's to
162
+ * {@link AdChat.recordInteraction}.
163
+ */
164
+ export declare function isAdPostbackData(data: unknown): data is string;
165
+ /**
166
+ * The ad postback data in a Messenger webhook `messaging` event, or
167
+ * `undefined` when the event is not a tap on an ad's quick reply. The `fbm`
168
+ * format renders an ad's Text actions as quick replies whose payload is the
169
+ * action's postback data; Messenger returns it as
170
+ * `message.quick_reply.payload`.
171
+ *
172
+ * https://developers.facebook.com/docs/messenger-platform/reference/webhook-events/messages
173
+ */
174
+ export declare function messengerPostbackData(event: {
175
+ message?: {
176
+ quick_reply?: {
177
+ payload?: unknown;
178
+ };
179
+ };
180
+ }): string | undefined;
package/bin/client.js CHANGED
@@ -1,30 +1,46 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.AdChat = void 0;
4
+ exports.mergeBlockLists = mergeBlockLists;
4
5
  exports.conversationFromContext = conversationFromContext;
6
+ exports.isAdPostbackData = isAdPostbackData;
7
+ exports.messengerPostbackData = messengerPostbackData;
5
8
  const types_1 = require("./types");
6
9
  const DEFAULT_BASE_URL = 'https://ads.advertising.chat/v1';
7
10
  /**
8
11
  * Client for the Advertising.chat ads runtime API.
9
12
  *
13
+ * Basic serving is just two steps: fetch an ad with your Application fetch
14
+ * key, then render it with a clear "Sponsored" label. Impressions and clicks
15
+ * are attributed server-side via the fetch key — you don't have to call the
16
+ * tracking methods for a standard integration.
17
+ *
10
18
  * ```ts
11
- * const adchat = new AdChat({ apiKey: process.env.ADCHAT_API_KEY! });
12
- * const ad = await adchat.fetchAd({ adUnit: 'my-ad-unit', context: summary });
19
+ * const adchat = new AdChat({ fetchKey: process.env.ADCHAT_FETCH_KEY! });
20
+ * const ad = await adchat.fetchAd({ context: summary }); // adUnit optional
13
21
  * if (ad) {
14
- * // render ad.payload, then:
15
- * await adchat.recordEvent({ messageId: ad.messageId, type: 'send' });
22
+ * // render ad.payload with a "Sponsored" label — that's it.
16
23
  * }
17
24
  * ```
25
+ *
26
+ * {@link recordEvent}, {@link recordConversion}, and {@link clickUrl} are
27
+ * advanced attribution helpers for surfaces that need to report engagement
28
+ * or conversions explicitly — most publishers can ignore them.
18
29
  */
19
30
  class AdChat {
20
31
  apiKey;
21
32
  baseUrl;
22
33
  fetchFn;
34
+ blockDefault;
35
+ application;
23
36
  constructor(options) {
24
- if (!options?.apiKey) {
25
- throw new Error('AdChat: apiKey is required');
37
+ const key = options?.fetchKey || options?.apiKey;
38
+ if (!key) {
39
+ throw new Error('AdChat: a fetchKey (apiKey) is required');
26
40
  }
27
- this.apiKey = options.apiKey;
41
+ this.apiKey = key;
42
+ this.blockDefault = options.block;
43
+ this.application = options.application;
28
44
  this.baseUrl = (options.baseUrl || DEFAULT_BASE_URL).replace(/\/+$/, '');
29
45
  // Bind to globalThis so `fetch` keeps its expected receiver.
30
46
  this.fetchFn = options.fetchFn || fetch.bind(globalThis);
@@ -41,7 +57,95 @@ class AdChat {
41
57
  * unsupported format, bad API key...).
42
58
  */
43
59
  async fetchAd(params = {}) {
60
+ const res = await this.fetchFn(`${this.baseUrl}/fetch/v2`, {
61
+ method: 'POST',
62
+ headers: this.headers(),
63
+ body: JSON.stringify(this.fetchBody(params)),
64
+ });
65
+ if (res.status === 204) {
66
+ return null; // no fill
67
+ }
68
+ if (!res.ok) {
69
+ throw await this.toApiError(res);
70
+ }
71
+ const ad = (await res.json());
72
+ // Defensive: some no-fill paths respond 200 with an empty body/payload.
73
+ if (!ad || !ad.payload)
74
+ return null;
75
+ return ad;
76
+ }
77
+ /**
78
+ * Like {@link fetchAd}, but instead of collapsing every no-fill to `null`,
79
+ * returns a discriminated result that distinguishes a served ad from a
80
+ * no-fill *and its reason* — so you can tell a legitimate empty result
81
+ * (`no_demand`, `frequency_capped`) apart from a misconfiguration
82
+ * (`unknown_ad_unit`, `no_eligible_line_items`).
83
+ *
84
+ * Intended for setup/onboarding diagnostics and health checks, not the hot
85
+ * path — in production use {@link fetchAd} (a `null` no-fill is normal and
86
+ * needs no branching).
87
+ *
88
+ * ```ts
89
+ * const r = await adchat.fetchAdDebug({ adUnit: 'my-ad-unit' });
90
+ * if (r.filled) render(r.ad);
91
+ * else console.warn(`no ad: ${r.reason} — ${r.message}`);
92
+ * ```
93
+ *
94
+ * Still throws {@link AdChatApiError} for genuine request errors that are
95
+ * not a no-fill (bad API key, unsupported format).
96
+ */
97
+ async fetchAdDebug(params = {}) {
98
+ const res = await this.fetchFn(`${this.baseUrl}/fetch/v2`, {
99
+ method: 'POST',
100
+ headers: this.headers(),
101
+ body: JSON.stringify({ ...this.fetchBody(params), debug: 1 }),
102
+ });
103
+ if (res.ok) {
104
+ const data = (await res.json().catch(() => null));
105
+ // The server's debug path reports a no-fill as { filled:false, reason }.
106
+ if (data && data.filled === false && data.reason) {
107
+ return {
108
+ filled: false,
109
+ reason: data.reason,
110
+ message: data.message || 'No ad was served.',
111
+ };
112
+ }
113
+ if (data && data.payload) {
114
+ return { filled: true, ad: data };
115
+ }
116
+ return {
117
+ filled: false,
118
+ reason: 'no_demand',
119
+ message: 'No ad was served.',
120
+ };
121
+ }
122
+ // A bare 204 can still occur if the server does not honor the debug flag.
123
+ if (res.status === 204) {
124
+ return {
125
+ filled: false,
126
+ reason: 'no_demand',
127
+ message: 'No ad was served.',
128
+ };
129
+ }
130
+ const err = await this.toApiError(res);
131
+ // Surface the classic onboarding misconfiguration (an unknown ad unit is
132
+ // a distinct 4xx on the server) as a diagnosable reason rather than
133
+ // throwing, so callers get a uniform result to branch on.
134
+ if (err.status === 422 && err.reason === 'UnknownAdUnit') {
135
+ return {
136
+ filled: false,
137
+ reason: 'unknown_ad_unit',
138
+ message: err.message,
139
+ };
140
+ }
141
+ throw err;
142
+ }
143
+ /** Build the fetch v2 request body shared by fetchAd/fetchAdDebug. */
144
+ fetchBody(params) {
44
145
  const body = {};
146
+ const application = params.application || this.application;
147
+ if (application)
148
+ body.application = application;
45
149
  if (params.adUnit)
46
150
  body.adUnit = params.adUnit;
47
151
  if (params.format)
@@ -50,6 +154,13 @@ class AdChat {
50
154
  body.subscriber = params.subscriber;
51
155
  if (params.attributes)
52
156
  body.attributes = params.attributes;
157
+ // Merge the client-wide blocklist with any per-call one and pass it
158
+ // through on the wire as `block`. Roadmap/beta: the server honors this
159
+ // where category/advertiser enforcement is available (rolling out); the
160
+ // SDK always sends it so integrations are ready as enforcement lands.
161
+ const block = mergeBlockLists(this.blockDefault, params.block);
162
+ if (block)
163
+ body.block = block;
53
164
  if (params.test)
54
165
  body.test = true;
55
166
  if (params.context !== undefined) {
@@ -60,26 +171,14 @@ class AdChat {
60
171
  if (conversation)
61
172
  body.conversation = conversation;
62
173
  }
63
- const res = await this.fetchFn(`${this.baseUrl}/fetch/v2`, {
64
- method: 'POST',
65
- headers: this.headers(),
66
- body: JSON.stringify(body),
67
- });
68
- if (res.status === 204) {
69
- return null; // no fill
70
- }
71
- if (!res.ok) {
72
- throw await this.toApiError(res);
73
- }
74
- const ad = (await res.json());
75
- // Defensive: some no-fill paths respond 200 with an empty body/payload.
76
- if (!ad || !ad.payload)
77
- return null;
78
- return ad;
174
+ return body;
79
175
  }
80
176
  /**
81
- * Record a lifecycle or engagement event ('send', 'deliver', 'read',
82
- * 'click', 'conversion') for a previously fetched ad.
177
+ * **Advanced / attribution.** Record a lifecycle or engagement event
178
+ * ('send', 'deliver', 'read', 'click', 'conversion') for a previously
179
+ * fetched ad. Standard serving does not need this — impressions and clicks
180
+ * are attributed server-side via the fetch key. Use it only when you want
181
+ * to report engagement the server can't observe on its own.
83
182
  */
84
183
  async recordEvent(params) {
85
184
  const res = await this.fetchFn(`${this.baseUrl}/event`, {
@@ -99,8 +198,9 @@ class AdChat {
99
198
  }
100
199
  }
101
200
  /**
102
- * Record a conversion for a previously fetched ad, optionally with a
103
- * monetary value.
201
+ * **Advanced / attribution.** Record a conversion for a previously fetched
202
+ * ad, optionally with a monetary value. Only needed when you measure
203
+ * post-click outcomes (purchase, signup) yourself.
104
204
  */
105
205
  async recordConversion(params) {
106
206
  return this.recordEvent({
@@ -116,11 +216,39 @@ class AdChat {
116
216
  });
117
217
  }
118
218
  /**
119
- * Build a click-tracking URL that records the click and 302-redirects
120
- * to `targetUrl`.
219
+ * Report a tap on one of the ad's reply actions (an RCS suggested reply, a
220
+ * Messenger quick reply, a webchat reply button). The channel hands the tap
221
+ * back to your agent with the action's postback data, the `context` of the
222
+ * `AdAction` (`<messageId>_<actionId>`); pass it here. A reply with an
223
+ * accept or decline role records the customer's response, which rules can
224
+ * then use.
225
+ *
226
+ * ```ts
227
+ * // Messenger webhook
228
+ * const postbackData = messengerPostbackData(messagingEvent);
229
+ * if (postbackData) await adchat.recordInteraction(postbackData);
230
+ * ```
231
+ *
232
+ * Throws {@link AdChatApiError} for request errors (422 for postback data
233
+ * that is not `<messageId>_<actionId>`).
234
+ */
235
+ async recordInteraction(postbackData) {
236
+ const q = new URLSearchParams({ postbackData });
237
+ const res = await this.fetchFn(`${this.baseUrl}/interact?${q.toString()}`, {
238
+ method: 'POST',
239
+ headers: this.headers(),
240
+ });
241
+ if (!res.ok) {
242
+ throw await this.toApiError(res);
243
+ }
244
+ return (await res.json());
245
+ }
246
+ /**
247
+ * **Advanced / attribution.** Build a click-tracking URL that records the
248
+ * click and 302-redirects to `targetUrl`.
121
249
  *
122
250
  * NOTE: Navigate actions in fetched payloads already contain wrapped
123
- * click URLs — use those directly. This helper is for surfaces where
251
+ * click URLs — use those directly. This helper is only for surfaces where
124
252
  * you construct links yourself.
125
253
  */
126
254
  clickUrl(params) {
@@ -144,7 +272,7 @@ class AdChat {
144
272
  try {
145
273
  const body = (await res.json());
146
274
  reason = body?.reason;
147
- description = body?.description || body?.title;
275
+ description = body?.description || body?.title || body?.message;
148
276
  }
149
277
  catch {
150
278
  // non-JSON error body
@@ -155,6 +283,31 @@ class AdChat {
155
283
  }
156
284
  }
157
285
  exports.AdChat = AdChat;
286
+ /**
287
+ * Merge a client-wide {@link BlockList} with a per-call one into a single
288
+ * blocklist for the wire. Categories and advertisers are unioned (deduped,
289
+ * order-preserving). Returns `undefined` when neither list contributes
290
+ * anything, so an empty blocklist is omitted from the request body.
291
+ */
292
+ function mergeBlockLists(base, override) {
293
+ const categories = dedupe([
294
+ ...(base?.categories ?? []),
295
+ ...(override?.categories ?? []),
296
+ ]);
297
+ const advertisers = dedupe([
298
+ ...(base?.advertisers ?? []),
299
+ ...(override?.advertisers ?? []),
300
+ ]);
301
+ const result = {};
302
+ if (categories.length)
303
+ result.categories = categories;
304
+ if (advertisers.length)
305
+ result.advertisers = advertisers;
306
+ return categories.length || advertisers.length ? result : undefined;
307
+ }
308
+ function dedupe(values) {
309
+ return [...new Set(values)];
310
+ }
158
311
  /**
159
312
  * Derive the fetch API's structured `conversation` field from structured
160
313
  * context turns. String context has no turn structure, so it is sent only
@@ -176,4 +329,27 @@ function conversationFromContext(context, subscriber) {
176
329
  })),
177
330
  };
178
331
  }
332
+ /** Ad message ids are `direqt-<uuid>`; postback data appends `_<actionId>`. */
333
+ const AD_POSTBACK = /^direqt-[^_]+_.+$/;
334
+ /**
335
+ * True for postback data that belongs to an ad action. Your agent's own
336
+ * replies carry their own payloads; report only the ad's to
337
+ * {@link AdChat.recordInteraction}.
338
+ */
339
+ function isAdPostbackData(data) {
340
+ return typeof data === 'string' && AD_POSTBACK.test(data);
341
+ }
342
+ /**
343
+ * The ad postback data in a Messenger webhook `messaging` event, or
344
+ * `undefined` when the event is not a tap on an ad's quick reply. The `fbm`
345
+ * format renders an ad's Text actions as quick replies whose payload is the
346
+ * action's postback data; Messenger returns it as
347
+ * `message.quick_reply.payload`.
348
+ *
349
+ * https://developers.facebook.com/docs/messenger-platform/reference/webhook-events/messages
350
+ */
351
+ function messengerPostbackData(event) {
352
+ const payload = event?.message?.quick_reply?.payload;
353
+ return isAdPostbackData(payload) ? payload : undefined;
354
+ }
179
355
  //# sourceMappingURL=client.js.map
package/bin/client.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
package/bin/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
- export { AdChat, AdChatOptions, conversationFromContext } from './client';
1
+ export { AdChat, AdChatOptions, conversationFromContext, isAdPostbackData, mergeBlockLists, messengerPostbackData, } from './client';
2
2
  export * from './types';
3
+ export { AdToolAction, AdToolResult, SPONSORED_DISCLOSURE, describeAd, } from './render/llm';
3
4
  export { renderWebchat, WebchatMessage, WebchatButton } from './render/webchat';
4
- export { renderWhatsApp, WhatsAppMessage } from './render/whatsapp';
5
+ export { renderWhatsApp, WhatsAppMessage, whatsAppPostbackData, } from './render/whatsapp';
package/bin/index.js CHANGED
@@ -14,13 +14,20 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
14
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
- exports.renderWhatsApp = exports.renderWebchat = exports.conversationFromContext = exports.AdChat = void 0;
17
+ exports.whatsAppPostbackData = exports.renderWhatsApp = exports.renderWebchat = exports.describeAd = exports.SPONSORED_DISCLOSURE = exports.messengerPostbackData = exports.mergeBlockLists = exports.isAdPostbackData = exports.conversationFromContext = exports.AdChat = void 0;
18
18
  var client_1 = require("./client");
19
19
  Object.defineProperty(exports, "AdChat", { enumerable: true, get: function () { return client_1.AdChat; } });
20
20
  Object.defineProperty(exports, "conversationFromContext", { enumerable: true, get: function () { return client_1.conversationFromContext; } });
21
+ Object.defineProperty(exports, "isAdPostbackData", { enumerable: true, get: function () { return client_1.isAdPostbackData; } });
22
+ Object.defineProperty(exports, "mergeBlockLists", { enumerable: true, get: function () { return client_1.mergeBlockLists; } });
23
+ Object.defineProperty(exports, "messengerPostbackData", { enumerable: true, get: function () { return client_1.messengerPostbackData; } });
21
24
  __exportStar(require("./types"), exports);
25
+ var llm_1 = require("./render/llm");
26
+ Object.defineProperty(exports, "SPONSORED_DISCLOSURE", { enumerable: true, get: function () { return llm_1.SPONSORED_DISCLOSURE; } });
27
+ Object.defineProperty(exports, "describeAd", { enumerable: true, get: function () { return llm_1.describeAd; } });
22
28
  var webchat_1 = require("./render/webchat");
23
29
  Object.defineProperty(exports, "renderWebchat", { enumerable: true, get: function () { return webchat_1.renderWebchat; } });
24
30
  var whatsapp_1 = require("./render/whatsapp");
25
31
  Object.defineProperty(exports, "renderWhatsApp", { enumerable: true, get: function () { return whatsapp_1.renderWhatsApp; } });
32
+ Object.defineProperty(exports, "whatsAppPostbackData", { enumerable: true, get: function () { return whatsapp_1.whatsAppPostbackData; } });
26
33
  //# sourceMappingURL=index.js.map
package/bin/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;AAAA,mCAA0E;AAAjE,gGAAA,MAAM,OAAA;AAAiB,iHAAA,uBAAuB,OAAA;AACvD,0CAAwB;AACxB,4CAAgF;AAAvE,wGAAA,aAAa,OAAA;AACtB,8CAAoE;AAA3D,0GAAA,cAAc,OAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;AAAA,mCAOkB;AANhB,gGAAA,MAAM,OAAA;AAEN,iHAAA,uBAAuB,OAAA;AACvB,0GAAA,gBAAgB,OAAA;AAChB,yGAAA,eAAe,OAAA;AACf,+GAAA,qBAAqB,OAAA;AAEvB,0CAAwB;AACxB,oCAKsB;AAFpB,2GAAA,oBAAoB,OAAA;AACpB,iGAAA,UAAU,OAAA;AAEZ,4CAAgF;AAAvE,wGAAA,aAAa,OAAA;AACtB,8CAI2B;AAHzB,0GAAA,cAAc,OAAA;AAEd,gHAAA,oBAAoB,OAAA"}
@@ -0,0 +1,48 @@
1
+ import { Ad, UniversalAdPayload } from '../types';
2
+ /**
3
+ * The disclosure duty that travels with every ad handed to a model. It
4
+ * tells the model to label the ad as sponsored, present it faithfully, and
5
+ * use click URLs verbatim so clicks are attributed.
6
+ */
7
+ export declare const SPONSORED_DISCLOSURE: string;
8
+ /** One action of an ad, as a model sees it. */
9
+ export interface AdToolAction {
10
+ label: string;
11
+ /** Click-tracking URL to share verbatim (Navigate actions). */
12
+ click_url?: string;
13
+ /** Phone number to offer (Call actions). */
14
+ phone_number?: string;
15
+ /**
16
+ * For a reply (Text action): pass it to `AdChat.recordInteraction` when
17
+ * the user chooses the reply.
18
+ */
19
+ postback_data?: string;
20
+ }
21
+ /** A filled ad as a tool result for a model. */
22
+ export interface AdToolResult {
23
+ status: 'filled';
24
+ message_id: string;
25
+ sponsored_disclosure: string;
26
+ ad: {
27
+ title: string;
28
+ subtitle: string;
29
+ image_url?: string;
30
+ alt_text?: string;
31
+ actions: AdToolAction[];
32
+ };
33
+ /** A ready-to-send rendering: the sponsored line, then the first link. */
34
+ suggested_text: string;
35
+ }
36
+ /**
37
+ * Convert a fetched ad into a tool result for a model: the essentials for
38
+ * presenting it, the disclosure duty, and each reply's `postback_data`.
39
+ * This is the result the MCP server's `fetch_ad` returns; use it in any
40
+ * other tool loop (OpenAI-style function tools, a custom framework) so the
41
+ * model sees the same shape.
42
+ *
43
+ * ```ts
44
+ * const ad = await adchat.fetchAd<UniversalAdPayload>({ subscriber });
45
+ * return ad ? describeAd(ad) : { status: 'no_ad' };
46
+ * ```
47
+ */
48
+ export declare function describeAd(ad: Ad<UniversalAdPayload>): AdToolResult;
@@ -0,0 +1,57 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SPONSORED_DISCLOSURE = void 0;
4
+ exports.describeAd = describeAd;
5
+ const types_1 = require("../types");
6
+ /**
7
+ * The disclosure duty that travels with every ad handed to a model. It
8
+ * tells the model to label the ad as sponsored, present it faithfully, and
9
+ * use click URLs verbatim so clicks are attributed.
10
+ */
11
+ exports.SPONSORED_DISCLOSURE = 'This is a paid advertisement. When you present it, you MUST clearly ' +
12
+ "label it as sponsored (e.g. prefix with 'Sponsored:' or an equivalent " +
13
+ 'disclosure in the conversation language). Present the ad content ' +
14
+ 'faithfully — do not exaggerate, embellish, or add claims of your own. ' +
15
+ 'Use the provided click_url values verbatim for any links, so clicks ' +
16
+ 'are attributed.';
17
+ /**
18
+ * Convert a fetched ad into a tool result for a model: the essentials for
19
+ * presenting it, the disclosure duty, and each reply's `postback_data`.
20
+ * This is the result the MCP server's `fetch_ad` returns; use it in any
21
+ * other tool loop (OpenAI-style function tools, a custom framework) so the
22
+ * model sees the same shape.
23
+ *
24
+ * ```ts
25
+ * const ad = await adchat.fetchAd<UniversalAdPayload>({ subscriber });
26
+ * return ad ? describeAd(ad) : { status: 'no_ad' };
27
+ * ```
28
+ */
29
+ function describeAd(ad) {
30
+ const p = ad.payload;
31
+ const actions = (p.actions || []).map(a => ({
32
+ label: a.text,
33
+ ...(a.navigate?.url ? { click_url: a.navigate.url } : {}),
34
+ ...(a.call?.phoneNumber ? { phone_number: a.call.phoneNumber } : {}),
35
+ ...(a.type === types_1.AdActionType.Text && a.context
36
+ ? { postback_data: a.context }
37
+ : {}),
38
+ }));
39
+ const firstUrl = actions.find(a => a.click_url)?.click_url;
40
+ return {
41
+ status: 'filled',
42
+ message_id: ad.messageId,
43
+ sponsored_disclosure: exports.SPONSORED_DISCLOSURE,
44
+ ad: {
45
+ title: p.title,
46
+ subtitle: p.subtitle,
47
+ ...(p.imageUrl ? { image_url: p.imageUrl } : {}),
48
+ ...(p.altText ? { alt_text: p.altText } : {}),
49
+ actions,
50
+ },
51
+ suggested_text: [
52
+ `Sponsored: ${[p.title, p.subtitle].filter(Boolean).join(' — ') || p.altText || ''}`,
53
+ ...(firstUrl ? [firstUrl] : []),
54
+ ].join('\n'),
55
+ };
56
+ }
57
+ //# sourceMappingURL=llm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"llm.js","sourceRoot":"","sources":["../../src/render/llm.ts"],"names":[],"mappings":";;;AAyDA,gCA2BC;AApFD,oCAAgE;AAEhE;;;;GAIG;AACU,QAAA,oBAAoB,GAC/B,sEAAsE;IACtE,wEAAwE;IACxE,mEAAmE;IACnE,wEAAwE;IACxE,sEAAsE;IACtE,iBAAiB,CAAC;AAgCpB;;;;;;;;;;;GAWG;AACH,SAAgB,UAAU,CAAC,EAA0B;IACnD,MAAM,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC;IACrB,MAAM,OAAO,GAAmB,CAAC,CAAC,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;QAC1D,KAAK,EAAE,CAAC,CAAC,IAAI;QACb,GAAG,CAAC,CAAC,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,CAAC,CAAC,QAAQ,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACzD,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,WAAW,CAAC,CAAC,CAAC,EAAE,YAAY,EAAE,CAAC,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACpE,GAAG,CAAC,CAAC,CAAC,IAAI,KAAK,oBAAY,CAAC,IAAI,IAAI,CAAC,CAAC,OAAO;YAC3C,CAAC,CAAC,EAAE,aAAa,EAAE,CAAC,CAAC,OAAO,EAAE;YAC9B,CAAC,CAAC,EAAE,CAAC;KACR,CAAC,CAAC,CAAC;IACJ,MAAM,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,EAAE,SAAS,CAAC;IAC3D,OAAO;QACL,MAAM,EAAE,QAAQ;QAChB,UAAU,EAAE,EAAE,CAAC,SAAS;QACxB,oBAAoB,EAAE,4BAAoB;QAC1C,EAAE,EAAE;YACF,KAAK,EAAE,CAAC,CAAC,KAAK;YACd,QAAQ,EAAE,CAAC,CAAC,QAAQ;YACpB,GAAG,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAChD,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAC7C,OAAO;SACR;QACD,cAAc,EAAE;YACd,cAAc,CAAC,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,OAAO,IAAI,EAAE,EAAE;YACpF,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;SAChC,CAAC,IAAI,CAAC,IAAI,CAAC;KACb,CAAC;AACJ,CAAC"}
@@ -23,6 +23,11 @@ export interface WebchatButton {
23
23
  url?: string;
24
24
  /** Phone number to dial (Call actions). */
25
25
  phoneNumber?: string;
26
+ /**
27
+ * Postback data for a reply (Text actions). When the user taps the button,
28
+ * pass it to `AdChat.recordInteraction`.
29
+ */
30
+ postbackData?: string;
26
31
  /** Stable id for the button (the ad action id). */
27
32
  id: string;
28
33
  }
@@ -26,6 +26,9 @@ function renderWebchat(payload) {
26
26
  ...(action.type === types_1.AdActionType.Call && action.call
27
27
  ? { phoneNumber: action.call.phoneNumber }
28
28
  : {}),
29
+ ...(action.type === types_1.AdActionType.Text
30
+ ? { postbackData: action.context }
31
+ : {}),
29
32
  id: action.actionId,
30
33
  })),
31
34
  disclosure: 'Sponsored',
@@ -1 +1 @@
1
- {"version":3,"file":"webchat.js","sourceRoot":"","sources":["../../src/render/webchat.ts"],"names":[],"mappings":";;AA0CA,sCAiBC;AA3DD,oCAA4D;AA+B5D;;;;;;;;;;GAUG;AACH,SAAgB,aAAa,CAAC,OAA2B;IACvD,MAAM,KAAK,GAAG,CAAC,OAAO,CAAC,KAAK,EAAE,OAAO,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IAChE,OAAO;QACL,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,OAAO,CAAC,OAAO,IAAI,EAAE;QAC/C,GAAG,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,OAAO,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC3D,OAAO,EAAE,CAAC,OAAO,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;YAC9C,KAAK,EAAE,MAAM,CAAC,IAAI;YAClB,GAAG,CAAC,MAAM,CAAC,IAAI,KAAK,oBAAY,CAAC,QAAQ,IAAI,MAAM,CAAC,QAAQ;gBAC1D,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,CAAC,QAAQ,CAAC,GAAG,EAAE;gBAC9B,CAAC,CAAC,EAAE,CAAC;YACP,GAAG,CAAC,MAAM,CAAC,IAAI,KAAK,oBAAY,CAAC,IAAI,IAAI,MAAM,CAAC,IAAI;gBAClD,CAAC,CAAC,EAAE,WAAW,EAAE,MAAM,CAAC,IAAI,CAAC,WAAW,EAAE;gBAC1C,CAAC,CAAC,EAAE,CAAC;YACP,EAAE,EAAE,MAAM,CAAC,QAAQ;SACpB,CAAC,CAAC;QACH,UAAU,EAAE,WAAW;KACxB,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"webchat.js","sourceRoot":"","sources":["../../src/render/webchat.ts"],"names":[],"mappings":";;AA+CA,sCAoBC;AAnED,oCAA4D;AAoC5D;;;;;;;;;;GAUG;AACH,SAAgB,aAAa,CAAC,OAA2B;IACvD,MAAM,KAAK,GAAG,CAAC,OAAO,CAAC,KAAK,EAAE,OAAO,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IAChE,OAAO;QACL,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,OAAO,CAAC,OAAO,IAAI,EAAE;QAC/C,GAAG,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,OAAO,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC3D,OAAO,EAAE,CAAC,OAAO,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;YAC9C,KAAK,EAAE,MAAM,CAAC,IAAI;YAClB,GAAG,CAAC,MAAM,CAAC,IAAI,KAAK,oBAAY,CAAC,QAAQ,IAAI,MAAM,CAAC,QAAQ;gBAC1D,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,CAAC,QAAQ,CAAC,GAAG,EAAE;gBAC9B,CAAC,CAAC,EAAE,CAAC;YACP,GAAG,CAAC,MAAM,CAAC,IAAI,KAAK,oBAAY,CAAC,IAAI,IAAI,MAAM,CAAC,IAAI;gBAClD,CAAC,CAAC,EAAE,WAAW,EAAE,MAAM,CAAC,IAAI,CAAC,WAAW,EAAE;gBAC1C,CAAC,CAAC,EAAE,CAAC;YACP,GAAG,CAAC,MAAM,CAAC,IAAI,KAAK,oBAAY,CAAC,IAAI;gBACnC,CAAC,CAAC,EAAE,YAAY,EAAE,MAAM,CAAC,OAAO,EAAE;gBAClC,CAAC,CAAC,EAAE,CAAC;YACP,EAAE,EAAE,MAAM,CAAC,QAAQ;SACpB,CAAC,CAAC;QACH,UAAU,EAAE,WAAW;KACxB,CAAC;AACJ,CAAC"}
@@ -26,9 +26,39 @@ export type WhatsAppMessage = {
26
26
  * message object.
27
27
  *
28
28
  * Mapping (faithful to the platform's channel renderers):
29
- * - One Navigate action → interactive `cta_url` message: image header,
30
- * title/subtitle body, "Sponsored" footer, URL button (click-tracked).
29
+ * - Text (reply) actions → interactive `button` message: image header,
30
+ * title/subtitle body (Navigate URLs appended), "Sponsored" footer, and
31
+ * up to three reply buttons whose id is the action's postback data. A tap
32
+ * comes back on your webhook as `interactive.button_reply.id`; pass it to
33
+ * `AdChat.recordInteraction` (see {@link whatsAppPostbackData}).
34
+ * - Otherwise one Navigate action → interactive `cta_url` message: image
35
+ * header, title/subtitle body, "Sponsored" footer, URL button
36
+ * (click-tracked).
31
37
  * - Otherwise → image with caption (or plain text if no image); Navigate
32
38
  * URLs are appended to the text, SMS-style.
39
+ *
40
+ * WhatsApp does not mix URL and reply buttons in one message, so an ad with
41
+ * both shows its replies as buttons and its links in the body.
33
42
  */
34
43
  export declare function renderWhatsApp(payload: UniversalAdPayload): WhatsAppMessage;
44
+ /**
45
+ * The ad postback data in a WhatsApp Cloud API webhook message, or
46
+ * `undefined` when the message is not a tap on an ad's reply button.
47
+ * {@link renderWhatsApp} sets each reply button's id to the action's
48
+ * postback data; WhatsApp returns it as `interactive.button_reply.id`.
49
+ *
50
+ * ```ts
51
+ * // entry[].changes[].value.messages[]
52
+ * const postbackData = whatsAppPostbackData(message);
53
+ * if (postbackData) await adchat.recordInteraction(postbackData);
54
+ * ```
55
+ *
56
+ * https://developers.facebook.com/docs/whatsapp/cloud-api/webhooks/payload-examples
57
+ */
58
+ export declare function whatsAppPostbackData(message: {
59
+ interactive?: {
60
+ button_reply?: {
61
+ id?: unknown;
62
+ };
63
+ };
64
+ }): string | undefined;
@@ -1,22 +1,68 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.renderWhatsApp = renderWhatsApp;
4
+ exports.whatsAppPostbackData = whatsAppPostbackData;
5
+ const client_1 = require("../client");
4
6
  const types_1 = require("../types");
7
+ /** WhatsApp Cloud API limits for interactive messages. */
8
+ const MAX_REPLY_BUTTONS = 3;
9
+ const MAX_BUTTON_TITLE = 20;
10
+ const MAX_BUTTON_ID = 256;
11
+ const MAX_BODY = 1024;
5
12
  /**
6
13
  * Render a universal ('direqt' format) ad payload as a WhatsApp Cloud API
7
14
  * message object.
8
15
  *
9
16
  * Mapping (faithful to the platform's channel renderers):
10
- * - One Navigate action → interactive `cta_url` message: image header,
11
- * title/subtitle body, "Sponsored" footer, URL button (click-tracked).
17
+ * - Text (reply) actions → interactive `button` message: image header,
18
+ * title/subtitle body (Navigate URLs appended), "Sponsored" footer, and
19
+ * up to three reply buttons whose id is the action's postback data. A tap
20
+ * comes back on your webhook as `interactive.button_reply.id`; pass it to
21
+ * `AdChat.recordInteraction` (see {@link whatsAppPostbackData}).
22
+ * - Otherwise one Navigate action → interactive `cta_url` message: image
23
+ * header, title/subtitle body, "Sponsored" footer, URL button
24
+ * (click-tracked).
12
25
  * - Otherwise → image with caption (or plain text if no image); Navigate
13
26
  * URLs are appended to the text, SMS-style.
27
+ *
28
+ * WhatsApp does not mix URL and reply buttons in one message, so an ad with
29
+ * both shows its replies as buttons and its links in the body.
14
30
  */
15
31
  function renderWhatsApp(payload) {
16
32
  const bodyText = [payload.title, payload.subtitle].filter(Boolean).join('\n') ||
17
33
  payload.altText ||
18
34
  '';
19
- const navigateActions = (payload.actions || []).filter(a => a.type === types_1.AdActionType.Navigate && a.navigate?.url);
35
+ const actions = payload.actions || [];
36
+ const navigateActions = actions.filter(a => a.type === types_1.AdActionType.Navigate && a.navigate?.url);
37
+ const urls = navigateActions.map(a => a.navigate.url);
38
+ const header = payload.imageUrl
39
+ ? { header: { type: 'image', image: { link: payload.imageUrl } } }
40
+ : {};
41
+ const replies = actions
42
+ .filter((a) => a.type === types_1.AdActionType.Text &&
43
+ !!a.text &&
44
+ !!a.context &&
45
+ a.context.length <= MAX_BUTTON_ID)
46
+ .slice(0, MAX_REPLY_BUTTONS);
47
+ if (replies.length) {
48
+ return {
49
+ messaging_product: 'whatsapp',
50
+ recipient_type: 'individual',
51
+ type: 'interactive',
52
+ interactive: {
53
+ type: 'button',
54
+ ...header,
55
+ body: { text: truncate([bodyText, ...urls].join('\n'), MAX_BODY) },
56
+ footer: { text: 'Sponsored' },
57
+ action: {
58
+ buttons: replies.map(a => ({
59
+ type: 'reply',
60
+ reply: { id: a.context, title: truncate(a.text, MAX_BUTTON_TITLE) },
61
+ })),
62
+ },
63
+ },
64
+ };
65
+ }
20
66
  if (navigateActions.length === 1) {
21
67
  const action = navigateActions[0];
22
68
  return {
@@ -25,15 +71,13 @@ function renderWhatsApp(payload) {
25
71
  type: 'interactive',
26
72
  interactive: {
27
73
  type: 'cta_url',
28
- ...(payload.imageUrl
29
- ? { header: { type: 'image', image: { link: payload.imageUrl } } }
30
- : {}),
74
+ ...header,
31
75
  body: { text: bodyText },
32
76
  footer: { text: 'Sponsored' },
33
77
  action: {
34
78
  name: 'cta_url',
35
79
  parameters: {
36
- display_text: truncate(action.text, 20) || 'Learn more',
80
+ display_text: truncate(action.text, MAX_BUTTON_TITLE) || 'Learn more',
37
81
  // Already a click-tracking URL that 302s to the advertiser.
38
82
  url: action.navigate.url,
39
83
  },
@@ -42,7 +86,6 @@ function renderWhatsApp(payload) {
42
86
  };
43
87
  }
44
88
  // Fallback: degrade like the SMS renderer — text plus URL(s).
45
- const urls = navigateActions.map(a => a.navigate.url);
46
89
  const caption = ['Sponsored', bodyText, ...urls].filter(Boolean).join('\n');
47
90
  if (payload.imageUrl) {
48
91
  return {
@@ -59,7 +102,25 @@ function renderWhatsApp(payload) {
59
102
  text: { body: caption, preview_url: true },
60
103
  };
61
104
  }
62
- /** WhatsApp cta_url display_text is limited to 20 characters. */
105
+ /**
106
+ * The ad postback data in a WhatsApp Cloud API webhook message, or
107
+ * `undefined` when the message is not a tap on an ad's reply button.
108
+ * {@link renderWhatsApp} sets each reply button's id to the action's
109
+ * postback data; WhatsApp returns it as `interactive.button_reply.id`.
110
+ *
111
+ * ```ts
112
+ * // entry[].changes[].value.messages[]
113
+ * const postbackData = whatsAppPostbackData(message);
114
+ * if (postbackData) await adchat.recordInteraction(postbackData);
115
+ * ```
116
+ *
117
+ * https://developers.facebook.com/docs/whatsapp/cloud-api/webhooks/payload-examples
118
+ */
119
+ function whatsAppPostbackData(message) {
120
+ const id = message?.interactive?.button_reply?.id;
121
+ return (0, client_1.isAdPostbackData)(id) ? id : undefined;
122
+ }
123
+ /** WhatsApp limits button titles to 20 characters and bodies to 1024. */
63
124
  function truncate(s, max) {
64
125
  if (!s)
65
126
  return s;
@@ -1 +1 @@
1
- {"version":3,"file":"whatsapp.js","sourceRoot":"","sources":["../../src/render/whatsapp.ts"],"names":[],"mappings":";;AA6BA,wCAqDC;AAlFD,oCAA4D;AAmB5D;;;;;;;;;GASG;AACH,SAAgB,cAAc,CAAC,OAA2B;IACxD,MAAM,QAAQ,GACZ,CAAC,OAAO,CAAC,KAAK,EAAE,OAAO,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC;QAC5D,OAAO,CAAC,OAAO;QACf,EAAE,CAAC;IAEL,MAAM,eAAe,GAAG,CAAC,OAAO,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,MAAM,CACpD,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,oBAAY,CAAC,QAAQ,IAAI,CAAC,CAAC,QAAQ,EAAE,GAAG,CACzD,CAAC;IAEF,IAAI,eAAe,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACjC,MAAM,MAAM,GAAG,eAAe,CAAC,CAAC,CAAC,CAAC;QAClC,OAAO;YACL,iBAAiB,EAAE,UAAU;YAC7B,cAAc,EAAE,YAAY;YAC5B,IAAI,EAAE,aAAa;YACnB,WAAW,EAAE;gBACX,IAAI,EAAE,SAAS;gBACf,GAAG,CAAC,OAAO,CAAC,QAAQ;oBAClB,CAAC,CAAC,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,OAAO,CAAC,QAAQ,EAAE,EAAE,EAAE;oBAClE,CAAC,CAAC,EAAE,CAAC;gBACP,IAAI,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;gBACxB,MAAM,EAAE,EAAE,IAAI,EAAE,WAAW,EAAE;gBAC7B,MAAM,EAAE;oBACN,IAAI,EAAE,SAAS;oBACf,UAAU,EAAE;wBACV,YAAY,EAAE,QAAQ,CAAC,MAAM,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,YAAY;wBACvD,4DAA4D;wBAC5D,GAAG,EAAE,MAAM,CAAC,QAAS,CAAC,GAAG;qBAC1B;iBACF;aACF;SACF,CAAC;IACJ,CAAC;IAED,8DAA8D;IAC9D,MAAM,IAAI,GAAG,eAAe,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,QAAS,CAAC,GAAG,CAAC,CAAC;IACvD,MAAM,OAAO,GAAG,CAAC,WAAW,EAAE,QAAQ,EAAE,GAAG,IAAI,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAE5E,IAAI,OAAO,CAAC,QAAQ,EAAE,CAAC;QACrB,OAAO;YACL,iBAAiB,EAAE,UAAU;YAC7B,cAAc,EAAE,YAAY;YAC5B,IAAI,EAAE,OAAO;YACb,KAAK,EAAE,EAAE,IAAI,EAAE,OAAO,CAAC,QAAQ,EAAE,OAAO,EAAE;SAC3C,CAAC;IACJ,CAAC;IACD,OAAO;QACL,iBAAiB,EAAE,UAAU;QAC7B,cAAc,EAAE,YAAY;QAC5B,IAAI,EAAE,MAAM;QACZ,IAAI,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,WAAW,EAAE,IAAI,EAAE;KAC3C,CAAC;AACJ,CAAC;AAED,iEAAiE;AACjE,SAAS,QAAQ,CAAC,CAAS,EAAE,GAAW;IACtC,IAAI,CAAC,CAAC;QAAE,OAAO,CAAC,CAAC;IACjB,OAAO,CAAC,CAAC,MAAM,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,GAAG,CAAC,CAAC,GAAG,CAAC;AACzD,CAAC"}
1
+ {"version":3,"file":"whatsapp.js","sourceRoot":"","sources":["../../src/render/whatsapp.ts"],"names":[],"mappings":"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"}
package/bin/types.d.ts CHANGED
@@ -21,10 +21,51 @@ export interface ConversationTurn {
21
21
  * summary of the conversation so far, or the most recent turns.
22
22
  */
23
23
  export type AdContext = string | ConversationTurn[];
24
+ /**
25
+ * A high-level blocklist: categories and/or advertisers a publisher never
26
+ * wants served into their conversations. Set it once on the {@link AdChat}
27
+ * client (applies to every fetch) and/or per {@link FetchAdParams} call
28
+ * (merged with the client-wide list).
29
+ *
30
+ * ```ts
31
+ * const adchat = new AdChat({
32
+ * fetchKey: process.env.ADCHAT_FETCH_KEY!,
33
+ * block: { categories: ['gambling', 'alcohol'], advertisers: ['AcmeCorp'] },
34
+ * });
35
+ * ```
36
+ *
37
+ * @remarks **Roadmap / beta.** Block lists are sent to the server on every
38
+ * fetch and are honored where server-side enforcement is available;
39
+ * category/advertiser enforcement is still rolling out, so treat this as a
40
+ * best-effort filter rather than a guarantee for now. The SDK surface and the
41
+ * wire contract are stable — enforcement is tracked as an `ads` follow-up.
42
+ */
43
+ export interface BlockList {
44
+ /**
45
+ * Category codes/labels to block (e.g. `'gambling'`, `'alcohol'`,
46
+ * `'politics'`). Matched against each candidate ad's categories.
47
+ */
48
+ categories?: string[];
49
+ /**
50
+ * Advertiser names/ids to block. Ads from these advertisers are excluded
51
+ * from selection.
52
+ */
53
+ advertisers?: string[];
54
+ }
24
55
  export interface FetchAdParams {
25
56
  /**
26
- * Ad unit code identifying the placement. If omitted, the org's default
27
- * ad unit is used.
57
+ * Id of the Application (agent) this fetch is for. Not needed with a fetch
58
+ * key, which is bound to one application. Required with any other key.
59
+ * When both are present they must agree. Overrides
60
+ * {@link AdChatOptions.application}.
61
+ */
62
+ application?: string;
63
+ /**
64
+ * **Optional — most publishers can omit this.** Ad units are an *Offer
65
+ * Manager* concept for pinning a specific placement; a plain in-chat-ads
66
+ * integration doesn't need one. When omitted, the fetch fills against the
67
+ * application's default ad unit (created on first use). Advanced users pin
68
+ * a specific unit by code; codes are unique within an application.
28
69
  */
29
70
  adUnit?: string;
30
71
  /** Render format for the payload. Defaults to 'direqt' (universal JSON). */
@@ -41,6 +82,12 @@ export interface FetchAdParams {
41
82
  context?: AdContext;
42
83
  /** Targeting attributes (key/value pairs) for rule targeting. */
43
84
  attributes?: Record<string, string>;
85
+ /**
86
+ * Block categories/advertisers for this fetch only. Merged with any
87
+ * client-wide {@link AdChatOptions.block}. See {@link BlockList} (roadmap /
88
+ * beta — enforcement is rolling out server-side).
89
+ */
90
+ block?: BlockList;
44
91
  /**
45
92
  * Marks the fetch as a test: excluded from normal reporting and
46
93
  * performance statistics.
@@ -50,7 +97,9 @@ export interface FetchAdParams {
50
97
  /** Action types on a universal ad payload. */
51
98
  export declare enum AdActionType {
52
99
  Navigate = "Navigate",
53
- Call = "Call"
100
+ Call = "Call",
101
+ /** A reply: the tap comes back to your agent with the postback data. */
102
+ Text = "Text"
54
103
  }
55
104
  /**
56
105
  * A tappable action on an ad (button / suggested reply).
@@ -116,6 +165,46 @@ export interface Ad<P = unknown> {
116
165
  media?: AdMedia[];
117
166
  isTest?: boolean;
118
167
  }
168
+ /**
169
+ * Reserved, always-fills sandbox ad unit. Fetching this ad unit returns a
170
+ * canned, clearly-labeled demo card regardless of account configuration —
171
+ * use it to verify your integration end-to-end before your real ad units
172
+ * have eligible line items. It records no billing or impression events.
173
+ */
174
+ export declare const SANDBOX_AD_UNIT = "__sandbox__";
175
+ /**
176
+ * Machine-readable reason a fetch produced no ad. Returned by
177
+ * {@link AdChat.fetchAdDebug} so a legitimate empty result can be told apart
178
+ * from a misconfiguration:
179
+ *
180
+ * - `unknown_ad_unit` — no ad unit with that code exists for this org
181
+ * (typo, or the API key belongs to a different org). **Misconfiguration.**
182
+ * - `ad_unit_disabled` — the ad unit exists but is disabled.
183
+ * **Misconfiguration.**
184
+ * - `no_eligible_line_items` — the ad unit exists but has no line
185
+ * items configured, or none eligible for this request. **Misconfiguration**
186
+ * during onboarding (nothing to serve yet).
187
+ * - `frequency_capped` — this subscriber has hit the configured frequency
188
+ * cap. Normal.
189
+ * - `appropriateness_suppressed` — serving was suppressed because the
190
+ * conversational context was assessed as inappropriate for advertising.
191
+ * Normal.
192
+ * - `no_demand` — everything is configured correctly; there was simply no
193
+ * ad to serve right now. Normal (a legitimate no-fill).
194
+ */
195
+ export type NoFillReason = 'unknown_ad_unit' | 'ad_unit_disabled' | 'no_eligible_line_items' | 'frequency_capped' | 'appropriateness_suppressed' | 'no_demand';
196
+ /**
197
+ * Result of {@link AdChat.fetchAdDebug}: either a served ad, or a no-fill
198
+ * with a machine-readable {@link NoFillReason} explaining why.
199
+ */
200
+ export type FetchAdResult<P = unknown> = {
201
+ filled: true;
202
+ ad: Ad<P>;
203
+ } | {
204
+ filled: false;
205
+ reason: NoFillReason;
206
+ message: string;
207
+ };
119
208
  /** Event types accepted by the v2 event API. */
120
209
  export type AdEventType = 'send' | 'deliver' | 'read' | 'click' | 'conversion';
121
210
  export interface RecordEventParams {
@@ -143,6 +232,19 @@ export interface RecordConversionParams {
143
232
  data?: object;
144
233
  subscriber?: string;
145
234
  }
235
+ /**
236
+ * Result of {@link AdChat.recordInteraction}: the tapped action, and for a
237
+ * reply with an accept or decline role, the response recorded in the
238
+ * customer's history.
239
+ */
240
+ export interface InteractionResult {
241
+ /** The tapped action's id. */
242
+ actionId: string;
243
+ /** The tapped reply's role, if it has one. */
244
+ role?: 'accept' | 'decline';
245
+ /** The response recorded in subscriber history, if any. */
246
+ recorded?: 'accepted' | 'declined';
247
+ }
146
248
  export interface ClickUrlParams {
147
249
  /** The `messageId` from the fetch response. */
148
250
  messageId: string;
package/bin/types.js CHANGED
@@ -6,13 +6,22 @@
6
6
  * the `ads` service (`src/ads-api/v1`) and `ads/ads-api/src/v1/fetch.ts`.
7
7
  */
8
8
  Object.defineProperty(exports, "__esModule", { value: true });
9
- exports.AdChatApiError = exports.AdActionType = void 0;
9
+ exports.AdChatApiError = exports.SANDBOX_AD_UNIT = exports.AdActionType = void 0;
10
10
  /** Action types on a universal ad payload. */
11
11
  var AdActionType;
12
12
  (function (AdActionType) {
13
13
  AdActionType["Navigate"] = "Navigate";
14
14
  AdActionType["Call"] = "Call";
15
+ /** A reply: the tap comes back to your agent with the postback data. */
16
+ AdActionType["Text"] = "Text";
15
17
  })(AdActionType || (exports.AdActionType = AdActionType = {}));
18
+ /**
19
+ * Reserved, always-fills sandbox ad unit. Fetching this ad unit returns a
20
+ * canned, clearly-labeled demo card regardless of account configuration —
21
+ * use it to verify your integration end-to-end before your real ad units
22
+ * have eligible line items. It records no billing or impression events.
23
+ */
24
+ exports.SANDBOX_AD_UNIT = '__sandbox__';
16
25
  /**
17
26
  * Error thrown for non-2xx API responses.
18
27
  *
package/bin/types.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":";AAAA;;;;;GAKG;;;AAyDH,8CAA8C;AAC9C,IAAY,YAGX;AAHD,WAAY,YAAY;IACtB,qCAAqB,CAAA;IACrB,6BAAa,CAAA;AACf,CAAC,EAHW,YAAY,4BAAZ,YAAY,QAGvB;AA0GD;;;;;GAKG;AACH,MAAa,cAAe,SAAQ,KAAK;IAGrB;IAKA;IAPlB;IACE,uCAAuC;IACvB,MAAc;IAC9B;;;OAGG;IACa,MAAe,EAC/B,OAAgB;QAEhB,KAAK,CAAC,OAAO,IAAI,8BAA8B,MAAM,IAAI,MAAM,IAAI,EAAE,EAAE,CAAC,CAAC;QARzD,WAAM,GAAN,MAAM,CAAQ;QAKd,WAAM,GAAN,MAAM,CAAS;QAI/B,IAAI,CAAC,IAAI,GAAG,gBAAgB,CAAC;IAC/B,CAAC;CACF;AAdD,wCAcC"}
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":";AAAA;;;;;GAKG;;;AAyGH,8CAA8C;AAC9C,IAAY,YAKX;AALD,WAAY,YAAY;IACtB,qCAAqB,CAAA;IACrB,6BAAa,CAAA;IACb,wEAAwE;IACxE,6BAAa,CAAA;AACf,CAAC,EALW,YAAY,4BAAZ,YAAY,QAKvB;AAmED;;;;;GAKG;AACU,QAAA,eAAe,GAAG,aAAa,CAAC;AA2F7C;;;;;GAKG;AACH,MAAa,cAAe,SAAQ,KAAK;IAGrB;IAKA;IAPlB;IACE,uCAAuC;IACvB,MAAc;IAC9B;;;OAGG;IACa,MAAe,EAC/B,OAAgB;QAEhB,KAAK,CAAC,OAAO,IAAI,8BAA8B,MAAM,IAAI,MAAM,IAAI,EAAE,EAAE,CAAC,CAAC;QARzD,WAAM,GAAN,MAAM,CAAQ;QAKd,WAAM,GAAN,MAAM,CAAS;QAI/B,IAAI,CAAC,IAAI,GAAG,gBAAgB,CAAC;IAC/B,CAAC;CACF;AAdD,wCAcC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@advertising-chat/sdk",
3
- "version": "0.2.1",
3
+ "version": "0.11.0",
4
4
  "description": "Typed client for the Advertising.chat ads API — fetch, click, and event tracking plus per-channel render helpers",
5
5
  "license": "MIT",
6
6
  "author": "Advertising.chat",