@mindful-web/marko-web-omeda-identity-x 1.0.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.
Files changed (59) hide show
  1. package/.eslintignore +1 -0
  2. package/LICENSE +21 -0
  3. package/README.md +86 -0
  4. package/add-integration-hooks.js +119 -0
  5. package/api/queries/customer-by-encrypted-id.js +10 -0
  6. package/browser/.eslintrc.js +1 -0
  7. package/browser/index.js +43 -0
  8. package/browser/rapid-identify.vue +34 -0
  9. package/components/identify.marko +16 -0
  10. package/components/identify.marko.js +60 -0
  11. package/components/marko.json +6 -0
  12. package/external-id/find-encrypted-customer-id.js +10 -0
  13. package/external-id/get-encrypted-customer-id.js +7 -0
  14. package/external-id/get-omeda-namespaces.js +16 -0
  15. package/external-id/is-demographic-id.js +8 -0
  16. package/external-id/is-deployment-type-id.js +8 -0
  17. package/external-id/is-encrypted-customer-id.js +10 -0
  18. package/external-id/is-omeda-namespace.js +18 -0
  19. package/external-id/is-product-id.js +8 -0
  20. package/index.js +261 -0
  21. package/integration-hooks/index.js +9 -0
  22. package/integration-hooks/on-authentication-success.js +59 -0
  23. package/integration-hooks/on-login-link-sent.js +106 -0
  24. package/integration-hooks/on-user-profile-update.js +54 -0
  25. package/marko.json +6 -0
  26. package/middleware/README.md +12 -0
  27. package/middleware/rapid-identify.js +56 -0
  28. package/middleware/resync-customer-data.js +69 -0
  29. package/middleware/set-identity-cookie.js +77 -0
  30. package/middleware/set-olytics-cookie.js +25 -0
  31. package/middleware/set-promo-source.js +20 -0
  32. package/middleware/strip-olytics-param.js +23 -0
  33. package/omeda-data/get-answered-question-map.js +19 -0
  34. package/omeda-data/get-omeda-customer-record.js +46 -0
  35. package/omeda-data/get-omeda-linked-fields.js +66 -0
  36. package/omeda-data/index.js +19 -0
  37. package/omeda-data/set-omeda-data.js +35 -0
  38. package/omeda-data/set-omeda-demographics.js +137 -0
  39. package/omeda-data/set-omeda-deployment-types.js +51 -0
  40. package/omeda-data/set-omeda-products.js +50 -0
  41. package/omeda-data/update-identity-x.js +68 -0
  42. package/package.json +33 -0
  43. package/rapid-identify.js +194 -0
  44. package/routes/rapid-identify.js +70 -0
  45. package/utils/append-data.js +24 -0
  46. package/utils/build-behavior.js +32 -0
  47. package/utils/extract-promo-code.js +8 -0
  48. package/utils/get-cookie-id.js +11 -0
  49. package/utils/resync-cookie.js +25 -0
  50. package/validation/props.js +20 -0
  51. package/validation/schemas/append-behavior.js +7 -0
  52. package/validation/schemas/append-demographic.js +8 -0
  53. package/validation/schemas/append-promo-code.js +5 -0
  54. package/validation/schemas/behavior.js +7 -0
  55. package/validation/schemas/hook-behavior.js +12 -0
  56. package/validation/schemas/hook-demographic.js +16 -0
  57. package/validation/schemas/hook-promo-code.js +12 -0
  58. package/validation/schemas/index.js +19 -0
  59. package/validation/schemas/should-await.js +7 -0
package/index.js ADDED
@@ -0,0 +1,261 @@
1
+ const Joi = require('@parameter1/joi');
2
+ const { validate } = require('@parameter1/joi/utils');
3
+ const omeda = require('@mindful-web/marko-web-omeda');
4
+ const identityX = require('@mindful-web/marko-web-identity-x');
5
+ const addOmedaHooksToIdentityXConfig = require('./add-integration-hooks');
6
+ const setPromoSourceCookie = require('./middleware/set-promo-source');
7
+ const stripOlyticsParam = require('./middleware/strip-olytics-param');
8
+ const resyncCustomerData = require('./middleware/resync-customer-data');
9
+ const setOlyticsCookie = require('./middleware/set-olytics-cookie');
10
+ const setIdentityCookie = require('./middleware/set-identity-cookie');
11
+ const rapidIdentify = require('./middleware/rapid-identify');
12
+ const rapidIdentifyRouter = require('./routes/rapid-identify');
13
+ const props = require('./validation/props');
14
+ const schemas = require('./validation/schemas');
15
+
16
+ /**
17
+ * @typedef {import('./validation/schemas/hook-behavior')
18
+ * .HookBehaviorSchema} HookBehaviorSchema
19
+ * @typedef {import('./validation/schemas/hook-demographic')
20
+ * .HookDemographicSchema} HookDemographicSchema
21
+ * @typedef {import('./validation/schemas/hook-promo-code')
22
+ * .HookPromoCodeSchema} HookPromoCodeSchema
23
+ * @typedef {import('@mindful-web/marko-web-identity-x/config')} IdentityXConfig
24
+
25
+ * @typedef OmedaIdentityXConfig
26
+ * @prop {HookDemographicSchema[]} appendDemographicToHook
27
+ * @prop {HookPromoCodeSchema[]} appendPromoCodeToHook
28
+ * @prop {string} appId The Omeda Application Identifier
29
+ * @prop {BehaviorSchema} behaviors
30
+ * @prop {BehaviorAttributeSchema} behaviorAttributes
31
+ * @prop {string} brandKey
32
+ * @prop {string} clientKey
33
+ * @prop {boolean} [createFromIdentity=true] createFromIdentity
34
+ * @prop {HookBehaviorSchema[]} hookBehavior
35
+ * @prop {IdentityXConfig} idxConfig
36
+ * @prop {string} [idxOmedaRapidIdentifyProp=$idxOmedaRapidIdentify]
37
+ * @prop {object} idxRouteTemplates
38
+ * @prop {string} inputId
39
+ * @prop {string} [omedaGraphQLClientProp=$omedaGraphQLClient]
40
+ * @prop {string} [omedaPromoCodeCookieName=omeda_promo_code]
41
+ * @prop {?string} omedaPromoCodeDefault
42
+ * @prop {string} [omedaRapidIdentifyProp=$omedaRapidIdentify]
43
+ * @prop {?ShouldAwaitSchema} shouldAwait
44
+ * @prop {?Promise<object>} onLoginLinkSentFormatter
45
+ * @prop {?Promise<object>} onAuthenticationSuccessFormatter
46
+ * @prop {?Promise<object>} onUserProfileUpdateFormatter
47
+ * @prop {number} rapidIdentProductId
48
+ *
49
+ * @typedef BehaviorSchema
50
+ * @prop {number} logIn
51
+ * @prop {number} verifyEmail
52
+ * @prop {number} submitProfile
53
+ *
54
+ * @typedef BehaviorAttributeSchema
55
+ * @prop {BehaviorAttributeWebsite} website
56
+ * @prop {BehaviorAttributeActionSource} actionSource
57
+ * @prop {BehaviorAttributeNewsletterSignupType} newsletterSignupType
58
+ * @prop {BehaviorAttributeContentGateType} contentGateType
59
+ *
60
+ * @typedef BehaviorAttributeWebsite
61
+ * @prop {number} id
62
+ * @prop {number} valueId
63
+ *
64
+ * @typedef BehaviorAttributeActionSource
65
+ * @prop {number} id
66
+ * @prop {object} valueIds
67
+ * @prop {number} valueIds.default
68
+ * @prop {number} valueIds.newsletterSignup
69
+ * @prop {number} valueIds.comments
70
+ * @prop {number} valueIds.contentGate
71
+ *
72
+ * @typedef BehaviorAttributeContentGateType
73
+ * @prop {number} id
74
+ * @prop {object} valueIds
75
+ * @prop {number} valueIds.default
76
+ * @prop {number} valueIds.metered
77
+ * @prop {number} valueIds.printPreview
78
+ *
79
+ * @typedef BehaviorAttributeNewsletterSignupType
80
+ * @prop {number} id
81
+ * @prop {object} valueIds
82
+ * @prop {number} valueIds.default
83
+ * @prop {number} valueIds.pushdown
84
+ * @prop {number} valueIds.inlineContent
85
+ * @prop {number} valueIds.inlineSection
86
+ * @prop {number} valueIds.footer
87
+ *
88
+ * @typedef ShouldAwaitSchema
89
+ * @prop {boolean} [onLoginLinkSent=false] onLoginLinkSent
90
+ * @prop {boolean} [onAuthenticationSuccess=true] onAuthenticationSuccess
91
+ * @prop {boolean} [onUserProfileUpdate=false] onUserProfileUpdate
92
+ */
93
+
94
+ const defaultFormatter = async ({ payload }) => payload;
95
+
96
+ /**
97
+ * @param {import('express').Application} app
98
+ * @param {OmedaIdentityXConfig} params
99
+ */
100
+ module.exports = (app, params = {}) => {
101
+ const {
102
+ appendBehaviorToHook,
103
+ appendDemographicToHook,
104
+ appendPromoCodeToHook,
105
+ appId,
106
+ behaviors,
107
+ behaviorAttributes,
108
+ brandKey,
109
+ clientKey,
110
+ createFromIdentity,
111
+ idxConfig,
112
+ idxOmedaRapidIdentifyProp,
113
+ idxRouteTemplates,
114
+ inputId,
115
+ omedaGraphQLClientProp,
116
+ omedaPromoCodeCookieName,
117
+ omedaPromoCodeDefault,
118
+ omedaRapidIdentifyProp,
119
+ shouldAwait,
120
+ onLoginLinkSentFormatter = defaultFormatter,
121
+ onAuthenticationSuccessFormatter = defaultFormatter,
122
+ onUserProfileUpdateFormatter = defaultFormatter,
123
+ rapidIdentProductId,
124
+ } = validate(Joi.object({
125
+ appendBehaviorToHook: Joi.array().items(schemas.hookBehavior),
126
+ appendDemographicToHook: Joi.array().items(schemas.hookDemographic),
127
+ appendPromoCodeToHook: Joi.array().items(schemas.hookPromoCode),
128
+ appId: Joi.string().required(),
129
+ shouldAwait: schemas.shouldAwait,
130
+ /**
131
+ * Behavior config is now mandatory and can be generated by the CLI.
132
+ * @see https://github.com/parameter1/identity-x-omeda-cli
133
+ * */
134
+ behaviors: Joi.object({
135
+ logIn: Joi.number().required(),
136
+ verifyEmail: Joi.number().required(),
137
+ submitProfile: Joi.number().required(),
138
+ }).required(),
139
+ behaviorAttributes: Joi.object({
140
+ website: Joi.object({
141
+ // The Omeda BehaviorAttribute ID for the `Parameter1 Website` attribute.
142
+ id: Joi.number().required(),
143
+ // The Omeda DefinedValue ID for the current website.
144
+ valueId: Joi.number().required(),
145
+ }).required(),
146
+ actionSource: Joi.object({
147
+ // The Omeda BehaviorAttribute ID for the `Parameter1 Action Source` attribute.
148
+ id: Joi.number().required(),
149
+ valueIds: Joi.object({
150
+ default: Joi.number().required(),
151
+ newsletterSignup: Joi.number().required(),
152
+ comments: Joi.number().required(),
153
+ contentGate: Joi.number().required(),
154
+ }).required(),
155
+ }).required(),
156
+ newsletterSignupType: Joi.object({
157
+ // The Omeda BehaviorAttribute ID for the `Parameter1 Newsletter Signup Type` attribute.
158
+ id: Joi.number().required(),
159
+ valueIds: Joi.object({
160
+ default: Joi.number().required(),
161
+ pushdown: Joi.number().required(),
162
+ inlineContent: Joi.number().required(),
163
+ inlineSection: Joi.number().required(),
164
+ footer: Joi.number().required(),
165
+ }).required(),
166
+ }).required(),
167
+ contentGateType: Joi.object({
168
+ // The Omeda BehaviorAttribute ID for the `Parameter1 Content Gate Type` attribute.
169
+ id: Joi.number().required(),
170
+ valueIds: Joi.object({
171
+ default: Joi.number().required(),
172
+ metered: Joi.number().required(),
173
+ printPreview: Joi.number().required(),
174
+ }).required(),
175
+ }).required(),
176
+ }).required(),
177
+ brandKey: props.brandKey.required(),
178
+ clientKey: props.clientKey.required(),
179
+ createFromIdentity: Joi.boolean().default(true),
180
+ idxConfig: props.idxConfig.required(),
181
+ idxOmedaRapidIdentifyProp: Joi.string().default('$idxOmedaRapidIdentify'),
182
+ idxRouteTemplates: Joi.object().required(),
183
+ inputId: Joi.string().required(),
184
+ omedaGraphQLClientProp: Joi.string().default('$omedaGraphQLClient'),
185
+ omedaPromoCodeCookieName: Joi.string().default('omeda_promo_code'),
186
+ omedaPromoCodeDefault: Joi.string(),
187
+ omedaRapidIdentifyProp: Joi.string().default('$omedaRapidIdentify'),
188
+ onLoginLinkSentFormatter: Joi.function(),
189
+ onAuthenticationSuccessFormatter: Joi.function(),
190
+ onUserProfileUpdateFormatter: Joi.function(),
191
+ rapidIdentProductId: Joi.number().required(),
192
+ }), params);
193
+
194
+ // strip `oly_enc_id` when identity-x user is logged-in
195
+ app.use(stripOlyticsParam());
196
+
197
+ // set `omeda_promo_code` when the URL parameter is present
198
+ app.use(setPromoSourceCookie({
199
+ omedaPromoCodeCookieName,
200
+ }));
201
+
202
+ // install omeda middleware
203
+ omeda(app, {
204
+ brandKey,
205
+ clientKey,
206
+ appId,
207
+ inputId,
208
+ rapidIdentProductId,
209
+ omedaGraphQLClientProp,
210
+ omedaRapidIdentifyProp,
211
+ });
212
+
213
+ // add appropiate identity-x to omeda integration hooks
214
+ addOmedaHooksToIdentityXConfig({
215
+ appendBehaviorToHook,
216
+ appendDemographicToHook,
217
+ appendPromoCodeToHook,
218
+ behaviors,
219
+ behaviorAttributes,
220
+ brandKey,
221
+ idxConfig,
222
+ idxOmedaRapidIdentifyProp,
223
+ omedaGraphQLClientProp,
224
+ omedaPromoCodeCookieName,
225
+ omedaPromoCodeDefault,
226
+ shouldAwait,
227
+ onLoginLinkSentFormatter,
228
+ onAuthenticationSuccessFormatter,
229
+ onUserProfileUpdateFormatter,
230
+ });
231
+
232
+ // attach the identity-x rapid identification wrapper middleware
233
+ app.use(rapidIdentify({
234
+ brandKey,
235
+ productId: rapidIdentProductId,
236
+ prop: idxOmedaRapidIdentifyProp,
237
+ omedaRapidIdentifyProp,
238
+ omedaPromoCodeCookieName,
239
+ omedaPromoCodeDefault,
240
+ }));
241
+
242
+ // install identity x
243
+ identityX(app, idxConfig, { templates: idxRouteTemplates });
244
+
245
+ app.use(setOlyticsCookie({ brandKey }));
246
+ app.use(setIdentityCookie({ brandKey, createFromIdentity }));
247
+
248
+ // install the Omeda data sync middleware
249
+ app.use(resyncCustomerData({
250
+ brandKey,
251
+ omedaGraphQLClientProp,
252
+ }));
253
+
254
+ // register the rapid identify AJAX route
255
+ app.use('/__idx/omeda-rapid-ident', rapidIdentifyRouter({
256
+ brandKey,
257
+ idxOmedaRapidIdentifyProp,
258
+ omedaPromoCodeCookieName,
259
+ omedaPromoCodeDefault,
260
+ }));
261
+ };
@@ -0,0 +1,9 @@
1
+ const onAuthenticationSuccess = require('./on-authentication-success');
2
+ const onLoginLinkSent = require('./on-login-link-sent');
3
+ const onUserProfileUpdate = require('./on-user-profile-update');
4
+
5
+ module.exports = {
6
+ onAuthenticationSuccess,
7
+ onLoginLinkSent,
8
+ onUserProfileUpdate,
9
+ };
@@ -0,0 +1,59 @@
1
+ const Joi = require('@parameter1/joi');
2
+ const { validate } = require('@parameter1/joi/utils');
3
+ const olyticsCookie = require('@mindful-web/marko-web-omeda/olytics/customer-cookie');
4
+ const extractPromoCode = require('../utils/extract-promo-code');
5
+ const findEncryptedId = require('../external-id/find-encrypted-customer-id');
6
+ const props = require('../validation/props');
7
+ const schemas = require('../validation/schemas');
8
+
9
+ module.exports = async (params = {}) => {
10
+ const {
11
+ appendBehaviors,
12
+ appendDemographics,
13
+ appendPromoCodes,
14
+ behavior,
15
+ brandKey,
16
+ formatter,
17
+ idxOmedaRapidIdentify,
18
+ omedaPromoCodeCookieName,
19
+ omedaPromoCodeDefault,
20
+ promoCode: hookDataPromoCode,
21
+ res,
22
+ req,
23
+ user,
24
+ } = validate(Joi.object({
25
+ appendBehaviors: Joi.array().items(schemas.appendBehavior).default([]),
26
+ appendDemographics: Joi.array().items(schemas.appendDemographic).default([]),
27
+ appendPromoCodes: Joi.array().items(schemas.appendPromoCode).default([]),
28
+ behavior: schemas.behavior.required(),
29
+ brandKey: props.brandKey.required(),
30
+ formatter: Joi.function().required(),
31
+ user: Joi.object().required(),
32
+ res: Joi.object().required(),
33
+ req: Joi.object().required(),
34
+ }).unknown(true), params);
35
+ const encryptedId = findEncryptedId({ externalIds: user.externalIds, brandKey });
36
+ if (!encryptedId) return;
37
+ olyticsCookie.setTo(res, encryptedId);
38
+
39
+ const promoCode = extractPromoCode({
40
+ promoCode: hookDataPromoCode,
41
+ omedaPromoCodeCookieName,
42
+ omedaPromoCodeDefault,
43
+ cookies: res.req.cookies,
44
+ });
45
+
46
+ const payload = await formatter({
47
+ ...params, // Pass all params through to the formatter
48
+ req,
49
+ payload: {
50
+ user,
51
+ behavior,
52
+ promoCode,
53
+ appendBehaviors,
54
+ appendDemographics,
55
+ appendPromoCodes,
56
+ },
57
+ });
58
+ await idxOmedaRapidIdentify(payload);
59
+ };
@@ -0,0 +1,106 @@
1
+ const Joi = require('@parameter1/joi');
2
+ const { validate } = require('@parameter1/joi/utils');
3
+ const extractPromoCode = require('../utils/extract-promo-code');
4
+ const schemas = require('../validation/schemas');
5
+ const {
6
+ getAnsweredQuestionMap,
7
+ getOmedaCustomerRecord,
8
+ getOmedaLinkedFields,
9
+ updateIdentityX,
10
+ } = require('../omeda-data');
11
+ const props = require('../validation/props');
12
+
13
+ module.exports = async (params = {}) => {
14
+ const {
15
+ appendBehaviors,
16
+ appendDemographics,
17
+ appendPromoCodes,
18
+ behavior,
19
+ brandKey,
20
+ formatter,
21
+ idxOmedaRapidIdentify,
22
+ omedaGraphQLClient,
23
+ omedaPromoCodeCookieName,
24
+ omedaPromoCodeDefault,
25
+ promoCode: hookDataPromoCode,
26
+ req,
27
+ source,
28
+ service: identityX,
29
+ user,
30
+ } = validate(Joi.object({
31
+ appendBehaviors: Joi.array().items(schemas.appendBehavior).default([]),
32
+ appendDemographics: Joi.array().items(schemas.appendDemographic).default([]),
33
+ appendPromoCodes: Joi.array().items(schemas.appendPromoCode).default([]),
34
+ behavior: schemas.behavior.required(),
35
+ brandKey: props.brandKey.required(),
36
+ formatter: Joi.function().required(),
37
+ idxOmedaRapidIdentify: Joi.function().required(),
38
+ omedaGraphQLClient: Joi.object().required(),
39
+ omedaPromoCodeCookieName: Joi.string().required(),
40
+ omedaPromoCodeDefault: Joi.string(),
41
+ promoCode: Joi.string(),
42
+ req: Joi.object().required(),
43
+ source: Joi.string().required(),
44
+ service: Joi.object().required(),
45
+ user: Joi.object().required(),
46
+ }).unknown(), params);
47
+
48
+ const promoCode = extractPromoCode({
49
+ promoCode: hookDataPromoCode,
50
+ omedaPromoCodeCookieName,
51
+ omedaPromoCodeDefault,
52
+ cookies: req.cookies,
53
+ });
54
+
55
+ const requiredCreateFields = identityX.config.getRequiredCreateFields();
56
+
57
+ const [omedaLinkedFields, { encryptedCustomerId }] = await Promise.all([
58
+ getOmedaLinkedFields({ identityX, brandKey }),
59
+ idxOmedaRapidIdentify(await formatter({
60
+ ...params, // Pass all params through to the formatter
61
+ req,
62
+ source,
63
+ payload: {
64
+ user: user.verified ? user : {
65
+ ...(requiredCreateFields.reduce((obj, key) => ({ ...obj, [key]: user[key] }), {})),
66
+ id: user.id,
67
+ email: user.email,
68
+ },
69
+ behavior,
70
+ promoCode,
71
+ appendBehaviors,
72
+ appendDemographics,
73
+ appendPromoCodes,
74
+ },
75
+ })),
76
+ ]);
77
+
78
+ const answers = getAnsweredQuestionMap(user);
79
+ const hasAllDemos = omedaLinkedFields.demographic.every((field) => answers.has(field.id));
80
+ const hasAllDeployments = omedaLinkedFields.deploymentType
81
+ .every((field) => answers.has(field.id));
82
+ const hasAllProducts = omedaLinkedFields.product.every((field) => answers.has(field.id));
83
+ const hasAnsweredAllOmedaQuestions = hasAllDemos && hasAllDeployments && hasAllProducts;
84
+
85
+ // If the user is verified and has answered all Omeda questions, do nothing.
86
+ if (user.verified && hasAnsweredAllOmedaQuestions) return;
87
+
88
+ // Find the Omeda customer record to prime the identity-x user record.
89
+ const omedaCustomer = await getOmedaCustomerRecord({ omedaGraphQLClient, encryptedCustomerId });
90
+
91
+ // Update the IdentityX user record with the Omeda data (if any is present)
92
+ await updateIdentityX({
93
+ identityX,
94
+ brandKey,
95
+ user,
96
+ omedaCustomer,
97
+ omedaLinkedFields,
98
+ }, {
99
+ // Only set existing Omeda data to IdentityX if the user is not yet verified (aka, new user)
100
+ updateData: !user.verified,
101
+ // Update each type of linked (custom select/boolean) field if any are missing a value.
102
+ updateDemographics: !hasAllDemos,
103
+ updateDeploymentTypes: !hasAllDeployments,
104
+ updateProducts: !hasAllProducts,
105
+ });
106
+ };
@@ -0,0 +1,54 @@
1
+ const Joi = require('@parameter1/joi');
2
+ const { validate } = require('@parameter1/joi/utils');
3
+ const extractPromoCode = require('../utils/extract-promo-code');
4
+ const schemas = require('../validation/schemas');
5
+
6
+ module.exports = async (params = {}) => {
7
+ const {
8
+ appendBehaviors,
9
+ appendDemographics,
10
+ appendPromoCodes,
11
+ behavior,
12
+ formatter,
13
+ idxOmedaRapidIdentify,
14
+ omedaPromoCodeCookieName,
15
+ omedaPromoCodeDefault,
16
+ promoCode: hookDataPromoCode,
17
+ req,
18
+ user,
19
+ } = validate(Joi.object({
20
+ appendBehaviors: Joi.array().items(schemas.appendBehavior).default([]),
21
+ appendDemographics: Joi.array().items(schemas.appendDemographic).default([]),
22
+ appendPromoCodes: Joi.array().items(schemas.appendPromoCode).default([]),
23
+ behavior: schemas.behavior.required(),
24
+ formatter: Joi.function().required(),
25
+ idxOmedaRapidIdentify: Joi.function().required(),
26
+ omedaPromoCodeCookieName: Joi.string().required(),
27
+ omedaPromoCodeDefault: Joi.string(),
28
+ promoCode: Joi.string(),
29
+ req: Joi.object().required(),
30
+ user: Joi.object().required(),
31
+ }).unknown(), params);
32
+
33
+ const promoCode = extractPromoCode({
34
+ promoCode: hookDataPromoCode,
35
+ omedaPromoCodeCookieName,
36
+ omedaPromoCodeDefault,
37
+ cookies: req.cookies,
38
+ });
39
+
40
+ const payload = await formatter({
41
+ ...params, // Pass all params through to the formatter
42
+ req,
43
+ payload: {
44
+ user,
45
+ behavior,
46
+ promoCode,
47
+ appendBehaviors,
48
+ appendDemographics,
49
+ appendPromoCodes,
50
+ },
51
+ });
52
+
53
+ return idxOmedaRapidIdentify(payload);
54
+ };
package/marko.json ADDED
@@ -0,0 +1,6 @@
1
+ {
2
+ "taglib-id": "@mindful-web/marko-web-omeda",
3
+ "taglib-imports": [
4
+ "./components/marko.json"
5
+ ]
6
+ }
@@ -0,0 +1,12 @@
1
+ Omeda+IdentityX Middleware
2
+ ===
3
+
4
+ ## Set Identity Cookie
5
+ This [middleware](./set-identity-cookie.js) sets the IdentityX Identity cookie. This cookie represents the identity that all user behavior should be attributed to.
6
+
7
+ Test cases:
8
+ 1. Fully anonymous user (no omeda or idx tokens). No cookie should be set.
9
+ 2. Logged in user w/o cookie: cookkie should be set
10
+ 3. Logged in user w/ cookie: Cookie shoudl not be set.
11
+ 4. Omeda identity cookie present, identityx user exists for ext id: cookie shoul dbe set.
12
+ 5. Omeda identity cookie present, no identityx user exists: cookie should be set.
@@ -0,0 +1,56 @@
1
+ const idxOmedaRapidIdentify = require('../rapid-identify');
2
+ const extractPromoCode = require('../utils/extract-promo-code');
3
+
4
+ module.exports = ({
5
+ brandKey,
6
+ productId,
7
+
8
+ prop = '$idxOmedaRapidIdentify',
9
+ omedaRapidIdentifyProp = '$omedaRapidIdentify',
10
+ omedaPromoCodeCookieName = 'omeda_promo_code',
11
+ omedaPromoCodeDefault,
12
+ }) => {
13
+ if (!prop) throw new Error('An Omeda + IdentityX rapid identifcation prop is required.');
14
+ if (!omedaRapidIdentifyProp) throw new Error('The Omeda rapid identifcation prop is required.');
15
+
16
+ return (req, res, next) => {
17
+ const omedaRapidIdentify = req[omedaRapidIdentifyProp];
18
+ if (!omedaRapidIdentify) throw new Error(`Unable to find the Omeda rapid identifier on the request using ${omedaRapidIdentifyProp}`);
19
+
20
+ const handler = async ({
21
+ user,
22
+ promoCode,
23
+ deploymentTypes,
24
+ appendBehaviors,
25
+ appendDemographics,
26
+ appendPromoCodes,
27
+ behavior,
28
+ } = {}) => idxOmedaRapidIdentify({
29
+ brandKey,
30
+ productId,
31
+ appUser: user,
32
+ promoCode: extractPromoCode({
33
+ promoCode,
34
+ omedaPromoCodeCookieName,
35
+ omedaPromoCodeDefault,
36
+ cookies: req.cookies,
37
+ }),
38
+
39
+ // Custom behaviors, demos, or codes to append to the API call
40
+ deploymentTypes,
41
+ appendBehaviors,
42
+ appendDemographics,
43
+ appendPromoCodes,
44
+
45
+ // The pre-configured behavior/attribute definitions.
46
+ behavior,
47
+
48
+ identityX: req.identityX,
49
+ omedaRapidIdentify,
50
+ });
51
+
52
+ req[prop] = handler;
53
+ res.locals[prop] = handler;
54
+ next();
55
+ };
56
+ };
@@ -0,0 +1,69 @@
1
+ const { asyncRoute } = require('@mindful-web/utils');
2
+ const { getAsArray } = require('@mindful-web/object-path');
3
+ const resyncCookie = require('../utils/resync-cookie');
4
+ const findEncryptedId = require('../external-id/find-encrypted-customer-id');
5
+ const {
6
+ getOmedaCustomerRecord,
7
+ getOmedaLinkedFields,
8
+ updateIdentityX,
9
+ } = require('../omeda-data');
10
+
11
+ const { log } = console;
12
+
13
+ /**
14
+ * A middleware to resync the IdentityX user data with the Omeda customer data. After syncing data,
15
+ * sets a cookie to prevent resyncs for the configured timeframe.
16
+ */
17
+ module.exports = ({
18
+ brandKey,
19
+ omedaGraphQLClientProp = '$omedaGraphQLClient',
20
+ }) => asyncRoute(async (req, res, next) => {
21
+ const { identityX, [omedaGraphQLClientProp]: omedaGraphQLClient } = req;
22
+ if (!omedaGraphQLClient) throw new Error(`Unable to load the Omeda GraphQL API from the request using prop ${omedaGraphQLClientProp}`);
23
+
24
+ // Load the user and find their external ID
25
+ const { user } = await req.identityX.loadActiveContext();
26
+ if (!user) return next();
27
+
28
+ // Only check for the cookie if we have a valid IdX user and Omeda customer
29
+ const encryptedCustomerId = findEncryptedId({ externalIds: getAsArray(user, 'externalIds'), brandKey });
30
+ if (!encryptedCustomerId) return next();
31
+
32
+ // If the cookie doesn't exist (or has expired), resync the user data
33
+ const cookie = resyncCookie.parseFrom(req);
34
+ if (cookie) return next();
35
+
36
+ // Load the customer data from Omeda and fields from IdentityX
37
+ const [omedaCustomer, omedaLinkedFields] = await Promise.all([
38
+ getOmedaCustomerRecord({ omedaGraphQLClient, encryptedCustomerId }),
39
+ getOmedaLinkedFields({ identityX, brandKey }),
40
+ ]);
41
+
42
+ if (!omedaCustomer) {
43
+ log(`Unable to resync Omeda customer using "${encryptedCustomerId}", aborting sync!`);
44
+ return next();
45
+ }
46
+
47
+ // Update the IdentityX user record custom select fields with the Omeda user data
48
+ await updateIdentityX({
49
+ identityX,
50
+ brandKey,
51
+ user,
52
+ omedaCustomer,
53
+ omedaLinkedFields,
54
+ }, {
55
+ // Do not update core user fields (name, email, etc) on a verified user.
56
+ updateData: !user.verified,
57
+ // Update any linked custom fields with the relevant values from Omeda.
58
+ updateDemographics: true,
59
+ updateDeploymentTypes: true,
60
+ updateProducts: true,
61
+ });
62
+
63
+ // Set the cookie to prevent further resyncs until interval is reached.
64
+ const value = { lastSync: Date.now() };
65
+ const maxAge = identityX.config.get('userResyncInterval', 7 * 24 * 60 * 60 * 1000); // 1 week
66
+ resyncCookie.setTo({ res, value, maxAge });
67
+
68
+ return next();
69
+ });