Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
116 changes: 49 additions & 67 deletions src/channel/httpBeaconChannel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,19 @@ import type {Logger} from '../logging';
import {NullLogger} from '../logging';
import type {CidAssigner} from '../cid';
import {formatMessage} from '../error';
import type {ApiProblem} from '../error';
import {CLIENT_LIBRARY} from '../constants';
import {Help} from '../help';
import type {EventPublisher, PublishRequest} from '../eventPublisher';
import {PublishError} from '../eventPublisher';

export type Configuration = {
appId: string,
endpointUrl: string,
publisher: EventPublisher,
cidAssigner: CidAssigner,
logger?: Logger,
};

/**
* @deprecated Use `PublishErrorType` from the `eventPublisher` module instead.
*/
export enum TrackingErrorType {
SUSPENDED_SERVICE = 'https://croct.help/sdk/javascript/suspended-service',
// Server errors
Expand All @@ -41,70 +43,50 @@ export class HttpBeaconChannel implements DuplexChannel<string, Envelope<string,
return Promise.reject(MessageDeliveryError.nonRetryable('Channel is closed'));
}

const {token, timestamp, context, payload} = JSON.parse(message);
const {endpointUrl, appId, cidAssigner} = this.configuration;

return fetch(endpointUrl, {
method: 'POST',
headers: {
'X-App-Id': appId,
'X-Client-Id': await cidAssigner.assignCid(),
'X-Client-Library': CLIENT_LIBRARY,
'Content-Type': 'application/json',
...(token !== undefined ? {'X-Token': token} : {}),
},
body: JSON.stringify({
context: context,
payload: payload,
originalTime: timestamp,
departureTime: Date.now(),
}),
keepalive: true,
}).then(async response => {
if (response.ok) {
if (response.status === 202) {
this.logger.warn(
'Event tracking is currently suspended for this application, check the workspace settings. '
+ `For help, see ${TrackingErrorType.SUSPENDED_SERVICE}`,
const {token, timestamp, context, payload: {type, ...payload}} = JSON.parse(message);
const {publisher, cidAssigner} = this.configuration;

const request: PublishRequest = {
type: type,
payload: payload,
clientId: await cidAssigner.assignCid(),
timestamp: timestamp,
context: context,
userToken: token,
};

return publisher.publish(request, {extra: {keepalive: true}})
.then(
() => this.notify(receiptId),
(error: unknown) => {
if (!(error instanceof PublishError)) {
throw error;
}

const problem = error.response;
const isRetryable = HttpBeaconChannel.isRetryable(problem.status);

// The publisher already logs the help for known problems
if (!isRetryable && Help.forApiProblem(problem) === undefined) {
this.logger.error(`Beacon rejected with non-retryable status: ${problem.title}`);
}

return Promise.reject(
isRetryable
? MessageDeliveryError.retryable(problem.title)
: MessageDeliveryError.nonRetryable(problem.title),
);
}

this.notify(receiptId);

return;
}

const problem: ApiProblem = await response.json().catch(
() => ({
type: TrackingErrorType.INTERNAL_ERROR,
title: response.statusText,
status: response.status,
}),
);

const isRetryable = HttpBeaconChannel.isRetryable(problem.status);
const help = Help.forApiProblem(problem);

if (help !== undefined) {
this.logger.error(help);
} else if (!isRetryable) {
this.logger.error(`Beacon rejected with non-retryable status: ${problem.title}`);
}

return Promise.reject(
isRetryable
? MessageDeliveryError.retryable(problem.title)
: MessageDeliveryError.nonRetryable(problem.title),
);
}).catch(error => {
this.logger.error(`Failed to publish beacon: ${formatMessage(error)}`);

return Promise.reject(
error instanceof MessageDeliveryError
? error
: MessageDeliveryError.retryable(error),
);
});
},
)
.catch(error => {
this.logger.error(`Failed to publish beacon: ${formatMessage(error)}`);

return Promise.reject(
error instanceof MessageDeliveryError
? error
: MessageDeliveryError.retryable(error),
);
});
}

public subscribe(listener: ChannelListener<string>): void {
Expand Down
12 changes: 8 additions & 4 deletions src/container.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import type {CookieCacheConfiguration} from './cache/cookieCache';
import {CookieCache} from './cache/cookieCache';
import {FilteredLogger} from './logging/filteredLogger';
import {HttpBeaconChannel} from './channel/httpBeaconChannel';
import {EventPublisher} from './eventPublisher';
import {DeduplicatedLogger} from './logging/deduplicatedLogger';

export type Configuration = {
Expand All @@ -36,7 +37,7 @@ export type Configuration = {
test: boolean,
disableCidMirroring: boolean,
cidAssignerEndpointUrl: string,
trackerEndpointUrl: string,
trackerBaseEndpointUrl: string,
evaluationBaseEndpointUrl: string,
contentBaseEndpointUrl: string,
beaconQueueSize: number,
Expand Down Expand Up @@ -235,14 +236,17 @@ export class Container {
}

const channelLogger = this.getLogger('BeaconChannel');
const {appId, trackerEndpointUrl} = this.configuration;
const {appId, trackerBaseEndpointUrl} = this.configuration;

const queuedChannel = new QueuedChannel(
new RetryChannel({
channel: new GuaranteedChannel({
channel: new HttpBeaconChannel({
appId: appId,
endpointUrl: trackerEndpointUrl,
publisher: new EventPublisher({
appId: appId,
baseEndpointUrl: trackerBaseEndpointUrl,
logger: channelLogger,
}),
cidAssigner: this.getCidAssigner(),
logger: channelLogger,
}),
Expand Down
Loading
Loading