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
45 changes: 27 additions & 18 deletions packages/target-decisioning-engine/src/artifactProvider.spec.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
/* eslint-disable jest/no-test-callback */
import * as HttpStatus from "http-status-codes";
import { ENVIRONMENT_PROD, ENVIRONMENT_STAGE } from "@adobe/target-tools";
import {
ENVIRONMENT_PROD,
ENVIRONMENT_STAGE,
isDefined
} from "@adobe/target-tools";
import ArtifactProvider from "./artifactProvider";
import * as constants from "./constants";
import {
Expand Down Expand Up @@ -36,7 +40,9 @@ describe("artifactProvider", () => {
});

afterEach(() => {
provider.stopPolling();
if (isDefined(provider)) {
provider.stopPolling();
}
provider = undefined;
});

Expand Down Expand Up @@ -134,6 +140,8 @@ describe("artifactProvider", () => {

// eslint-disable-next-line jest/no-test-callback
it("reports an error if it failed to retrieve the artifact after 10 tries", async () => {
expect.assertions(3);

fetch.mockResponses(
["", { status: HttpStatus.UNAUTHORIZED }],
["", { status: HttpStatus.NOT_FOUND }],
Expand Down Expand Up @@ -283,24 +291,25 @@ describe("artifactProvider", () => {

it("emits artifactDownloadSucceeded event", async done => {
fetch.mockResponse(JSON.stringify(DUMMY_ARTIFACT_PAYLOAD));
expect.assertions(3);
expect.assertions(2);

function eventEmitter(eventName, payload) {
expect(eventName).toEqual(ARTIFACT_DOWNLOAD_SUCCEEDED);
expect(payload).toEqual(
expect.objectContaining({
artifactLocation:
"https://assets.adobetarget.com/clientId/production/v1/rules.bin",
artifactPayload: expect.any(Object)
})
);
setTimeout(() => done(), 100);
expect(payload).toMatchObject({
artifactLocation:
"https://assets.adobetarget.com/clientId/production/v1/rules.bin",
artifactPayload: expect.any(Object)
});
done();
}

provider = await ArtifactProvider({
...TEST_CONF,
pollingInterval: 0,
eventEmitter
eventEmitter: (eventName, payload) =>
eventName === ARTIFACT_DOWNLOAD_SUCCEEDED
? eventEmitter(eventName, payload)
: undefined
});
});

Expand Down Expand Up @@ -340,7 +349,7 @@ describe("determineArtifactLocation", () => {
cdnEnvironment: "staging"
})
).toEqual(
`${CDN_BASE_STAGE}/someClientId/production/v${SUPPORTED_ARTIFACT_MAJOR_VERSION}/${ARTIFACT_FILENAME}`
`https://${CDN_BASE_STAGE}/someClientId/production/v${SUPPORTED_ARTIFACT_MAJOR_VERSION}/${ARTIFACT_FILENAME}`
);
});

Expand All @@ -350,7 +359,7 @@ describe("determineArtifactLocation", () => {
client: "someClientId"
})
).toEqual(
`${CDN_BASE_PROD}/someClientId/production/v${SUPPORTED_ARTIFACT_MAJOR_VERSION}/${ARTIFACT_FILENAME}`
`https://${CDN_BASE_PROD}/someClientId/production/v${SUPPORTED_ARTIFACT_MAJOR_VERSION}/${ARTIFACT_FILENAME}`
);
});

Expand All @@ -361,7 +370,7 @@ describe("determineArtifactLocation", () => {
environment: ENVIRONMENT_STAGE
})
).toEqual(
`${CDN_BASE_PROD}/someClientId/${ENVIRONMENT_STAGE}/v${SUPPORTED_ARTIFACT_MAJOR_VERSION}/${ARTIFACT_FILENAME}`
`https://${CDN_BASE_PROD}/someClientId/${ENVIRONMENT_STAGE}/v${SUPPORTED_ARTIFACT_MAJOR_VERSION}/${ARTIFACT_FILENAME}`
);
});

Expand All @@ -380,7 +389,7 @@ describe("determineArtifactLocation", () => {
}
})
).toEqual(
`${CDN_BASE_PROD}/someClientId/${ENVIRONMENT_PROD}/v${SUPPORTED_ARTIFACT_MAJOR_VERSION}/${ARTIFACT_FILENAME}`
`https://${CDN_BASE_PROD}/someClientId/${ENVIRONMENT_PROD}/v${SUPPORTED_ARTIFACT_MAJOR_VERSION}/${ARTIFACT_FILENAME}`
);
});

Expand All @@ -391,7 +400,7 @@ describe("determineArtifactLocation", () => {
propertyToken: "xyz-123-abc"
})
).toEqual(
`${CDN_BASE_PROD}/someClientId/production/v${SUPPORTED_ARTIFACT_MAJOR_VERSION}/${ARTIFACT_FILENAME}`
`https://${CDN_BASE_PROD}/someClientId/production/v${SUPPORTED_ARTIFACT_MAJOR_VERSION}/${ARTIFACT_FILENAME}`
);
});

Expand All @@ -405,7 +414,7 @@ describe("determineArtifactLocation", () => {
true
)
).toEqual(
`${CDN_BASE_PROD}/someClientId/production/v${SUPPORTED_ARTIFACT_MAJOR_VERSION}/xyz-123-abc/${ARTIFACT_FILENAME}`
`https://${CDN_BASE_PROD}/someClientId/production/v${SUPPORTED_ARTIFACT_MAJOR_VERSION}/xyz-123-abc/${ARTIFACT_FILENAME}`
);
});
});
6 changes: 3 additions & 3 deletions packages/target-decisioning-engine/src/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@ export const ARTIFACT_FORMAT_JSON = "json";

export const LOG_PREFIX = "LD";

export const CDN_BASE_PROD = "https://assets.adobetarget.com";
export const CDN_BASE_STAGE = "https://assets.staging.adobetarget.com";
export const CDN_BASE_DEV = "https://assets.staging.adobetarget.com";
export const CDN_BASE_PROD = "assets.adobetarget.com";
export const CDN_BASE_STAGE = "assets.staging.adobetarget.com";
export const CDN_BASE_DEV = "assets.staging.adobetarget.com";

export const HTTP_HEADER_FORWARDED_FOR = "x-forwarded-for";
export const HTTP_HEADER_GEO_LATITUDE = "x-geo-latitude";
Expand Down
43 changes: 16 additions & 27 deletions packages/target-decisioning-engine/src/index.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,4 @@
import {
DEFAULT_MAXIMUM_WAIT_READY,
getLogger,
isDefined,
isUndefined,
whenReady
} from "@adobe/target-tools";
import { getLogger, isDefined, isUndefined } from "@adobe/target-tools";
import { createDecisioningContext } from "./contextProvider";
import DecisionProvider from "./decisionProvider";
import ArtifactProvider from "./artifactProvider";
Expand All @@ -20,24 +14,10 @@ import { GeoProvider } from "./geoProvider";
* @param {import("../types/DecisioningConfig").DecisioningConfig} config Options map, required
*/
export default function TargetDecisioningEngine(config) {
const { maximumWaitReady = DEFAULT_MAXIMUM_WAIT_READY } = config;
const logger = getLogger(config.logger);
let artifactProvider;
let artifact;

ArtifactProvider({
...config,
logger
}).then(providerInstance => {
artifactProvider = providerInstance;
artifact = artifactProvider.getArtifact();

// subscribe to new artifacts that are downloaded on the polling interval
artifactProvider.subscribe(data => {
artifact = data;
});
});

/**
* The get offers method
* @param {import("../types/TargetOptions").TargetOptions} targetOptions
Expand Down Expand Up @@ -95,13 +75,22 @@ export default function TargetDecisioningEngine(config) {
return isDefined(artifact);
}

const whenArtifactReady = whenReady(
isReady,
maximumWaitReady,
Messages.ARTIFACT_NOT_AVAILABLE
);
return ArtifactProvider({
...config,
logger
}).then(providerInstance => {
artifactProvider = providerInstance;
artifact = artifactProvider.getArtifact();

if (isUndefined(artifact)) {
throw new Error(Messages.ARTIFACT_NOT_AVAILABLE);
}

// subscribe to new artifacts that are downloaded on the polling interval
artifactProvider.subscribe(data => {
artifact = data;
});

return whenArtifactReady.then(() => {
return {
getRawArtifact: () => artifact,
stopPolling: () => artifactProvider.stopPolling(),
Expand Down
1 change: 1 addition & 0 deletions packages/target-decisioning-engine/src/messages.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ const Messages = {
ARTIFACT_VERSION_UNSUPPORTED: (artifactVersion, supportedMajorVersion) =>
`The decisioning artifact version (${artifactVersion}) is not supported. This library is compatible with this major version: ${supportedMajorVersion}`,
ARTIFACT_FETCH_ERROR: reason => `Failed to retrieve artifact: ${reason}`,
ARTIFACT_INVALID: "Invalid Artifact",
INVALID_ENVIRONMENT: (expectedEnvironment, defaultEnvironment) =>
`'${expectedEnvironment}' is not a valid target environment, defaulting to '${defaultEnvironment}'.`,
NOT_APPLICABLE: "Not Applicable",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ function ObfuscationProvider(config) {
const header = getHeader(buffer.slice(0, HEADER_BOUNDARY));

if (header.version !== SUPPORTED_ARTIFACT_OBFUSCATION_VERSION) {
throw new Error("Invalid Artifact");
throw new Error(Messages.ARTIFACT_INVALID);
}

return getArtifact(header.key, buffer.slice(HEADER_BOUNDARY));
Expand Down
15 changes: 10 additions & 5 deletions packages/target-decisioning-engine/src/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -180,13 +180,18 @@ export function getCdnEnvironment(config) {
* @return {string}
*/
export function getCdnBasePath(config) {
const cdnEnvironment = getCdnEnvironment(config);
let { cdnBasePath } = config;

const env = includes(cdnEnvironment, POSSIBLE_ENVIRONMENTS)
? cdnEnvironment
: ENVIRONMENT_PROD;
if (!isDefined(cdnBasePath)) {
const cdnEnvironment = getCdnEnvironment(config);

return CDN_BASE[env];
const env = includes(cdnEnvironment, POSSIBLE_ENVIRONMENTS)
? cdnEnvironment
: ENVIRONMENT_PROD;
cdnBasePath = CDN_BASE[env];
}

return `https://${cdnBasePath}`;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,11 @@ export interface DecisioningConfig {
*/
cdnEnvironment?: String;

/**
* A CDN base URL to override the default based on cdnEnvironment.
*/
cdnBasePath?: String;

/**
* Replaces the default noop logger
*/
Expand Down
2 changes: 2 additions & 0 deletions packages/target-nodejs-sdk/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ export default function bootstrap(fetchApi) {
propertyToken: options.propertyToken,
environment: options.environment,
cdnEnvironment: options.cdnEnvironment,
cdnBasePath: options.cdnBasePath,
logger: this.logger,
fetchApi: fetchImpl,
eventEmitter,
Expand Down Expand Up @@ -185,6 +186,7 @@ export default function bootstrap(fetchApi) {
* @param {Array} options.customerIds An array of Customer Ids in VisitorId-compatible format, optional
* @param {String} options.sessionId Session Id, used for linking multiple requests, optional
* @param {Object} options.visitor Supply an external VisitorId instance, optional
* @param {('on-device'|'server-side'|'hybrid')} options.decisioningMethod The execution mode, defaults to remote, optional
*/
getAttributes(mboxNames, options = {}) {
// eslint-disable-next-line no-param-reassign
Expand Down
8 changes: 6 additions & 2 deletions packages/target-nodejs-sdk/src/target.js
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,6 @@ export function executeDelivery(options, decisioningEngine) {

const deliveryRequest = createDeliveryRequest(request, requestOptions);

logger.debug(Messages.REQUEST_SENT, JSON.stringify(deliveryRequest, null, 2));

const configuration = createConfiguration(
fetchWithRetry,
host,
Expand All @@ -108,6 +106,12 @@ export function executeDelivery(options, decisioningEngine) {
decisioningEngine
);

logger.debug(
Messages.REQUEST_SENT,
deliveryMethod.decisioningMethod,
JSON.stringify(deliveryRequest, null, 2)
);

return deliveryMethod
.execute(client, sessionId, deliveryRequest, config.version)
.then((response = {}) => {
Expand Down
2 changes: 2 additions & 0 deletions packages/target-tools/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -94,4 +94,6 @@ export {
PROPERTY_TOKEN_MISMATCH
} from "./messages";

export { perfTool } from "./perftool";

export { default as parseURI } from "parse-uri";
44 changes: 44 additions & 0 deletions packages/target-tools/src/perftool.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/* eslint-disable import/prefer-default-export */
import { now } from "./lodash";
import { isDefined, isUndefined } from "./utils";

let timingIds = {};
let startTimes = {};
let timings = {};

function getUniqueTimingId(id) {
const count = (isDefined(timingIds[id]) ? timingIds[id] : 0) + 1;
timingIds[id] = count;

return `${id}${count}`;
}

function timeStart(id, incrementTimer = false) {
const timingId = incrementTimer ? getUniqueTimingId(id) : id;
if (isUndefined(startTimes[timingId])) {
startTimes[timingId] = now();
}
return timingId;
}

function timeEnd(id, offset = 0) {
if (isUndefined(startTimes[id])) return `No timer was started for "${id}"`;

const timing = now() - startTimes[id] - offset;
timings[id] = timing;
return timing;
}

function reset() {
timingIds = {};
startTimes = {};
timings = {};
}

export const perfTool = {
timeStart,
timeEnd,
getTimings: () => timings,
getTiming: key => timings[key],
reset
};
Loading