Skip to content

Commit

Permalink
Fallback to regular collection when demdex.net is blocked.
Browse files Browse the repository at this point in the history
  • Loading branch information
shammowla committed Nov 8, 2024
1 parent 640a88c commit 62c1503
Show file tree
Hide file tree
Showing 5 changed files with 168 additions and 26 deletions.
76 changes: 50 additions & 26 deletions src/core/edgeNetwork/injectSendEdgeNetworkRequest.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,14 @@ governing permissions and limitations under the License.
import { ID_THIRD_PARTY as ID_THIRD_PARTY_DOMAIN } from "../../constants/domain.js";
import apiVersion from "../../constants/apiVersion.js";
import { createCallbackAggregator, noop } from "../../utils/index.js";
import { isNetworkError } from "../../utils/networkErrors.js";
import mergeLifecycleResponses from "./mergeLifecycleResponses.js";
import handleRequestFailure from "./handleRequestFailure.js";

const isDemdexBlockedError = (error, request) => {
return request.getUseIdThirdPartyDomain() && isNetworkError(error);
};

export default ({
config,
lifecycle,
Expand All @@ -27,6 +32,27 @@ export default ({
getAssuranceValidationTokenParams,
}) => {
const { edgeDomain, edgeBasePath, datastreamId } = config;
let hasDemdexFailed = false;

const buildEndpointUrl = (endpointDomain, request) => {
const locationHint = getLocationHint();
const edgeBasePathWithLocationHint = locationHint
? `${edgeBasePath}/${locationHint}${request.getEdgeSubPath()}`
: `${edgeBasePath}${request.getEdgeSubPath()}`;
const configId = request.getDatastreamIdOverride() || datastreamId;

if (configId !== datastreamId) {
request.getPayload().mergeMeta({
sdkConfig: {
datastream: {
original: datastreamId,
},
},
});
}

return `https://${endpointDomain}/${edgeBasePathWithLocationHint}/${apiVersion}/${request.getAction()}?configId=${configId}&requestId=${request.getId()}${getAssuranceValidationTokenParams()}`;
};

/**
* Sends a network request that is aware of payload interfaces,
Expand All @@ -52,26 +78,15 @@ export default ({
onRequestFailure: onRequestFailureCallbackAggregator.add,
})
.then(() => {
const endpointDomain = request.getUseIdThirdPartyDomain()
? ID_THIRD_PARTY_DOMAIN
: edgeDomain;
const locationHint = getLocationHint();
const edgeBasePathWithLocationHint = locationHint
? `${edgeBasePath}/${locationHint}${request.getEdgeSubPath()}`
: `${edgeBasePath}${request.getEdgeSubPath()}`;
const configId = request.getDatastreamIdOverride() || datastreamId;
const endpointDomain =
hasDemdexFailed || !request.getUseIdThirdPartyDomain()
? edgeDomain
: ID_THIRD_PARTY_DOMAIN;

const url = buildEndpointUrl(endpointDomain, request);
const payload = request.getPayload();
if (configId !== datastreamId) {
payload.mergeMeta({
sdkConfig: {
datastream: {
original: datastreamId,
},
},
});
}
const url = `https://${endpointDomain}/${edgeBasePathWithLocationHint}/${apiVersion}/${request.getAction()}?configId=${configId}&requestId=${request.getId()}${getAssuranceValidationTokenParams()}`;
cookieTransfer.cookiesToPayload(payload, endpointDomain);

return sendNetworkRequest({
requestId: request.getId(),
url,
Expand All @@ -83,17 +98,26 @@ export default ({
processWarningsAndErrors(networkResponse);
return networkResponse;
})
.catch(handleRequestFailure(onRequestFailureCallbackAggregator))
.catch((error) => {
if (isDemdexBlockedError(error, request)) {
hasDemdexFailed = true;
request.setUseIdThirdPartyDomain(false);
const url = buildEndpointUrl(edgeDomain, request);
const payload = request.getPayload();
cookieTransfer.cookiesToPayload(payload, edgeDomain);

return sendNetworkRequest({
requestId: request.getId(),
url,
payload,
useSendBeacon: request.getUseSendBeacon(),
});
}
return handleRequestFailure(onRequestFailureCallbackAggregator)(error);
})
.then(({ parsedBody, getHeader }) => {
// Note that networkResponse.parsedBody may be undefined if it was a
// 204 No Content response. That's fine.
const response = createResponse({ content: parsedBody, getHeader });
cookieTransfer.responseToCookies(response);

// Notice we're calling the onResponse lifecycle method even if there are errors
// inside the response body. This is because the full request didn't actually fail--
// only portions of it that are considered non-fatal (a specific, non-critical
// Konductor plugin, for example).
return onResponseCallbackAggregator
.call({
response,
Expand Down
15 changes: 15 additions & 0 deletions src/utils/networkErrors.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
export const TYPE_ERROR = "TypeError";
export const NETWORK_ERROR = "NetworkError";

/**
* Checks if the error is a network-related error
* @param {Error} error The error to check
* @returns {boolean} True if the error is network-related
*/
export const isNetworkError = (error) => {
return (
error.name === TYPE_ERROR ||
error.name === NETWORK_ERROR ||
error.status === 0
);
};
12 changes: 12 additions & 0 deletions test/functional/helpers/requestHooks/demdexBlockerMock.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { RequestMock } from "testcafe";

// Mock that fails demdex requests
export default RequestMock()
.onRequestTo((request) => request.url.includes("demdex.net"))
.respond((req, res) => {
res.statusCode = 500;
res.headers = {
"content-type": "application/json",
};
return "";
});
43 changes: 43 additions & 0 deletions test/functional/specs/Identity/demdexFallback.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { t } from "testcafe";
import createNetworkLogger from "../../helpers/networkLogger/index.js";
import createFixture from "../../helpers/createFixture/index.js";
import {
compose,
orgMainConfigMain,
thirdPartyCookiesEnabled,
} from "../../helpers/constants/configParts/index.js";
import createAlloyProxy from "../../helpers/createAlloyProxy.js";
import demdexBlockerMock from "../../helpers/requestHooks/demdexBlockerMock.js";

const networkLogger = createNetworkLogger();
const config = compose(orgMainConfigMain, thirdPartyCookiesEnabled);

createFixture({
title: "Demdex Fallback Behavior",
requestHooks: [networkLogger.edgeEndpointLogs, demdexBlockerMock],
});

test("Continues collecting data when demdex is blocked", async () => {
const alloy = createAlloyProxy();

await alloy.configure(config);
await alloy.sendEvent();

// Get all requests
const requests = networkLogger.edgeEndpointLogs.requests;

// Find the successful request (should be the last one)
const successfulRequest = requests[requests.length - 1];

// Verify the successful request
await t.expect(successfulRequest.request.url).notContains("demdex.net");
await t.expect(successfulRequest.request.url).contains(config.edgeDomain);
await t.expect(successfulRequest.response.statusCode).eql(200);

// Verify at least one request was successful
const successfulRequests = requests.filter(
(r) =>
!r.request.url.includes("demdex.net") && r.response.statusCode === 200,
);
await t.expect(successfulRequests.length).gte(1);
});
48 changes: 48 additions & 0 deletions test/unit/specs/utils/networkErrors.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import {
TYPE_ERROR,
NETWORK_ERROR,
isNetworkError,
} from "../../../../src/utils/networkErrors.js";

describe("Network Errors", () => {
describe("isNetworkError", () => {
it("returns true for TypeError", () => {
const error = new Error();
error.name = TYPE_ERROR;

expect(isNetworkError(error)).toBe(true);
});

it("returns true for NetworkError", () => {
const error = new Error();
error.name = NETWORK_ERROR;

expect(isNetworkError(error)).toBe(true);
});

it("returns true for status 0", () => {
const error = { status: 0 };

expect(isNetworkError(error)).toBe(true);
});

it("returns false for other errors", () => {
const error = new Error();
error.name = "SyntaxError";

expect(isNetworkError(error)).toBe(false);
});

it("returns false for non-zero status", () => {
const error = { status: 500 };

expect(isNetworkError(error)).toBe(false);
});

it("returns false for undefined status", () => {
const error = new Error();

expect(isNetworkError(error)).toBe(false);
});
});
});

0 comments on commit 62c1503

Please sign in to comment.