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 +21 -0
- package/README.md +187 -3
- package/dist/adsbooster.esm.js +473 -0
- package/dist/adsbooster.js +491 -0
- package/examples/browser-ugc.html +54 -0
- package/examples/express-webhooks.js +45 -0
- package/examples/node-create-campaign.js +52 -0
- package/examples/node-smoke-test.js +55 -0
- package/package.json +29 -5
- package/src/adsbooster.js +491 -0
- package/types/index.d.ts +193 -0
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
|
-
#
|
|
2
|
-
|
|
3
|
-
|
|
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;
|