@trillboards/api-client 0.1.3 → 0.3.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 +40 -8
- package/dist/index.d.mts +555 -27
- package/dist/index.d.ts +555 -27
- package/dist/index.js +211 -25
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +209 -26
- package/dist/index.mjs.map +1 -1
- package/package.json +7 -2
package/dist/index.js
CHANGED
|
@@ -76,6 +76,47 @@ var VERSION = "0.1.3";
|
|
|
76
76
|
|
|
77
77
|
// src/http.ts
|
|
78
78
|
var PUBLIC_PATHS = ["/v1/errors", "/v1/changelog", "/api/discover", "/v1/health"];
|
|
79
|
+
function isRetryable(err) {
|
|
80
|
+
if (err instanceof TrillboardsError) {
|
|
81
|
+
return err.statusCode === 429 || err.statusCode >= 500;
|
|
82
|
+
}
|
|
83
|
+
if (err instanceof TypeError) return true;
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
function computeBackoffMs(attempt, baseMs, err) {
|
|
87
|
+
if (err instanceof TrillboardsError && err.retryAfter != null && err.retryAfter > 0) {
|
|
88
|
+
return err.retryAfter * 1e3;
|
|
89
|
+
}
|
|
90
|
+
const exponential = baseMs * Math.pow(2, attempt);
|
|
91
|
+
return Math.floor(Math.random() * exponential);
|
|
92
|
+
}
|
|
93
|
+
function sleep(ms) {
|
|
94
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
95
|
+
}
|
|
96
|
+
async function retryWithBackoff(operation, opts = {}) {
|
|
97
|
+
const retries = opts.retries ?? 6;
|
|
98
|
+
const baseMs = opts.baseMs ?? 1e3;
|
|
99
|
+
let lastError;
|
|
100
|
+
for (let attempt = 0; attempt <= retries; attempt += 1) {
|
|
101
|
+
try {
|
|
102
|
+
return await operation();
|
|
103
|
+
} catch (err) {
|
|
104
|
+
lastError = err;
|
|
105
|
+
if (!isRetryable(err) || attempt === retries) {
|
|
106
|
+
throw err;
|
|
107
|
+
}
|
|
108
|
+
if (opts.onAttemptError) {
|
|
109
|
+
try {
|
|
110
|
+
opts.onAttemptError(attempt + 1, err);
|
|
111
|
+
} catch {
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
const delayMs = computeBackoffMs(attempt, baseMs, err);
|
|
115
|
+
await sleep(delayMs);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
throw lastError;
|
|
119
|
+
}
|
|
79
120
|
var HttpClient = class {
|
|
80
121
|
constructor(config) {
|
|
81
122
|
this.config = config;
|
|
@@ -88,8 +129,9 @@ var HttpClient = class {
|
|
|
88
129
|
"User-Agent": `trillboards-typescript/${VERSION}`,
|
|
89
130
|
...options?.headers ?? {}
|
|
90
131
|
};
|
|
132
|
+
const callerSuppliedAuth = Object.keys(options?.headers ?? {}).some((h) => h.toLowerCase() === "authorization");
|
|
91
133
|
const isPublic = PUBLIC_PATHS.some((prefix) => path.startsWith(prefix));
|
|
92
|
-
if (!isPublic && this.config.apiKey) {
|
|
134
|
+
if (!isPublic && this.config.apiKey && !callerSuppliedAuth) {
|
|
93
135
|
headers["Authorization"] = `Bearer ${this.config.apiKey}`;
|
|
94
136
|
}
|
|
95
137
|
if (this.config.version) {
|
|
@@ -125,17 +167,28 @@ var HttpClient = class {
|
|
|
125
167
|
get(path, opts) {
|
|
126
168
|
return this.request("GET", path, { params: opts?.params });
|
|
127
169
|
}
|
|
128
|
-
|
|
129
|
-
|
|
170
|
+
/**
|
|
171
|
+
* POST / PUT / PATCH / DELETE auto-retry on 429 + 5xx + network TypeError
|
|
172
|
+
* via `retryWithBackoff()`. Callers can opt into observability by passing
|
|
173
|
+
* `retry.onAttemptError` to receive a callback per transient failure.
|
|
174
|
+
*
|
|
175
|
+
* The retry behavior is identical across all four methods because all of
|
|
176
|
+
* them are state-changing operations subject to the same transient-failure
|
|
177
|
+
* classes (rate limit, upstream-down, network-flap). The wire idempotency
|
|
178
|
+
* of POST vs DELETE is enforced server-side via Idempotency-Key headers,
|
|
179
|
+
* which the caller can attach via `opts.headers`.
|
|
180
|
+
*/
|
|
181
|
+
post(path, body, opts) {
|
|
182
|
+
return retryWithBackoff(() => this.request("POST", path, { body, headers: opts?.headers }), opts?.retry);
|
|
130
183
|
}
|
|
131
|
-
put(path, body) {
|
|
132
|
-
return this.request("PUT", path, { body });
|
|
184
|
+
put(path, body, opts) {
|
|
185
|
+
return retryWithBackoff(() => this.request("PUT", path, { body, headers: opts?.headers }), opts?.retry);
|
|
133
186
|
}
|
|
134
|
-
patch(path, body) {
|
|
135
|
-
return this.request("PATCH", path, { body });
|
|
187
|
+
patch(path, body, opts) {
|
|
188
|
+
return retryWithBackoff(() => this.request("PATCH", path, { body, headers: opts?.headers }), opts?.retry);
|
|
136
189
|
}
|
|
137
|
-
delete(path) {
|
|
138
|
-
return this.request("DELETE", path);
|
|
190
|
+
delete(path, opts) {
|
|
191
|
+
return retryWithBackoff(() => this.request("DELETE", path, { headers: opts?.headers }), opts?.retry);
|
|
139
192
|
}
|
|
140
193
|
buildUrl(path, params) {
|
|
141
194
|
const base = `${this.config.baseUrl}${path}`;
|
|
@@ -196,9 +249,20 @@ var DevicesClient = class {
|
|
|
196
249
|
async delete(deviceId) {
|
|
197
250
|
return this.http.delete(`/v1/partner/device/${deviceId}`);
|
|
198
251
|
}
|
|
199
|
-
/**
|
|
200
|
-
|
|
201
|
-
|
|
252
|
+
/**
|
|
253
|
+
* Send heartbeat for a device.
|
|
254
|
+
*
|
|
255
|
+
* Auto-retries on 429 + 5xx + network TypeError via the shared
|
|
256
|
+
* retryWithBackoff() helper (see http.ts). Pass `opts.retry.onAttemptError`
|
|
257
|
+
* to observe transient failures (e.g. emit an `upload_error` event from
|
|
258
|
+
* the CTV SDK without coupling the retry loop to its event emitter).
|
|
259
|
+
*/
|
|
260
|
+
async heartbeat(deviceId, body, opts) {
|
|
261
|
+
return this.http.post(
|
|
262
|
+
`/v1/partner/device/${deviceId}/heartbeat`,
|
|
263
|
+
body ?? {},
|
|
264
|
+
{ retry: opts?.retry }
|
|
265
|
+
);
|
|
202
266
|
}
|
|
203
267
|
};
|
|
204
268
|
|
|
@@ -382,25 +446,144 @@ var TrillboardsClient = class {
|
|
|
382
446
|
this.billing = new BillingClient(this._http);
|
|
383
447
|
}
|
|
384
448
|
};
|
|
385
|
-
|
|
449
|
+
var DEFAULT_TOLERANCE_SECONDS = 300;
|
|
450
|
+
function parseSignatureHeader(rawSig) {
|
|
451
|
+
const versions = /* @__PURE__ */ new Map();
|
|
452
|
+
let timestamp;
|
|
453
|
+
let legacy;
|
|
454
|
+
const trimmed = rawSig.trim();
|
|
455
|
+
if (trimmed.startsWith("sha256=")) {
|
|
456
|
+
legacy = trimmed.slice("sha256=".length);
|
|
457
|
+
return { timestamp, versions, legacy };
|
|
458
|
+
}
|
|
459
|
+
for (const part of trimmed.split(",")) {
|
|
460
|
+
const eq = part.indexOf("=");
|
|
461
|
+
if (eq < 0) continue;
|
|
462
|
+
const key = part.slice(0, eq).trim();
|
|
463
|
+
const value = part.slice(eq + 1).trim();
|
|
464
|
+
if (!key || !value) continue;
|
|
465
|
+
if (key === "t") {
|
|
466
|
+
const ts = Number(value);
|
|
467
|
+
if (Number.isFinite(ts)) timestamp = ts;
|
|
468
|
+
} else if (/^v\d+$/.test(key)) {
|
|
469
|
+
versions.set(key, value);
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
return { timestamp, versions, legacy };
|
|
473
|
+
}
|
|
474
|
+
function getHeader(headers, name) {
|
|
475
|
+
const lc = name.toLowerCase();
|
|
476
|
+
for (const key of Object.keys(headers)) {
|
|
477
|
+
if (key.toLowerCase() === lc) {
|
|
478
|
+
const v = headers[key];
|
|
479
|
+
if (Array.isArray(v)) return v[0];
|
|
480
|
+
return v;
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
return void 0;
|
|
484
|
+
}
|
|
485
|
+
function constantTimeEqual(a, b) {
|
|
486
|
+
const aBuf = Buffer.from(a, "utf8");
|
|
487
|
+
const bBuf = Buffer.from(b, "utf8");
|
|
488
|
+
if (aBuf.length !== bBuf.length) return false;
|
|
489
|
+
return crypto.timingSafeEqual(aBuf, bBuf);
|
|
490
|
+
}
|
|
491
|
+
function authError(message) {
|
|
492
|
+
return new TrillboardsError({
|
|
493
|
+
type: "authentication_error",
|
|
494
|
+
code: "webhook_signature_invalid",
|
|
495
|
+
message,
|
|
496
|
+
statusCode: 401
|
|
497
|
+
});
|
|
498
|
+
}
|
|
499
|
+
function constructEvent(payload, headers, secret, opts = {}) {
|
|
500
|
+
let signatureHeader;
|
|
501
|
+
let timestampHeader;
|
|
502
|
+
if (typeof headers === "string") {
|
|
503
|
+
signatureHeader = headers;
|
|
504
|
+
} else if (headers && typeof headers === "object") {
|
|
505
|
+
signatureHeader = getHeader(headers, "X-Trillboards-Signature");
|
|
506
|
+
timestampHeader = getHeader(headers, "X-Trillboards-Timestamp");
|
|
507
|
+
}
|
|
508
|
+
if (!signatureHeader) {
|
|
509
|
+
throw authError("Missing X-Trillboards-Signature header.");
|
|
510
|
+
}
|
|
386
511
|
const payloadStr = typeof payload === "string" ? payload : payload.toString("utf8");
|
|
387
|
-
const
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
512
|
+
const parsed = parseSignatureHeader(signatureHeader);
|
|
513
|
+
let ts = parsed.timestamp;
|
|
514
|
+
if (ts === void 0 && timestampHeader) {
|
|
515
|
+
const fromHeader = Number(timestampHeader);
|
|
516
|
+
if (Number.isFinite(fromHeader)) ts = fromHeader;
|
|
517
|
+
}
|
|
518
|
+
if (ts === void 0 || !Number.isFinite(ts)) {
|
|
519
|
+
throw authError(
|
|
520
|
+
"Webhook signature missing timestamp. Provide either `t=<unix_seconds>` inside X-Trillboards-Signature OR a separate X-Trillboards-Timestamp header."
|
|
521
|
+
);
|
|
522
|
+
}
|
|
523
|
+
const tolerance = opts.tolerance ?? DEFAULT_TOLERANCE_SECONDS;
|
|
524
|
+
const now = opts.nowSec ?? Math.floor(Date.now() / 1e3);
|
|
525
|
+
if (Math.abs(now - ts) > tolerance) {
|
|
526
|
+
throw authError(
|
|
527
|
+
`Webhook timestamp ${ts} outside tolerance window of ${tolerance}s (now ${now}). Possible replay attack.`
|
|
528
|
+
);
|
|
529
|
+
}
|
|
530
|
+
const v1Expected = crypto.createHmac("sha256", secret).update(`${ts}.${payloadStr}`).digest("hex");
|
|
531
|
+
const v0Expected = crypto.createHmac("sha256", secret).update(payloadStr).digest("hex");
|
|
532
|
+
const v1Provided = parsed.versions.get("v1");
|
|
533
|
+
const v0Provided = parsed.versions.get("v0");
|
|
534
|
+
if (v1Provided) {
|
|
535
|
+
if (constantTimeEqual(v1Provided, v1Expected)) {
|
|
536
|
+
return JSON.parse(payloadStr);
|
|
537
|
+
}
|
|
538
|
+
throw authError(
|
|
539
|
+
"Webhook v1 signature verification failed. The payload may have been tampered with, or your webhook secret may be incorrect."
|
|
540
|
+
);
|
|
541
|
+
}
|
|
542
|
+
if (v0Provided && constantTimeEqual(v0Provided, v0Expected)) {
|
|
543
|
+
return JSON.parse(payloadStr);
|
|
544
|
+
}
|
|
545
|
+
if (parsed.legacy) {
|
|
546
|
+
if (constantTimeEqual(parsed.legacy, v1Expected)) {
|
|
547
|
+
return JSON.parse(payloadStr);
|
|
548
|
+
}
|
|
549
|
+
if (constantTimeEqual(parsed.legacy, v0Expected)) {
|
|
550
|
+
return JSON.parse(payloadStr);
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
throw authError(
|
|
554
|
+
"Webhook signature verification failed. The payload may have been tampered with, or your webhook secret may be incorrect."
|
|
555
|
+
);
|
|
556
|
+
}
|
|
557
|
+
var legacyWarned = false;
|
|
558
|
+
function constructEventLegacy(payload, signature, secret) {
|
|
559
|
+
if (!legacyWarned) {
|
|
560
|
+
console.warn(
|
|
561
|
+
"[@trillboards/api-client] constructEventLegacy is deprecated and unsafe (no replay protection). Migrate to Webhooks.constructEvent(payload, req.headers, secret). See https://api.trillboards.com/docs/integrations/webhook-signature-verification.md"
|
|
562
|
+
);
|
|
563
|
+
legacyWarned = true;
|
|
564
|
+
}
|
|
565
|
+
const payloadStr = typeof payload === "string" ? payload : payload.toString("utf8");
|
|
566
|
+
const expected = crypto.createHmac("sha256", secret).update(payloadStr).digest("hex");
|
|
567
|
+
if (!constantTimeEqual(signature, expected)) {
|
|
568
|
+
throw authError(
|
|
569
|
+
"Legacy webhook signature mismatch. If your server has been upgraded to the compound-header emitter, switch to Webhooks.constructEvent instead."
|
|
570
|
+
);
|
|
397
571
|
}
|
|
398
572
|
return JSON.parse(payloadStr);
|
|
399
573
|
}
|
|
400
|
-
var Webhooks = {
|
|
574
|
+
var Webhooks = {
|
|
575
|
+
constructEvent,
|
|
576
|
+
constructEventLegacy,
|
|
577
|
+
parseSignatureHeader
|
|
578
|
+
};
|
|
401
579
|
|
|
402
580
|
// src/generated/index.ts
|
|
403
|
-
var generatedTypes = {
|
|
581
|
+
var generatedTypes = {
|
|
582
|
+
schemaCount: 71,
|
|
583
|
+
operationCount: 175,
|
|
584
|
+
schemas: ["Ad", "AdSettings", "AudienceAnalyticsResponse", "AudienceAttentionPrediction", "AudienceExportResponse", "AudienceLoyaltyFeaturesRow", "AudienceMetricsResponse", "AudienceSegment", "AudienceSocialAttention", "BatchResult", "BleScanResultData", "ChangelogEntry", "ChangelogResponse", "ContentPreferencesInput", "ContentPreferencesResponse", "ContentPreset", "DashboardResponse", "DataExportSummaryResponse", "DemographicsResponse", "Device", "DeviceCapabilityPayload", "DeviceSummary", "DisplaySettingsInput", "DisplaySettingsResponse", "EarningsBreakdownResponse", "EarningsResponse", "Error", "HeaderBiddingSettings", "HttpProbeResult", "MdnsNetworkDevice", "Pagination", "Partner", "PartnerAdDeliveryProfile", "PartnerDefaultStream", "PartnerDefaultStreamCreate", "PartnerDefaultStreamUpdate", "PartnerDeviceHeartbeatBody", "PartnerHeartbeatCommand", "PartnerHeartbeatData", "PartnerHeartbeatMeasurementMetadata", "PartnerHeartbeatQueryData", "PartnerHeartbeatQueryResponse", "PartnerHeartbeatRequest", "PartnerHeartbeatResponse", "PartnerHeartbeatTelemetry", "PartnerLBarContent", "PartnerLBarDisplayPreferences", "PartnerLBarSettings", "PartnerSafeHeartbeatTelemetry", "PartnerSelfPromoAd", "PartnerSelfPromoCreate", "PartnerSelfPromoPlacement", "PartnerStats", "PayoutResponse", "PlaygroundResult", "PlaygroundValidationError", "ProgrammaticSettingsInput", "ProgrammaticSettingsResponse", "RequestMetadataResponse", "RolePermissions", "SDKConfig", "ScreenAnalyticsResponse", "SsdpDeviceInfo", "TeamInviteResponse", "TeamMember", "Transaction", "Webhook", "WebhookDelivery", "WebhookEvent", "WifiEnvironmentSnapshot", "WifiScanResult"],
|
|
585
|
+
operations: ["listPartnerSelfPromoAds", "createPartnerSelfPromoAd", "listPartnerSelfPromoPlacements", "schedulePartnerSelfPromo", "createPartnerSelfPromoUpload", "disableSelfPromoAd", "registerAgent", "getAnalytics", "getAudienceContinuity", "getAudienceProfile", "getBleSummary", "getContentPerformance", "searchCreativeCatalog", "getCreativeScenes", "getCrossScreenFlow", "getPartnerAnalyticsDashboard", "getDeviceSuiteSignature", "exportPartnerAnalytics", "exportPartnerAdsByDay", "getFleetHourly", "getHaloFunnel", "getLoyaltyClock", "getNetworkSummary", "getNetworkTrend", "getProximitySummary", "getRevenueAttentionCorrelation", "getSceneFeed", "getSceneIntelligence", "rotateApiKey", "getAudienceAnalytics", "exportAudienceData", "getAudienceHeatmap", "getLiveAudience", "getLookalikeScreens", "predictScreenAudience", "getResolvedHouseholdReach", "listAudienceSegments", "createAudienceSegment", "deleteAudienceSegment", "getAudienceSegment", "updateAudienceSegment", "matchAudienceSegment", "estimateSegmentReach", "generateSegmentVastTag", "getAudienceStats", "cancelBillingSubscription", "purchaseCredits", "createCreditCheckout", "listBillingPlans", "setupBilling", "createBillingSubscription", "getBillingSubscription", "getBillingUsage", "getUsageSummary", "stripeWebhookCallback", "getConnectDashboard", "getConnectEarningsChart", "getConnectScreenHealth", "listContentPresets", "getContentPreset", "getDashboard", "exportProximityGraphEdges", "getDataExportSummary", "exportDataset", "getGeohashAudiencePanel", "registerDevice", "deleteDevice", "getDevice", "updateDevice", "getAdsForDevice", "ackDeviceCommand", "getDeviceHeartbeat", "heartbeat", "recordProgrammaticEvent", "listDevices", "registerDevicesBatch", "batchUpdateDeviceOrientation", "partnerDocsBrowser", "getEarnings", "getEarningsBreakdown", "requestPayout", "getTransactions", "getActiveSSEConnections", "subscribeToEvents", "getFleetAnalytics", "pushFleetCommand", "getFleetOverview", "updateFleetSettings", "recordImpressionGet", "recordImpressionPost", "recordImpressionsBatch", "getPartnerInfo", "listIntentCategories", "getIntentCategory", "getIntentPricing", "getMeasurementMethodology", "getNetworkConfig", "patchNetworkConfig", "updateNetworkConfig", "getNetworkTrends", "partnerPing", "partnerPingAuth", "getPricing", "quickStart", "registerPartner", "generateSandboxEvent", "resetSandbox", "createSandboxScreen", "getSandboxStatus", "getScreenAudienceSegments", "batchUpdateContentPreferences", "batchUpdateDisplaySettings", "batchUpdateProgrammaticSettings", "resolvePartnerScreen", "getScreenAnalytics", "getPartnerScreenAtmosphere", "getPartnerScreenIntelligence", "getPartnerScreenTasteProfile", "getPartnerScreenTraffic", "getPartnerScreenVAS", "applyContentPreset", "getAudienceMetrics", "getContentPreferences", "patchContentPreferences", "updateContentPreferences", "listPartnerDefaultStreams", "createPartnerDefaultStream", "createPartnerDefaultStreamImages", "reorderPartnerDefaultStreams", "createPartnerDefaultStreamUpload", "deletePartnerDefaultStream", "updatePartnerDefaultStream", "getScreenDemographics", "getDisplaySettings", "patchDisplaySettings", "updateDisplaySettings", "getPartnerLBarSettings", "updatePartnerLBarSettings", "updatePartnerLBarLiveUpdates", "getProgrammaticSettings", "patchProgrammaticSettings", "updateProgrammaticSettings", "createStripeConnectAccount", "getStripeLoginLink", "getStripeOnboardingLink", "requestStripePayout", "getStripePayoutHistory", "getStripeAccountStatus", "handleStripeWebhook", "listTeamMembers", "acceptInvitation", "inviteTeamMember", "getInvitationByToken", "resendInvitation", "removeTeamMember", "changeTeamMemberRole", "getTrackingPublicKey", "recordTrackingBatch", "getUsageSummary__get__usage", "getUsageDashboard", "getRateLimitStatus", "queryChangelog", "playgroundDryRun", "getRequestMetadata", "getVastConfig", "getVenuesNearby", "getVenueAtmosphere", "getVenueTraffic", "getVenueIntelligence", "listWebhooks", "createWebhook", "deleteWebhook", "updateWebhook", "getWebhookDeliveries", "testWebhook"]
|
|
586
|
+
};
|
|
404
587
|
|
|
405
588
|
exports.AnalyticsClient = AnalyticsClient;
|
|
406
589
|
exports.AudienceClient = AudienceClient;
|
|
@@ -418,6 +601,9 @@ exports.VERSION = VERSION;
|
|
|
418
601
|
exports.Webhooks = Webhooks;
|
|
419
602
|
exports.WebhooksClient = WebhooksClient;
|
|
420
603
|
exports.constructEvent = constructEvent;
|
|
604
|
+
exports.constructEventLegacy = constructEventLegacy;
|
|
421
605
|
exports.generatedTypes = generatedTypes;
|
|
606
|
+
exports.parseSignatureHeader = parseSignatureHeader;
|
|
607
|
+
exports.retryWithBackoff = retryWithBackoff;
|
|
422
608
|
//# sourceMappingURL=index.js.map
|
|
423
609
|
//# sourceMappingURL=index.js.map
|