adsbooster-js-sdk 0.0.0-stage → 1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Adsbooster.net Engineering Team
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 CHANGED
@@ -1,3 +1,187 @@
1
- # Temporary Holding Version
2
-
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
1
+ # Adsbooster JavaScript SDK
2
+
3
+ Official JavaScript / TypeScript client for the **Adsbooster.net REST API v1** — built for SaaS teams that want AI ad creatives, multi-network campaign management, social publishing and server-side conversion tracking inside their own product.
4
+
5
+ - **Zero dependencies** — one small file, works in Node.js (≥18) and modern browsers
6
+ - **Typed** — ships complete TypeScript definitions
7
+ - **Resilient** — automatic retries on `429`/`5xx`, timeout via `AbortController`, rate-limit telemetry on every response
8
+ - **Universal** — UMD (`dist/adsbooster.js`), ESM (`dist/adsbooster.esm.js`)
9
+
10
+ > SDK repo: https://github.com/FushionHub/adsbooster-js-sdk
11
+ > Platform repo: https://github.com/FushionHub/seopp
12
+ > The SDK will move to the `adsbooster-sdks` organization — after transferring, update `repository` in package.json and the links above.
13
+
14
+ ---
15
+
16
+ ## Install
17
+
18
+ **npm / yarn / pnpm**
19
+ ```bash
20
+ npm install adsbooster-js-sdk
21
+ ```
22
+
23
+ **Browser (CDN or self-hosted from your Adsbooster deployment)**
24
+ ```html
25
+ <!-- Self-hosted — served straight from your platform, always in sync -->
26
+ <script src="https://adsbooster.net/api/v1/sdk/adsbooster.js"></script>
27
+
28
+ <!-- or jsDelivr once published -->
29
+ <script src="https://cdn.jsdelivr.net/npm/adsbooster-js-sdk/dist/adsbooster.js"></script>
30
+ ```
31
+
32
+ ## Quick start
33
+
34
+ ```js
35
+ const { Adsbooster } = require('adsbooster-js-sdk'); // Node
36
+ // import { Adsbooster } from 'adsbooster-js-sdk'; // ESM / TS
37
+ // const Adsbooster = window.Adsbooster.Adsbooster; // Browser <script>
38
+
39
+ const client = new Adsbooster({
40
+ apiKey: process.env.ADSBOOSTER_API_KEY, // from Developer Console → api-settings
41
+ // baseUrl: 'https://adsbooster.net', // default; change for self-hosted
42
+ });
43
+
44
+ // 1) Check wallet balance
45
+ const balance = await client.credits.balance();
46
+ console.log(balance.ad_credits, balance.ai_credits);
47
+
48
+ // 2) Generate an AI UGC creative (Nano Banana 2 image stack)
49
+ const creative = await client.ugc.generateImage({
50
+ product_name: 'Cloud-Knit Running Shoes',
51
+ ugc_style: 'selfie_hold',
52
+ creator_persona: 'gen_z',
53
+ aspect_ratio: '1:1',
54
+ });
55
+ console.log(creative.creative.image_url);
56
+
57
+ // 3) Launch a campaign across connected networks
58
+ const campaign = await client.campaigns.create({
59
+ name: 'Q4 Sneaker Drop',
60
+ platform: 'meta',
61
+ objective: 'conversions',
62
+ budget_amount: 25,
63
+ budget_type: 'daily',
64
+ });
65
+
66
+ // 4) Publish a post live to social
67
+ await client.posts.create({
68
+ platform: 'tiktok',
69
+ caption: 'New drop 🚀 link in bio #ad',
70
+ media_url: creative.creative.image_url,
71
+ media_type: 'image',
72
+ });
73
+ ```
74
+
75
+ ## API surface
76
+
77
+ | Namespace | Methods | Endpoint |
78
+ |---|---|---|
79
+ | `client.campaigns` | `list({page,limit,status})` · `get(id)` · `create(payload)` · `update(id,payload)` · `pause(id)` · `resume(id)` | `/api/v1/campaigns.php` |
80
+ | `client.posts` | `list({page,limit,status})` · `get(id)` · `create(payload)` · `remove(id)` | `/api/v1/posts.php` |
81
+ | `client.ugc` | `generateImage(payload)` · `listRecent({limit})` | `/api/v1/ugc.php` |
82
+ | `client.credits` | `balance()` | `/api/v1/credits.php` |
83
+ | `client.directory` | `list({category,q,limit})` · `createListing(payload)` | `/api/v1/directory.php` |
84
+ | `client.reviews` | `list({limit})` · `submit({rating,review_text,…})` | `/api/v1/reviews.php` |
85
+ | `client.capi` | `scriptUrl()` · `track({pixel_token,event_name,…})` | `/api/v1/capi-bridge.php` |
86
+ | `client.webhooks` | `list()` · `test(url)` — signed ping to YOUR registered https endpoint | `/api/v1/webhooks.php` |
87
+ | `client.publicAnalytics` | `feed(view?)` · `totals()` — public aggregate, no auth (30 req/min/IP) | `/api/v1/public-analytics.php` |
88
+
89
+ Every response is the raw API payload (`{ success: true, … }`). Non-2xx responses throw an [`AdsboosterError`](#error-handling).
90
+
91
+ ## Error handling
92
+
93
+ ```js
94
+ const { Adsbooster, AdsboosterError } = require('adsbooster-js-sdk');
95
+
96
+ try {
97
+ await client.ugc.generateImage({ product_name: '…' });
98
+ } catch (err) {
99
+ if (err instanceof AdsboosterError) {
100
+ console.error(err.status); // 401, 429, 500… (0 = network/timeout)
101
+ console.error(err.code); // 'Unauthorized', 'RateLimitExceeded'…
102
+ console.error(err.rateLimit); // { limit: 60, remaining: 0, reset: 1768… }
103
+ if (err.isRateLimit) {
104
+ await new Promise(r => setTimeout(r, 60_000)); // or rely on auto-retry
105
+ }
106
+ } else {
107
+ throw err;
108
+ }
109
+ }
110
+ ```
111
+
112
+ ### Rate limits
113
+ The platform enforces **60 requests / minute / key** (sliding window). The SDK:
114
+ - reads `X-RateLimit-*` headers on **every** response into `client.lastRateLimit`
115
+ - **auto-retries** `429` honoring `Retry-After` / `X-RateLimit-Reset`, and `5xx` with exponential backoff (default `maxRetries: 2`)
116
+
117
+ ```js
118
+ const client = new Adsbooster({ apiKey, maxRetries: 4, timeout: 60000 });
119
+ ```
120
+
121
+ ## Webhooks (server-side)
122
+
123
+ Outbound webhooks (`campaign.status_changed`, `post.published`, `payment.completed`) are signed with `X-Adsbooster-Signature: sha256=<hmac>` — verify with the raw body:
124
+
125
+ ```js
126
+ const express = require('express');
127
+ const { webhooks } = require('adsbooster-js-sdk');
128
+
129
+ app.post('/adsbooster/webhook', express.raw({ type: '*/*' }), (req, res) => {
130
+ const ok = webhooks.verify(
131
+ req.body, // RAW body (Buffer/string)
132
+ req.get('X-Adsbooster-Signature') || '',
133
+ process.env.WEBHOOK_SECRET // per-endpoint secret from api-settings
134
+ );
135
+ if (!ok) return res.status(401).send('bad signature');
136
+ const event = JSON.parse(req.body.toString('utf8'));
137
+ console.log(event.event, event.data);
138
+ res.sendStatus(200);
139
+ });
140
+ ```
141
+
142
+ ## Server-side conversion tracking (Meta CAPI / TikTok Events)
143
+
144
+ ```js
145
+ // Browser: drop-in script (no SDK needed)
146
+ // <script src="https://adsbooster.net/api/v1/capi-bridge.php?js=1"></script>
147
+ // <script>window._adsbooster_token='px_live_…'; AdsboosterTrack('Purchase',{value:49.99,currency:'USD'});</script>
148
+
149
+ // Server-side from your SaaS:
150
+ await client.capi.track({
151
+ pixel_token: 'px_live_…',
152
+ event_name: 'Purchase',
153
+ event_id: 'order_8841',
154
+ custom_data: { value: 49.99, currency: 'USD' },
155
+ user_data: { email_hash: sha256(email) },
156
+ });
157
+ ```
158
+
159
+ ## Examples
160
+
161
+ runnable scripts live in [`examples/`](examples/):
162
+
163
+ - `examples/node-smoke-test.js` — end-to-end smoke test against a live deployment
164
+ - `examples/node-create-campaign.js` — campaign lifecycle (create → pause → resume)
165
+ - `examples/browser-ugc.html` — generate an AI creative from a web page
166
+ - `examples/express-webhooks.js` — verify signed webhooks in Express
167
+
168
+ ## TypeScript
169
+
170
+ Full definitions ship in `types/index.d.ts`:
171
+
172
+ ```ts
173
+ import { Adsbooster, AdsboosterError } from 'adsbooster-js-sdk';
174
+ const client = new Adsbooster({ apiKey: process.env.ADSBOOSTER_API_KEY! });
175
+ const res = await client.campaigns.list({ status: 'active', limit: 50 });
176
+ ```
177
+
178
+ ## Getting an API key
179
+
180
+ 1. Sign up / log in on your Adsbooster deployment
181
+ 2. Open **Developer Console** (`/api-settings`)
182
+ 3. Generate a key (`adsb_live_…`) — optionally restrict it to your server IPs
183
+ 4. Fund ad/AI credits from **Billing & Wallet** (top-ups from $2.50)
184
+
185
+ ## License
186
+
187
+ [MIT](LICENSE) © Adsbooster.net Engineering Team
@@ -0,0 +1,473 @@
1
+ /**
2
+ * Adsbooster JavaScript SDK — ESM build. Generated from src/adsbooster.js (UMD).
3
+ * Do not edit by hand — edit the source and regenerate.
4
+ */
5
+ 'use strict';
6
+
7
+
8
+ var VERSION = '1.1.0';
9
+ var DEFAULT_TIMEOUT = 30000; // ms
10
+ var DEFAULT_RETRIES = 2; // retries on 429/5xx (initial attempt excluded)
11
+ var AI_CREDITS_PER_USD = 100; // documented platform rate
12
+
13
+ /* ─────────────────────────── Errors ─────────────────────────── */
14
+
15
+ /**
16
+ * Raised for any non-2xx API response, network failure, or usage error.
17
+ * `status` is the HTTP status (0 for network errors), `code` the API error
18
+ * slug (e.g. 'RateLimitExceeded'), `rateLimit` the last seen quota snapshot.
19
+ */
20
+ function AdsboosterError(message, opts) {
21
+ opts = opts || {};
22
+ this.name = 'AdsboosterError';
23
+ this.message = message || 'Unknown Adsbooster API error';
24
+ this.status = typeof opts.status === 'number' ? opts.status : 0;
25
+ this.code = opts.code || null;
26
+ this.details = opts.details || null;
27
+ this.rateLimit = opts.rateLimit || null;
28
+ this.isRateLimit = this.code === 'RateLimitExceeded' || this.status === 429;
29
+ var stackFn = Error.captureStackTrace;
30
+ if (stackFn) {
31
+ stackFn.call(Error, this, AdsboosterError);
32
+ } else {
33
+ this.stack = new Error(this.message).stack;
34
+ }
35
+ }
36
+ AdsboosterError.prototype = Object.create(Error.prototype);
37
+ AdsboosterError.prototype.constructor = AdsboosterError;
38
+
39
+ /* ─────────────────────── Small utilities ────────────────────── */
40
+
41
+ function isObject(v) { return v !== null && typeof v === 'object' && !Array.isArray(v); }
42
+
43
+ function sleep(ms) { return new Promise(function (r) { setTimeout(r, ms); }); }
44
+
45
+ function buildQuery(params) {
46
+ if (!params) return '';
47
+ var parts = [];
48
+ Object.keys(params).forEach(function (k) {
49
+ var v = params[k];
50
+ if (v === undefined || v === null || v === '') return;
51
+ parts.push(encodeURIComponent(k) + '=' + encodeURIComponent(v));
52
+ });
53
+ return parts.length ? '?' + parts.join('&') : '';
54
+ }
55
+
56
+ function nowSec() { return Math.floor(Date.now() / 1000); }
57
+
58
+ function parseRetryAfter(headers) {
59
+ if (!headers) return null;
60
+ var ra = headers.get ? headers.get('retry-after') : null;
61
+ if (ra) {
62
+ var n = parseInt(ra, 10);
63
+ if (!isNaN(n)) return n * 1000;
64
+ var asDate = Date.parse(ra);
65
+ if (!isNaN(asDate)) return Math.max(1000, asDate - Date.now());
66
+ }
67
+ var reset = headers.get ? headers.get('x-ratelimit-reset') : null;
68
+ if (reset) {
69
+ var delta = (parseInt(reset, 10) - nowSec()) * 1000;
70
+ if (!isNaN(delta) && delta > 0) return delta;
71
+ }
72
+ return null;
73
+ }
74
+
75
+ /* ────────────────────────── Client ──────────────────────────── */
76
+
77
+ /**
78
+ * @param {object} options
79
+ * @param {string} options.apiKey Required. adsb_live_… / adsb_test_… key from the Developer Console.
80
+ * @param {string} [options.baseUrl] Platform origin, e.g. https://adsbooster.net (no trailing /api).
81
+ * @param {number} [options.timeout=30000] Per-request timeout in ms.
82
+ * @param {number} [options.maxRetries=2] Automatic retries on 429 + 5xx.
83
+ * @param {function} [options.fetch] Custom fetch implementation (testing, proxies).
84
+ */
85
+ function Adsbooster(options) {
86
+ options = options || {};
87
+ if (!options.apiKey || typeof options.apiKey !== 'string') {
88
+ throw new AdsboosterError('An `apiKey` is required — generate one in your Adsbooster Developer Console (api-settings).');
89
+ }
90
+ this.apiKey = options.apiKey;
91
+ this.baseUrl = (options.baseUrl || 'https://adsbooster.net').replace(/\/+$/, '');
92
+ this.apiBase = this.baseUrl + '/api/v1';
93
+ this.timeout = typeof options.timeout === 'number' ? options.timeout : DEFAULT_TIMEOUT;
94
+ this.maxRetries = typeof options.maxRetries === 'number' ? options.maxRetries : DEFAULT_RETRIES;
95
+ this._fetch = options.fetch || (typeof fetch === 'function' ? fetch : null);
96
+ if (!this._fetch) {
97
+ throw new AdsboosterError('No fetch implementation found. Adsbooster SDK needs Node >= 18 or a modern browser.');
98
+ }
99
+
100
+ // Resource namespaces
101
+ this.campaigns = new CampaignsResource(this);
102
+ this.posts = new PostsResource(this);
103
+ this.ugc = new UgcResource(this);
104
+ this.credits = new CreditsResource(this);
105
+ this.directory = new DirectoryResource(this);
106
+ this.reviews = new ReviewsResource(this);
107
+ this.capi = new CapiResource(this);
108
+ this.webhooks = new WebhooksResource(this);
109
+ this.publicAnalytics = new PublicAnalyticsResource(this);
110
+
111
+ // Last observed quota: { limit, remaining, reset }
112
+ this.lastRateLimit = null;
113
+ }
114
+
115
+ Adsbooster.prototype.VERSION = VERSION;
116
+
117
+ /** Low-level request with timeout, retries and error normalisation. */
118
+ Adsbooster.prototype.request = function (method, path, body, options) {
119
+ options = options || {};
120
+ var self = this;
121
+ var url = this.apiBase + path;
122
+ var attempt = 0;
123
+
124
+ function once() {
125
+ var controller = typeof AbortController !== 'undefined' ? new AbortController() : null;
126
+ var timer = controller ? setTimeout(function () { controller.abort(); }, self.timeout) : null;
127
+
128
+ var headers = {
129
+ 'X-API-Key': self.apiKey,
130
+ 'Accept': 'application/json'
131
+ };
132
+ var payload;
133
+ if (body !== undefined && body !== null && method !== 'GET') {
134
+ if (typeof FormData !== 'undefined' && body instanceof FormData) {
135
+ payload = body; // browser sets the multipart boundary itself
136
+ } else {
137
+ headers['Content-Type'] = 'application/json';
138
+ payload = JSON.stringify(body);
139
+ }
140
+ }
141
+
142
+ return self._fetch(url, {
143
+ method: method,
144
+ headers: headers,
145
+ body: payload,
146
+ signal: controller ? controller.signal : undefined,
147
+ credentials: 'omit'
148
+ }).then(function (res) {
149
+ if (timer) clearTimeout(timer);
150
+ return res.text().then(function (text) {
151
+ var data = null;
152
+ try { data = text ? JSON.parse(text) : null; } catch (e) { /* non-JSON */ }
153
+
154
+ // Capture quota telemetry on every response.
155
+ if (res.headers && res.headers.get) {
156
+ var limit = parseInt(res.headers.get('x-ratelimit-limit'), 10);
157
+ var remaining = parseInt(res.headers.get('x-ratelimit-remaining'), 10);
158
+ var reset = parseInt(res.headers.get('x-ratelimit-reset'), 10);
159
+ if (!isNaN(limit)) {
160
+ self.lastRateLimit = {
161
+ limit: limit,
162
+ remaining: isNaN(remaining) ? null : remaining,
163
+ reset: isNaN(reset) ? null : reset
164
+ };
165
+ }
166
+ }
167
+
168
+ if (res.ok && data && data.success === false) {
169
+ // 2xx with success:false — treat as an API error (rare).
170
+ throw new AdsboosterError(data.message || data.error || 'Request failed', {
171
+ status: res.status, code: data.error || null, details: data,
172
+ rateLimit: self.lastRateLimit
173
+ });
174
+ }
175
+ if (!res.ok) {
176
+ throw new AdsboosterError(
177
+ (data && (data.message || data.error)) || ('HTTP ' + res.status),
178
+ { status: res.status, code: data && data.error || null, details: data, rateLimit: self.lastRateLimit }
179
+ );
180
+ }
181
+ return data;
182
+ });
183
+ }).catch(function (err) {
184
+ if (timer) clearTimeout(timer);
185
+ if (err instanceof AdsboosterError) throw err;
186
+ var aborted = controller && controller.signal && controller.signal.aborted;
187
+ throw new AdsboosterError(
188
+ aborted ? 'Request timed out after ' + self.timeout + 'ms' : ('Network error: ' + (err && err.message)),
189
+ { status: 0, code: aborted ? 'Timeout' : 'NetworkError' }
190
+ );
191
+ });
192
+ }
193
+
194
+ function run() {
195
+ return once().catch(function (err) {
196
+ var retryable = err.isRateLimit || (err.status >= 500 && err.status < 600) || err.status === 0;
197
+ if (retryable && attempt < self.maxRetries) {
198
+ attempt += 1;
199
+ var wait = err.isRateLimit
200
+ ? (parseRetryAfter(err.details && err.details.__headers) || 5000 * attempt)
201
+ : Math.min(8000, 400 * Math.pow(2, attempt - 1));
202
+ return sleep(wait).then(run);
203
+ }
204
+ throw err;
205
+ });
206
+ }
207
+ return run();
208
+ };
209
+
210
+ /* ───────────────────── Resource: Campaigns ──────────────────── */
211
+
212
+ function CampaignsResource(client) { this._c = client; }
213
+
214
+ /** GET /campaigns.php — list your campaigns. opts: { page, limit, status } */
215
+ CampaignsResource.prototype.list = function (opts) {
216
+ return this._c.request('GET', '/campaigns.php' + buildQuery(opts || {}));
217
+ };
218
+
219
+ /** GET /campaigns.php?id=N — fetch a single campaign. */
220
+ CampaignsResource.prototype.get = function (id) {
221
+ return this._c.request('GET', '/campaigns.php?id=' + encodeURIComponent(id));
222
+ };
223
+
224
+ /** POST /campaigns.php — create a campaign. */
225
+ CampaignsResource.prototype.create = function (payload) {
226
+ if (!isObject(payload)) {
227
+ return Promise.reject(new AdsboosterError('campaigns.create() expects a payload object.'));
228
+ }
229
+ return this._c.request('POST', '/campaigns.php', payload);
230
+ };
231
+
232
+ /** PUT /campaigns.php?id=N — update (status, budget, targeting…). */
233
+ CampaignsResource.prototype.update = function (id, payload) {
234
+ if (!isObject(payload)) {
235
+ return Promise.reject(new AdsboosterError('campaigns.update() expects a payload object.'));
236
+ }
237
+ return this._c.request('PUT', '/campaigns.php?id=' + encodeURIComponent(id), payload);
238
+ };
239
+
240
+ /** Convenience: pause a campaign. */
241
+ CampaignsResource.prototype.pause = function (id) {
242
+ return this.update(id, { status: 'paused' });
243
+ };
244
+
245
+ /** Convenience: resume a paused campaign. */
246
+ CampaignsResource.prototype.resume = function (id) {
247
+ return this.update(id, { status: 'active' });
248
+ };
249
+
250
+ /* ─────────────────────── Resource: Posts ────────────────────── */
251
+
252
+ function PostsResource(client) { this._c = client; }
253
+
254
+ /** GET /posts.php — list social posts. opts: { page, limit, status } */
255
+ PostsResource.prototype.list = function (opts) {
256
+ return this._c.request('GET', '/posts.php' + buildQuery(opts || {}));
257
+ };
258
+
259
+ /** GET /posts.php?id=N — fetch a single post. */
260
+ PostsResource.prototype.get = function (id) {
261
+ return this._c.request('GET', '/posts.php?id=' + encodeURIComponent(id));
262
+ };
263
+
264
+ /** POST /posts.php — create / publish / schedule a post. */
265
+ PostsResource.prototype.create = function (payload) {
266
+ if (!isObject(payload)) {
267
+ return Promise.reject(new AdsboosterError('posts.create() expects a payload object.'));
268
+ }
269
+ return this._c.request('POST', '/posts.php', payload);
270
+ };
271
+
272
+ /** DELETE /posts.php?id=N — remove a scheduled post. */
273
+ PostsResource.prototype.remove = function (id) {
274
+ return this._c.request('DELETE', '/posts.php?id=' + encodeURIComponent(id));
275
+ };
276
+
277
+ /* ──────────────────────── Resource: UGC ─────────────────────── */
278
+
279
+ function UgcResource(client) { this._c = client; }
280
+
281
+ /** GET /ugc.php — recent AI creatives. opts: { limit ≤ 50 } */
282
+ UgcResource.prototype.listRecent = function (opts) {
283
+ return this._c.request('GET', '/ugc.php' + buildQuery(opts || {}));
284
+ };
285
+
286
+ /**
287
+ * POST /ugc.php — generate a photoreal UGC image (Nano Banana 2 stack).
288
+ * payload: { product_name, ugc_style, creator_persona, aspect_ratio, target_audience, headline, badge_text }
289
+ */
290
+ UgcResource.prototype.generateImage = function (payload) {
291
+ if (!payload || !payload.product_name) {
292
+ return Promise.reject(new AdsboosterError("ugc.generateImage() requires { product_name: '…' }."));
293
+ }
294
+ return this._c.request('POST', '/ugc.php', payload);
295
+ };
296
+
297
+ /* ────────────────────── Resource: Credits ───────────────────── */
298
+
299
+ function CreditsResource(client) { this._c = client; }
300
+
301
+ /** GET /credits.php — ad + AI credit balance for the API key's account. */
302
+ CreditsResource.prototype.balance = function () {
303
+ return this._c.request('GET', '/credits.php');
304
+ };
305
+
306
+ /* ───────────────────── Resource: Directory ──────────────────── */
307
+
308
+ function DirectoryResource(client) { this._c = client; }
309
+
310
+ /** GET /directory.php — browse the verified business directory. opts: { category, q, limit } */
311
+ DirectoryResource.prototype.list = function (opts) {
312
+ return this._c.request('GET', '/directory.php' + buildQuery(opts || {}));
313
+ };
314
+
315
+ /** POST /directory.php — submit a marketplace listing. */
316
+ DirectoryResource.prototype.createListing = function (payload) {
317
+ if (!isObject(payload)) {
318
+ return Promise.reject(new AdsboosterError('directory.createListing() expects a payload object.'));
319
+ }
320
+ return this._c.request('POST', '/directory.php', payload);
321
+ };
322
+
323
+ /* ────────────────────── Resource: Reviews ───────────────────── */
324
+
325
+ function ReviewsResource(client) { this._c = client; }
326
+
327
+ /** GET /reviews.php — approved community reviews. opts: { limit } */
328
+ ReviewsResource.prototype.list = function (opts) {
329
+ return this._c.request('GET', '/reviews.php' + buildQuery(opts || {}));
330
+ };
331
+
332
+ /** POST /reviews.php — submit or update your platform review. */
333
+ ReviewsResource.prototype.submit = function (payload) {
334
+ if (!payload || !payload.rating || !payload.review_text) {
335
+ return Promise.reject(new AdsboosterError('reviews.submit() requires { rating: 1-5, review_text: "…" }.'));
336
+ }
337
+ return this._c.request('POST', '/reviews.php', payload);
338
+ };
339
+
340
+ /* ──────────────── Resource: Server-side tracking ────────────── */
341
+
342
+ function CapiResource(client) { this._c = client; }
343
+
344
+ /** URL of the drop-in browser tracking script (Meta CAPI / TikTok Events bridge). */
345
+ CapiResource.prototype.scriptUrl = function () {
346
+ return this._c.apiBase + '/capi-bridge.php?js=1';
347
+ };
348
+
349
+ /**
350
+ * POST /capi-bridge.php — forward a conversion event server-side.
351
+ * payload: { pixel_token, event_name, user_data, custom_data, source_url }
352
+ */
353
+ CapiResource.prototype.track = function (payload) {
354
+ if (!payload || !payload.pixel_token) {
355
+ return Promise.reject(new AdsboosterError('capi.track() requires { pixel_token: "…" } from your Server Tracking console.'));
356
+ }
357
+ return this._c.request('POST', '/capi-bridge.php', payload);
358
+ };
359
+
360
+ /* ───────────────────── Resource: Webhooks ───────────────────── */
361
+
362
+ function WebhooksResource(client) { this._c = client; }
363
+
364
+ /**
365
+ * GET /webhooks.php — list your registered webhook endpoints.
366
+ * Result: { success, endpoints: [{ id, event_type, target_url, is_active, created_at }] }
367
+ */
368
+ WebhooksResource.prototype.list = function () {
369
+ return this._c.request('GET', '/webhooks.php');
370
+ };
371
+
372
+ /**
373
+ * GET /webhooks.php?ping=<url> — fire a signed test ping to one of YOUR
374
+ * registered https:// endpoints. Authenticated with the API key, so no
375
+ * console session is needed. Returns { success, http_code, latency_ms,
376
+ * signature, response, error }.
377
+ */
378
+ WebhooksResource.prototype.test = function (targetUrl) {
379
+ if (!targetUrl || typeof targetUrl !== 'string' || targetUrl.indexOf('https://') !== 0) {
380
+ return Promise.reject(new AdsboosterError('webhooks.test() requires an exact https:// target_url registered to your account.'));
381
+ }
382
+ return this._c.request('GET', '/webhooks.php?ping=' + encodeURIComponent(targetUrl));
383
+ };
384
+
385
+ /**
386
+ * @deprecated SDK-side URL builder for the old console flow — the console
387
+ * tests endpoints server-side via the session (api-settings form POST),
388
+ * which an API key cannot sign for. Prefer webhooks.test(url).
389
+ */
390
+ WebhooksResource.prototype.testPingUrl = function (targetUrl) {
391
+ return Promise.resolve(this._c.apiBase + '/webhooks.php?ping=' + encodeURIComponent(targetUrl));
392
+ };
393
+
394
+ /* ───────────────── Public aggregate analytics (no auth) ─────── */
395
+
396
+ /**
397
+ * Feed of the SAME live aggregate analytics the homepage achievements
398
+ * modal renders — totals, performance ratios, 14-day trend, campaign
399
+ * status mix and platform mix. Zero user-level data; no API key is
400
+ * actually required server-side (the client key is simply ignored).
401
+ * Rate limit: 30 requests/minute/IP → 429 with Retry-After.
402
+ */
403
+ function PublicAnalyticsResource(client) { this._c = client; }
404
+
405
+ /**
406
+ * Fetch the aggregate analytics feed.
407
+ * @param {string} [view] one of 'all' | 'campaigns' | 'users' | 'platforms' | 'success' —
408
+ * selects which sections the homepage modal focuses on.
409
+ * @returns {Promise<object>} { success, generated, homepage, analytics, _meta }
410
+ */
411
+ PublicAnalyticsResource.prototype.feed = function (view) {
412
+ var suffix = view ? '?view=' + encodeURIComponent(String(view)) : '';
413
+ return this._c.request('GET', '/public-analytics.php' + suffix, undefined, { publicEndpoint: true });
414
+ };
415
+
416
+ /** Convenience: the analytics section only (totals, performance, trend, mixes). */
417
+ PublicAnalyticsResource.prototype.totals = function () {
418
+ return this.feed('all').then(function (j) {
419
+ return j && j.analytics ? j.analytics : j;
420
+ });
421
+ };
422
+
423
+ /* ───────────────────── Webhook verification ─────────────────── */
424
+
425
+ var webhooks = {
426
+ /**
427
+ * Verify an inbound webhook signature (X-Adsbooster-Signature header).
428
+ * Node-only: use `crypto.timingSafeEqual`. Provide the RAW request body.
429
+ * Signature format: "sha256=<hex hmac of raw body using the webhook secret>".
430
+ */
431
+ verify: function (rawBody, signature, secret) {
432
+ if (typeof signature !== 'string' || signature.indexOf('sha256=') !== 0) {
433
+ return false;
434
+ }
435
+ var expected = signature.slice(7);
436
+ var cryptoApi = null;
437
+ try { cryptoApi = require('crypto'); } catch (e) { /* browser */ }
438
+ if (!cryptoApi || !cryptoApi.createHmac) {
439
+ throw new AdsboosterError('Webhook signatures must be verified server-side in Node.js (crypto is unavailable here).');
440
+ }
441
+ var hmac = cryptoApi.createHmac('sha256', secret).update(String(rawBody), 'utf8').digest('hex');
442
+ var a = Buffer.from(hmac);
443
+ var b = Buffer.from(String(expected));
444
+ if (a.length !== b.length) return false;
445
+ return cryptoApi.timingSafeEqual(a, b);
446
+ },
447
+
448
+ /** Build the expected signature yourself (useful for tests). */
449
+ sign: function (rawBody, secret) {
450
+ var cryptoApi = null;
451
+ try { cryptoApi = require('crypto'); } catch (e) { /* browser */ }
452
+ if (!cryptoApi || !cryptoApi.createHmac) {
453
+ throw new AdsboosterError('webhooks.sign() is Node-only.');
454
+ }
455
+ return 'sha256=' + cryptoApi.createHmac('sha256', secret).update(String(rawBody), 'utf8').digest('hex');
456
+ }
457
+ };
458
+
459
+ /* ────────────────────────── Exports ─────────────────────────── */
460
+
461
+ var API = {
462
+ VERSION: VERSION,
463
+ Adsbooster: Adsbooster,
464
+ AdsboosterError: AdsboosterError,
465
+ AI_CREDITS_PER_USD: AI_CREDITS_PER_USD,
466
+ webhooks: webhooks,
467
+ /** Back-compat alias */
468
+ default: Adsbooster
469
+ };
470
+
471
+
472
+ export { VERSION, Adsbooster, AdsboosterError, AI_CREDITS_PER_USD, webhooks };
473
+ export default Adsbooster;