@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.
- package/.eslintignore +1 -0
- package/LICENSE +21 -0
- package/README.md +86 -0
- package/add-integration-hooks.js +119 -0
- package/api/queries/customer-by-encrypted-id.js +10 -0
- package/browser/.eslintrc.js +1 -0
- package/browser/index.js +43 -0
- package/browser/rapid-identify.vue +34 -0
- package/components/identify.marko +16 -0
- package/components/identify.marko.js +60 -0
- package/components/marko.json +6 -0
- package/external-id/find-encrypted-customer-id.js +10 -0
- package/external-id/get-encrypted-customer-id.js +7 -0
- package/external-id/get-omeda-namespaces.js +16 -0
- package/external-id/is-demographic-id.js +8 -0
- package/external-id/is-deployment-type-id.js +8 -0
- package/external-id/is-encrypted-customer-id.js +10 -0
- package/external-id/is-omeda-namespace.js +18 -0
- package/external-id/is-product-id.js +8 -0
- package/index.js +261 -0
- package/integration-hooks/index.js +9 -0
- package/integration-hooks/on-authentication-success.js +59 -0
- package/integration-hooks/on-login-link-sent.js +106 -0
- package/integration-hooks/on-user-profile-update.js +54 -0
- package/marko.json +6 -0
- package/middleware/README.md +12 -0
- package/middleware/rapid-identify.js +56 -0
- package/middleware/resync-customer-data.js +69 -0
- package/middleware/set-identity-cookie.js +77 -0
- package/middleware/set-olytics-cookie.js +25 -0
- package/middleware/set-promo-source.js +20 -0
- package/middleware/strip-olytics-param.js +23 -0
- package/omeda-data/get-answered-question-map.js +19 -0
- package/omeda-data/get-omeda-customer-record.js +46 -0
- package/omeda-data/get-omeda-linked-fields.js +66 -0
- package/omeda-data/index.js +19 -0
- package/omeda-data/set-omeda-data.js +35 -0
- package/omeda-data/set-omeda-demographics.js +137 -0
- package/omeda-data/set-omeda-deployment-types.js +51 -0
- package/omeda-data/set-omeda-products.js +50 -0
- package/omeda-data/update-identity-x.js +68 -0
- package/package.json +33 -0
- package/rapid-identify.js +194 -0
- package/routes/rapid-identify.js +70 -0
- package/utils/append-data.js +24 -0
- package/utils/build-behavior.js +32 -0
- package/utils/extract-promo-code.js +8 -0
- package/utils/get-cookie-id.js +11 -0
- package/utils/resync-cookie.js +25 -0
- package/validation/props.js +20 -0
- package/validation/schemas/append-behavior.js +7 -0
- package/validation/schemas/append-demographic.js +8 -0
- package/validation/schemas/append-promo-code.js +5 -0
- package/validation/schemas/behavior.js +7 -0
- package/validation/schemas/hook-behavior.js +12 -0
- package/validation/schemas/hook-demographic.js +16 -0
- package/validation/schemas/hook-promo-code.js +12 -0
- package/validation/schemas/index.js +19 -0
- 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,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
|
+
});
|