Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Consent & local storage config flag #1064

Merged
merged 10 commits into from
Oct 25, 2023
Merged
Show file tree
Hide file tree
Changes from 5 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
6 changes: 6 additions & 0 deletions sandbox/src/components/InAppMessagesDemo/InAppMessages.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,14 @@ const {
alloyInstance
} = config[configKey];

const getURLParams = key => {
const urlParams = new URLSearchParams(window.location.search);
return urlParams.get(key);
};

if (alloyInstance !== window.alloy) {
alloyInstance("configure", {
defaultConsent: getURLParams("defaultConsent") || "in",
datastreamId,
orgId,
edgeDomain,
Expand Down
4 changes: 2 additions & 2 deletions src/components/DecisioningEngine/createContextProvider.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import parseUrl from "../../utils/parseUrl";
import flattenObject from "../../utils/flattenObject";
import libraryVersion from "../../constants/libraryVersion";

export default ({ eventRegistry, window }) => {
export default ({ eventContext, window }) => {
const pageLoadTimestamp = new Date().getTime();
const getBrowserContext = () => {
return {
Expand Down Expand Up @@ -90,7 +90,7 @@ export default ({ eventRegistry, window }) => {
...addedContext
};

return { ...flattenObject(context), events: eventRegistry.toJSON() };
return { ...flattenObject(context), events: eventContext.toJSON() };
};
return {
getContext
Expand Down
4 changes: 2 additions & 2 deletions src/components/DecisioningEngine/createDecisionHistory.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,12 @@ governing permissions and limitations under the License.
*/
import { PropositionEventType } from "../../constants/propositionEventType";

export default ({ eventRegistry }) => {
export default ({ eventContext }) => {
const recordQualified = id => {
if (!id) {
return undefined;
}
return eventRegistry.addEvent({}, PropositionEventType.TRIGGER, id);
return eventContext.addEvent({}, PropositionEventType.TRIGGER, id);
};

return { recordQualified };
Expand Down
6 changes: 3 additions & 3 deletions src/components/DecisioningEngine/createDecisionProvider.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@ import createEvaluableRulesetPayload from "./createEvaluableRulesetPayload";
import createDecisionHistory from "./createDecisionHistory";
import { getActivityId } from "./utils";

export default ({ eventRegistry }) => {
export default ({ eventContext }) => {
const payloadsBasedOnActivityId = {};

const decisionHistory = createDecisionHistory({ eventRegistry });
const decisionHistory = createDecisionHistory({ eventContext });

const addPayload = payload => {
const activityId = getActivityId(payload);
Expand All @@ -26,7 +26,7 @@ export default ({ eventRegistry }) => {

const evaluableRulesetPayload = createEvaluableRulesetPayload(
payload,
eventRegistry,
eventContext,
decisionHistory
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ const isRulesetItem = item => {
}
};

export default (payload, eventRegistry, decisionHistory) => {
export default (payload, eventContext, decisionHistory) => {
const consequenceAdapter = createConsequenceAdapter();
const activityId = getActivityId(payload);
const items = [];
Expand All @@ -64,7 +64,7 @@ export default (payload, eventRegistry, decisionHistory) => {
};

const evaluate = context => {
const displayEvent = eventRegistry.getEvent(DISPLAY, activityId);
const displayEvent = eventContext.getEvent(DISPLAY, activityId);

const displayedDate = displayEvent
? displayEvent.firstTimestamp
Expand Down
49 changes: 49 additions & 0 deletions src/components/DecisioningEngine/createEventContext.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*
Copyright 2023 Adobe. All rights reserved.
This file is licensed to you under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. You may obtain a copy
of the License at http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software distributed under
the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
OF ANY KIND, either express or implied. See the License for the specific language
governing permissions and limitations under the License.
*/
import createNoopEventRegistry from "./createNoopEventRegistry";

export default () => {
let currentEventRegistry = createNoopEventRegistry();

const addExperienceEdgeEvent = event => {
return currentEventRegistry.addExperienceEdgeEvent(event);
};

const addEvent = (event, eventType, eventId, action) => {
return currentEventRegistry.addEvent(event, eventType, eventId, action);
};

const getEvent = (eventType, eventId) => {
return currentEventRegistry.getEvent(eventType, eventId);
};

const toJSON = () => {
return currentEventRegistry.toJSON();
};

const clear = () => {
currentEventRegistry.clear();
};

const setCurrentEventRegistry = eventRegistry => {
currentEventRegistry = eventRegistry;
Copy link
Contributor

@carterworks carterworks Oct 24, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If a new event registry is set, should it copy over the event history from the previous registry?

};

return {
addExperienceEdgeEvent,
addEvent,
getEvent,
toJSON,
setCurrentEventRegistry,
clear
};
};
23 changes: 23 additions & 0 deletions src/components/DecisioningEngine/createNoopEventRegistry.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*
Copyright 2023 Adobe. All rights reserved.
This file is licensed to you under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. You may obtain a copy
of the License at http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software distributed under
the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
OF ANY KIND, either express or implied. See the License for the specific language
governing permissions and limitations under the License.
*/
const EMPTY_OBJECT = () => {
return {};
};

export default () => {
return {
addExperienceEdgeEvent: EMPTY_OBJECT,
addEvent: EMPTY_OBJECT,
getEvent: EMPTY_OBJECT,
toJSON: EMPTY_OBJECT
};
};
46 changes: 33 additions & 13 deletions src/components/DecisioningEngine/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,24 +22,24 @@ import {
MOBILE_EVENT_TYPE
} from "./constants";
import createEvaluateRulesetsCommand from "./createEvaluateRulesetsCommand";
import createEventContext from "./createEventContext";
import createNoopEventRegistry from "./createNoopEventRegistry";

const createDecisioningEngine = ({ config, createNamespacedStorage }) => {
const { orgId } = config;
const storage = createNamespacedStorage(
`${sanitizeOrgIdForCookieName(orgId)}.decisioning.`
);
const eventRegistry = createEventRegistry({ storage: storage.persistent });
let applyResponse;

const decisionProvider = createDecisionProvider({ eventRegistry });
const contextProvider = createContextProvider({ eventRegistry, window });

const createDecisioningEngine = ({
config,
createNamespacedStorage,
consent
}) => {
const { orgId, personalizationStorageEnabled } = config;
const eventContext = createEventContext();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure we need the eventContext object. I see what you are trying to accomplish here, but i'd like to encapsulate the logic for event persistence within the eventRegistry as much as possible. It can manage event persistence without introducing another layer of abstraction above it.

The createEventRegistry method takes a storage parameter, this parameter specifies what to use for storage. What if we create a new function createInMemoryStorage() that provides a lean implementation of getItem and setItem where events are only stored in memory. This would be better than nothing, if the user opts out of consent or the customer disabled personalizationStorage.

// on initialization
const eventRegistry = createEventRegistry({ storage: createInMemoryStorage() })

Once the consent promise resolves, if the user opted in, you can set a new storage on the event registry.

eventRegistry.setStorage(storage.persistent); // this method doesn't exist yet, you'd need to add it. 

☝️ this would effectively tell eventRegistry to switch from in memory storage to local storage.

const decisionProvider = createDecisionProvider({ eventContext });
const contextProvider = createContextProvider({ eventContext, window });
const evaluateRulesetsCommand = createEvaluateRulesetsCommand({
contextProvider,
decisionProvider
});

const subscribeRulesetItems = createSubscribeRulesetItems();
let applyResponse;

return {
lifecycle: {
Expand All @@ -48,6 +48,26 @@ const createDecisioningEngine = ({ config, createNamespacedStorage }) => {
},
onComponentsRegistered(tools) {
applyResponse = createApplyResponse(tools.lifecycle);
if (personalizationStorageEnabled) {
const storage = createNamespacedStorage(
`${sanitizeOrgIdForCookieName(orgId)}.decisioning.`
);
consent
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's fine to have .awaitConsent() here in the index.js file. But I wonder if it makes sense to move the listener within eventRegistry instead? 🤔 There are pros/cons.

.awaitConsent()
.then(() => {
const eventRegistry = createEventRegistry({
storage: storage.persistent
});
eventContext.setCurrentEventRegistry(eventRegistry);
})
.catch(() => {
// If consent is rejected, we want to clear the local storage.
if (storage) {
storage.persistent.clear();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If a customer sets personalizationStorageEnabled to false at some point, local storage should also be cleared. That way customer's won't disable it and wonder why they still see an entry in local storage.

}
eventContext.setCurrentEventRegistry(createNoopEventRegistry());
});
}
},
onBeforeEvent({
event,
Expand All @@ -69,7 +89,7 @@ const createDecisioningEngine = ({ config, createNamespacedStorage }) => {
})
);

eventRegistry.addExperienceEdgeEvent(event);
eventContext.addExperienceEdgeEvent(event);
}
},
commands: {
Expand Down
1 change: 1 addition & 0 deletions src/core/config/createCoreConfigs.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ export default () =>
orgId: string()
.unique()
.required(),
personalizationStorageEnabled: boolean().default(true),
onBeforeEventSend: callback().default(noop),
edgeConfigOverrides: validateConfigOverride
}).deprecated("edgeConfigId", string().unique(), "datastreamId");
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import createContextProvider from "../../../../../src/components/DecisioningEngi
import createOnResponseHandler from "../../../../../src/components/DecisioningEngine/createOnResponseHandler";
import createEventRegistry from "../../../../../src/components/DecisioningEngine/createEventRegistry";
import createDecisionProvider from "../../../../../src/components/DecisioningEngine/createDecisionProvider";
import createEventContext from "../../../../../src/components/DecisioningEngine/createEventContext";

export const proposition = {
id: "2e4c7b28-b3e7-4d5b-ae6a-9ab0b44af87e",
Expand Down Expand Up @@ -124,9 +125,10 @@ export const setupResponseHandler = (applyResponse, window, condition) => {
"clear"
]);
const eventRegistry = createEventRegistry({ storage });
const decisionProvider = createDecisionProvider({ eventRegistry });

const contextProvider = createContextProvider({ eventRegistry, window });
const eventContext = createEventContext();
eventContext.setCurrentEventRegistry(eventRegistry);
const decisionProvider = createDecisionProvider({ eventContext });
const contextProvider = createContextProvider({ eventContext, window });

const onResponseHandler = createOnResponseHandler({
renderDecisions: true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,15 @@ governing permissions and limitations under the License.
*/
import createContextProvider from "../../../../../src/components/DecisioningEngine/createContextProvider";
import createEventRegistry from "../../../../../src/components/DecisioningEngine/createEventRegistry";
import createEventContext from "../../../../../src/components/DecisioningEngine/createEventContext";

describe("DecisioningEngine:createContextProvider", () => {
let contextProvider;
let eventRegistry;
let storage;
let window;
let mockedTimestamp;
let eventContext;

beforeEach(() => {
storage = jasmine.createSpyObj("storage", ["getItem", "setItem", "clear"]);
Expand All @@ -44,7 +46,9 @@ describe("DecisioningEngine:createContextProvider", () => {
});
it("returns page context", () => {
eventRegistry = createEventRegistry({ storage });
contextProvider = createContextProvider({ eventRegistry, window });
eventContext = createEventContext();
eventContext.setCurrentEventRegistry(eventRegistry);
contextProvider = createContextProvider({ eventContext, window });

expect(contextProvider.getContext()).toEqual(
jasmine.objectContaining({
Expand All @@ -62,7 +66,9 @@ describe("DecisioningEngine:createContextProvider", () => {
});
it("returns referring page context", () => {
eventRegistry = createEventRegistry({ storage });
contextProvider = createContextProvider({ eventRegistry, window });
eventContext = createEventContext();
eventContext.setCurrentEventRegistry(eventRegistry);
contextProvider = createContextProvider({ eventContext, window });

expect(contextProvider.getContext()).toEqual(
jasmine.objectContaining({
Expand All @@ -78,7 +84,9 @@ describe("DecisioningEngine:createContextProvider", () => {
});
it("returns browser context", () => {
eventRegistry = createEventRegistry({ storage });
contextProvider = createContextProvider({ eventRegistry, window });
eventContext = createEventContext();
eventContext.setCurrentEventRegistry(eventRegistry);
contextProvider = createContextProvider({ eventContext, window });

expect(contextProvider.getContext()).toEqual(
jasmine.objectContaining({
Expand All @@ -88,7 +96,9 @@ describe("DecisioningEngine:createContextProvider", () => {
});
it("returns windows context", () => {
eventRegistry = createEventRegistry({ storage });
contextProvider = createContextProvider({ eventRegistry, window });
eventContext = createEventContext();
eventContext.setCurrentEventRegistry(eventRegistry);
contextProvider = createContextProvider({ eventContext, window });

expect(contextProvider.getContext()).toEqual(
jasmine.objectContaining({
Expand All @@ -101,7 +111,9 @@ describe("DecisioningEngine:createContextProvider", () => {
});
it("includes provided context passed in", () => {
eventRegistry = createEventRegistry({ storage });
contextProvider = createContextProvider({ eventRegistry, window });
eventContext = createEventContext();
eventContext.setCurrentEventRegistry(eventRegistry);
contextProvider = createContextProvider({ eventContext, window });

expect(contextProvider.getContext({ cool: "beans" })).toEqual(
jasmine.objectContaining({
Expand All @@ -121,7 +133,9 @@ describe("DecisioningEngine:createContextProvider", () => {
eventRegistry = {
toJSON: () => events
};
contextProvider = createContextProvider({ eventRegistry, window });
eventContext = createEventContext();
eventContext.setCurrentEventRegistry(eventRegistry);
contextProvider = createContextProvider({ eventContext, window });

expect(contextProvider.getContext({ cool: "beans" }).events).toEqual(
events
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,20 @@ governing permissions and limitations under the License.
*/
import createDecisionHistory from "../../../../../src/components/DecisioningEngine/createDecisionHistory";
import createEventRegistry from "../../../../../src/components/DecisioningEngine/createEventRegistry";
import createEventContext from "../../../../../src/components/DecisioningEngine/createEventContext";

describe("DecisioningEngine:decisionHistory", () => {
let storage;
let history;
let eventRegistry;
let eventContext;

beforeEach(() => {
storage = jasmine.createSpyObj("storage", ["getItem", "setItem", "clear"]);

history = createDecisionHistory({
eventRegistry: createEventRegistry({ storage, saveDelay: 10 })
});
eventRegistry = createEventRegistry({ storage, saveDelay: 10 });
eventContext = createEventContext();
eventContext.setCurrentEventRegistry(eventRegistry);
history = createDecisionHistory({ eventContext });
});

it("records decision time", () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,20 @@ governing permissions and limitations under the License.
*/
import createDecisionProvider from "../../../../../src/components/DecisioningEngine/createDecisionProvider";
import createEventRegistry from "../../../../../src/components/DecisioningEngine/createEventRegistry";
import createEventContext from "../../../../../src/components/DecisioningEngine/createEventContext";

describe("DecisioningEngine:createDecisionProvider", () => {
let decisionProvider;
let storage;
let eventRegistry;
let eventContext;

beforeEach(() => {
storage = jasmine.createSpyObj("storage", ["getItem", "setItem", "clear"]);
eventRegistry = createEventRegistry({ storage });

decisionProvider = createDecisionProvider({ eventRegistry });
eventContext = createEventContext();
eventContext.setCurrentEventRegistry(eventRegistry);
decisionProvider = createDecisionProvider({ eventContext });
decisionProvider.addPayloads([
{
id: "2e4c7b28-b3e7-4d5b-ae6a-9ab0b44af87e",
Expand Down
Loading