@wavebirdai/api 0.1.0-beta.7 → 0.1.0-beta.9
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 +2 -2
- package/dist/browser.js +11 -3
- package/dist/server.js +9 -2
- package/package.json +1 -1
- package/src/browser.js +11 -3
- package/src/server.js +9 -2
package/README.md
CHANGED
|
@@ -9,7 +9,7 @@ You need a saved Wavebird Test project and placement in Dashboard, a Server Test
|
|
|
9
9
|
1. Install the package and generate the route files for your app directory:
|
|
10
10
|
|
|
11
11
|
```sh
|
|
12
|
-
npm install @wavebirdai/api@0.1.0-beta.
|
|
12
|
+
npm install @wavebirdai/api@0.1.0-beta.9
|
|
13
13
|
./node_modules/.bin/wavebird-next init --lang ts --root . --app-dir src/app
|
|
14
14
|
```
|
|
15
15
|
|
|
@@ -70,7 +70,7 @@ You need a saved Wavebird Test project and placement in Dashboard, a Server Test
|
|
|
70
70
|
5. Validate in Test: confirm the saved format and size, consent and GPC behavior, No-Fill, Stop/abort, and late responses. The generation result must continue independently of the ad path. Move to another package version or Live only through your normal project approval process.
|
|
71
71
|
|
|
72
72
|
JavaScript and TypeScript use this same package. Complete Next.js JavaScript and TypeScript examples are included in `node_modules/@wavebirdai/api/examples/next-js` and `node_modules/@wavebirdai/api/examples/next-ts`. For other frameworks, use the explicit `@wavebirdai/api/server` and `@wavebirdai/api/browser` exports described below. The [REST reference](https://wavebird.ai/api/reference/endpoints) covers integrations without this package. The established [`wavebird`](https://www.npmjs.com/package/wavebird) SDK is a separate package.
|
|
73
|
-
|
|
73
|
+
|
|
74
74
|
## Server and browser boundary
|
|
75
75
|
|
|
76
76
|
`@wavebirdai/api/server` exports `createPublisherServer`. Configure it with server-only Project ID, Server Test or production Key, preference signing secret, the publisher's real request authorization function, exact `publicOrigins`, and the saved project/placement values shown in the dashboard. The origin list is required. The package compares the browser Origin with a configured public origin whose host matches the route Host, even when Next.js or a proxy gives the route an internal URL. `localhost` and `127.0.0.1` are separate origins, and each port and scheme must match exactly; HTTP loopback is available only with a Server Test Key. A production key also requires its separately approved project and Wavebird server authority. Omit `bidfloor` and `bidfloorCurrency` to inherit saved pricing. An explicit override supplies both a nonnegative amount, including zero, and a three-letter currency; the API validates it against the saved project. It returns authenticated `placementPOST`, `consentGET`, `consentPOST`, and `withGeneration` handlers, plus the server-only `reportGeneration(jobId, event)` method. The helper constructs the existing `/v1/placements`, `/v1/consent`, and `/v1/jobs/{id}/generation/{event}` requests; it does not introduce a new Wavebird protocol. The Wavebird API remains authoritative for saved scope, creative formats, duration, fallback, and financial classification.
|
package/dist/browser.js
CHANGED
|
@@ -20,12 +20,19 @@ function loadRenderer(url) {
|
|
|
20
20
|
|
|
21
21
|
export function createPublisherAds(options) {
|
|
22
22
|
const target = options.target;
|
|
23
|
-
|
|
23
|
+
const requiredText = value => typeof value === "string" && value.trim().length > 0;
|
|
24
|
+
if (!target || !requiredText(options.userId) || !requiredText(options.conversationId))
|
|
25
|
+
throw new Error("Application container and session are required");
|
|
24
26
|
if (!["chat", "code", "image", "voice", "agent"].includes(options.jobType)) throw new Error("publisher_job_invalid");
|
|
27
|
+
if (typeof options.clientId !== "string" || !options.clientId.startsWith("wbproj_"))
|
|
28
|
+
throw new Error("publisher_project_invalid");
|
|
29
|
+
if (typeof options.surfaceId !== "string" || !/^[A-Za-z0-9_-]{1,128}$/.test(options.surfaceId))
|
|
30
|
+
throw new Error("publisher_placement_invalid");
|
|
25
31
|
const turnBinding = options.turnBinding === true;
|
|
26
32
|
const validId = value => typeof value === "string" && /^[A-Za-z0-9_-]{1,128}$/.test(value);
|
|
27
33
|
if (turnBinding && !validId(options.sessionId)) throw new Error("publisher_trusted_session_required");
|
|
28
|
-
const timing = options.timing
|
|
34
|
+
const timing = options.timing === undefined ? "during" : options.timing;
|
|
35
|
+
const consentMode = options.consentMode === undefined ? "wavebird_consent" : options.consentMode;
|
|
29
36
|
if (!["before", "during", "after"].includes(timing) || !["wavebird_consent", "own_cmp"].includes(consentMode)) throw new Error("Invalid publisher configuration");
|
|
30
37
|
let binding = { userId: options.userId, conversationId: options.conversationId,
|
|
31
38
|
sessionId: turnBinding ? options.sessionId : crypto.randomUUID() };
|
|
@@ -64,7 +71,8 @@ export function createPublisherAds(options) {
|
|
|
64
71
|
const prepare = () => { if (!disposed && sponsor) void sponsor.prepare().catch(() => {}); };
|
|
65
72
|
function setConversation(next) {
|
|
66
73
|
if (disposed) return;
|
|
67
|
-
if (!next
|
|
74
|
+
if (!requiredText(next?.userId) || !requiredText(next?.conversationId))
|
|
75
|
+
throw new Error("Application session is required");
|
|
68
76
|
if (turnBinding && !validId(next.sessionId)) throw new Error("publisher_trusted_session_required");
|
|
69
77
|
if (next.userId === binding.userId && next.conversationId === binding.conversationId &&
|
|
70
78
|
(!turnBinding || next.sessionId === binding.sessionId)) return;
|
package/dist/server.js
CHANGED
|
@@ -7,6 +7,9 @@ const PREFERENCE_TTL = 30 * 24 * 60 * 60;
|
|
|
7
7
|
const MAX_PUBLISHER_BODY_BYTES = 16 * 1024;
|
|
8
8
|
const MAX_WAVEBIRD_RESPONSE_BYTES = 256 * 1024;
|
|
9
9
|
const MAX_PARALLEL_WAVEBIRD_REQUESTS = 2;
|
|
10
|
+
const MAX_ALLOWED_SIZES = 8;
|
|
11
|
+
const MAX_SIZE_EDGE = 4096;
|
|
12
|
+
const MAX_ENVELOPE_EDGE = 8192;
|
|
10
13
|
const ID = /^[A-Za-z0-9_-]{1,128}$/;
|
|
11
14
|
const JOB_ID = /^job_[A-Za-z0-9_-]{1,128}$/;
|
|
12
15
|
const PLACEMENT_KEY = /^[a-z0-9][a-z0-9_-]{0,63}$/;
|
|
@@ -39,11 +42,15 @@ function normalizeOptions(options) {
|
|
|
39
42
|
if (options.gdprApplies !== undefined && typeof options.gdprApplies !== "boolean") throw new Error("publisher_gdpr_invalid");
|
|
40
43
|
if (!Array.isArray(options.allowedFormats) || options.allowedFormats.length === 0 || options.allowedFormats.some(value => !["banner", "native", "clip"].includes(value))) throw new Error("publisher_formats_invalid");
|
|
41
44
|
if (!["above", "below", "sidebar", "between"].includes(options.positionHint)) throw new Error("publisher_position_invalid");
|
|
42
|
-
if (!Array.isArray(options.allowedSizes) || options.allowedSizes.length === 0 || options.allowedSizes.some(size => !size || typeof size !== "object" || Object.keys(size).some(key => !["width", "height", "formats"].includes(key)) || !Number.isSafeInteger(size.width) || !Number.isSafeInteger(size.height) || size.width < 1 || size.height < 1 ||
|
|
45
|
+
if (!Array.isArray(options.allowedSizes) || options.allowedSizes.length === 0 || options.allowedSizes.length > MAX_ALLOWED_SIZES || options.allowedSizes.some(size => !size || typeof size !== "object" || Object.keys(size).some(key => !["width", "height", "formats"].includes(key)) || !Number.isSafeInteger(size.width) || !Number.isSafeInteger(size.height) || size.width < 1 || size.height < 1 || size.width > MAX_SIZE_EDGE || size.height > MAX_SIZE_EDGE ||
|
|
43
46
|
(size.formats !== undefined && (!Array.isArray(size.formats) || size.formats.length === 0 || size.formats.some(format => !options.allowedFormats.includes(format)))))) throw new Error("publisher_sizes_invalid");
|
|
47
|
+
if (new Set(options.allowedSizes.map(size => `${size.width}x${size.height}`)).size !== options.allowedSizes.length)
|
|
48
|
+
throw new Error("publisher_sizes_invalid");
|
|
44
49
|
const maxWidth = options.maxWidth === undefined ? options.allowedSizes.reduce((max, size) => Math.max(max, size.width), 0) : options.maxWidth;
|
|
45
50
|
const maxHeight = options.maxHeight === undefined ? options.allowedSizes.reduce((max, size) => Math.max(max, size.height), 0) : options.maxHeight;
|
|
46
|
-
if (!Number.isSafeInteger(maxWidth) || maxWidth < 1 ||
|
|
51
|
+
if (!Number.isSafeInteger(maxWidth) || maxWidth < 1 || maxWidth > MAX_ENVELOPE_EDGE ||
|
|
52
|
+
!Number.isSafeInteger(maxHeight) || maxHeight < 1 || maxHeight > MAX_ENVELOPE_EDGE)
|
|
53
|
+
throw new Error("publisher_envelope_invalid");
|
|
47
54
|
const eligibleSizes = options.allowedSizes.filter(size => size.width <= maxWidth && size.height <= maxHeight);
|
|
48
55
|
const hasFloor = Object.hasOwn(options, "bidfloor");
|
|
49
56
|
const hasCurrency = Object.hasOwn(options, "bidfloorCurrency");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wavebirdai/api",
|
|
3
|
-
"version": "0.1.0-beta.
|
|
3
|
+
"version": "0.1.0-beta.9",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "Integrate Banner, Native, and Clip placements into authenticated chat apps with server-side keys, consent, and generation lifecycle handling",
|
package/src/browser.js
CHANGED
|
@@ -20,12 +20,19 @@ function loadRenderer(url) {
|
|
|
20
20
|
|
|
21
21
|
export function createPublisherAds(options) {
|
|
22
22
|
const target = options.target;
|
|
23
|
-
|
|
23
|
+
const requiredText = value => typeof value === "string" && value.trim().length > 0;
|
|
24
|
+
if (!target || !requiredText(options.userId) || !requiredText(options.conversationId))
|
|
25
|
+
throw new Error("Application container and session are required");
|
|
24
26
|
if (!["chat", "code", "image", "voice", "agent"].includes(options.jobType)) throw new Error("publisher_job_invalid");
|
|
27
|
+
if (typeof options.clientId !== "string" || !options.clientId.startsWith("wbproj_"))
|
|
28
|
+
throw new Error("publisher_project_invalid");
|
|
29
|
+
if (typeof options.surfaceId !== "string" || !/^[A-Za-z0-9_-]{1,128}$/.test(options.surfaceId))
|
|
30
|
+
throw new Error("publisher_placement_invalid");
|
|
25
31
|
const turnBinding = options.turnBinding === true;
|
|
26
32
|
const validId = value => typeof value === "string" && /^[A-Za-z0-9_-]{1,128}$/.test(value);
|
|
27
33
|
if (turnBinding && !validId(options.sessionId)) throw new Error("publisher_trusted_session_required");
|
|
28
|
-
const timing = options.timing
|
|
34
|
+
const timing = options.timing === undefined ? "during" : options.timing;
|
|
35
|
+
const consentMode = options.consentMode === undefined ? "wavebird_consent" : options.consentMode;
|
|
29
36
|
if (!["before", "during", "after"].includes(timing) || !["wavebird_consent", "own_cmp"].includes(consentMode)) throw new Error("Invalid publisher configuration");
|
|
30
37
|
let binding = { userId: options.userId, conversationId: options.conversationId,
|
|
31
38
|
sessionId: turnBinding ? options.sessionId : crypto.randomUUID() };
|
|
@@ -64,7 +71,8 @@ export function createPublisherAds(options) {
|
|
|
64
71
|
const prepare = () => { if (!disposed && sponsor) void sponsor.prepare().catch(() => {}); };
|
|
65
72
|
function setConversation(next) {
|
|
66
73
|
if (disposed) return;
|
|
67
|
-
if (!next
|
|
74
|
+
if (!requiredText(next?.userId) || !requiredText(next?.conversationId))
|
|
75
|
+
throw new Error("Application session is required");
|
|
68
76
|
if (turnBinding && !validId(next.sessionId)) throw new Error("publisher_trusted_session_required");
|
|
69
77
|
if (next.userId === binding.userId && next.conversationId === binding.conversationId &&
|
|
70
78
|
(!turnBinding || next.sessionId === binding.sessionId)) return;
|
package/src/server.js
CHANGED
|
@@ -7,6 +7,9 @@ const PREFERENCE_TTL = 30 * 24 * 60 * 60;
|
|
|
7
7
|
const MAX_PUBLISHER_BODY_BYTES = 16 * 1024;
|
|
8
8
|
const MAX_WAVEBIRD_RESPONSE_BYTES = 256 * 1024;
|
|
9
9
|
const MAX_PARALLEL_WAVEBIRD_REQUESTS = 2;
|
|
10
|
+
const MAX_ALLOWED_SIZES = 8;
|
|
11
|
+
const MAX_SIZE_EDGE = 4096;
|
|
12
|
+
const MAX_ENVELOPE_EDGE = 8192;
|
|
10
13
|
const ID = /^[A-Za-z0-9_-]{1,128}$/;
|
|
11
14
|
const JOB_ID = /^job_[A-Za-z0-9_-]{1,128}$/;
|
|
12
15
|
const PLACEMENT_KEY = /^[a-z0-9][a-z0-9_-]{0,63}$/;
|
|
@@ -39,11 +42,15 @@ function normalizeOptions(options) {
|
|
|
39
42
|
if (options.gdprApplies !== undefined && typeof options.gdprApplies !== "boolean") throw new Error("publisher_gdpr_invalid");
|
|
40
43
|
if (!Array.isArray(options.allowedFormats) || options.allowedFormats.length === 0 || options.allowedFormats.some(value => !["banner", "native", "clip"].includes(value))) throw new Error("publisher_formats_invalid");
|
|
41
44
|
if (!["above", "below", "sidebar", "between"].includes(options.positionHint)) throw new Error("publisher_position_invalid");
|
|
42
|
-
if (!Array.isArray(options.allowedSizes) || options.allowedSizes.length === 0 || options.allowedSizes.some(size => !size || typeof size !== "object" || Object.keys(size).some(key => !["width", "height", "formats"].includes(key)) || !Number.isSafeInteger(size.width) || !Number.isSafeInteger(size.height) || size.width < 1 || size.height < 1 ||
|
|
45
|
+
if (!Array.isArray(options.allowedSizes) || options.allowedSizes.length === 0 || options.allowedSizes.length > MAX_ALLOWED_SIZES || options.allowedSizes.some(size => !size || typeof size !== "object" || Object.keys(size).some(key => !["width", "height", "formats"].includes(key)) || !Number.isSafeInteger(size.width) || !Number.isSafeInteger(size.height) || size.width < 1 || size.height < 1 || size.width > MAX_SIZE_EDGE || size.height > MAX_SIZE_EDGE ||
|
|
43
46
|
(size.formats !== undefined && (!Array.isArray(size.formats) || size.formats.length === 0 || size.formats.some(format => !options.allowedFormats.includes(format)))))) throw new Error("publisher_sizes_invalid");
|
|
47
|
+
if (new Set(options.allowedSizes.map(size => `${size.width}x${size.height}`)).size !== options.allowedSizes.length)
|
|
48
|
+
throw new Error("publisher_sizes_invalid");
|
|
44
49
|
const maxWidth = options.maxWidth === undefined ? options.allowedSizes.reduce((max, size) => Math.max(max, size.width), 0) : options.maxWidth;
|
|
45
50
|
const maxHeight = options.maxHeight === undefined ? options.allowedSizes.reduce((max, size) => Math.max(max, size.height), 0) : options.maxHeight;
|
|
46
|
-
if (!Number.isSafeInteger(maxWidth) || maxWidth < 1 ||
|
|
51
|
+
if (!Number.isSafeInteger(maxWidth) || maxWidth < 1 || maxWidth > MAX_ENVELOPE_EDGE ||
|
|
52
|
+
!Number.isSafeInteger(maxHeight) || maxHeight < 1 || maxHeight > MAX_ENVELOPE_EDGE)
|
|
53
|
+
throw new Error("publisher_envelope_invalid");
|
|
47
54
|
const eligibleSizes = options.allowedSizes.filter(size => size.width <= maxWidth && size.height <= maxHeight);
|
|
48
55
|
const hasFloor = Object.hasOwn(options, "bidfloor");
|
|
49
56
|
const hasCurrency = Object.hasOwn(options, "bidfloorCurrency");
|