@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
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
const { asyncRoute } = require('@mindful-web/utils');
|
|
2
|
+
const { get } = require('@mindful-web/object-path');
|
|
3
|
+
const olyticsCookie = require('@mindful-web/marko-web-omeda/olytics/customer-cookie');
|
|
4
|
+
const { getResponseCookies } = require('@mindful-web/utils');
|
|
5
|
+
const query = require('../api/queries/customer-by-encrypted-id');
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
*
|
|
9
|
+
* @param {import('express').Request} req
|
|
10
|
+
* @param {import('express').Response} res
|
|
11
|
+
* @returns {String|null}
|
|
12
|
+
*/
|
|
13
|
+
const findOlyticsId = (req, res) => {
|
|
14
|
+
// Read from request cookies
|
|
15
|
+
const reqId = olyticsCookie.parseFrom(req);
|
|
16
|
+
if (reqId) return reqId;
|
|
17
|
+
// request query param
|
|
18
|
+
const { oly_enc_id: qId } = req.query;
|
|
19
|
+
if (qId && olyticsCookie.clean(qId)) return olyticsCookie.clean(qId);
|
|
20
|
+
// response cookie
|
|
21
|
+
const cookies = getResponseCookies(res);
|
|
22
|
+
const resId = olyticsCookie.parseFrom({ cookies });
|
|
23
|
+
return resId;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* @typedef OIDXRequest
|
|
28
|
+
* @prop {import('@parameter1/omeda-graphql-client')} $omedaGraphQLClient
|
|
29
|
+
* @prop {import('@mindful-web/marko-web-identity-x/service')} identityX
|
|
30
|
+
*
|
|
31
|
+
* @typedef MiddlewareConstructor
|
|
32
|
+
* @prop {string} brandKey The Omeda Brand Key (tenant identifier).
|
|
33
|
+
* @prop {boolean} createFromIdentity If true, create the app user and set identity's external id.
|
|
34
|
+
*
|
|
35
|
+
* @param {MiddlewareConstructor}
|
|
36
|
+
*/
|
|
37
|
+
module.exports = ({
|
|
38
|
+
brandKey,
|
|
39
|
+
createFromIdentity = true,
|
|
40
|
+
}) => asyncRoute(async (req, res, next) => {
|
|
41
|
+
/** @type {OIDXRequest} */
|
|
42
|
+
const { identityX: idx, $omedaGraphQLClient: omeda } = req;
|
|
43
|
+
const cookie = idx.getIdentity(res);
|
|
44
|
+
|
|
45
|
+
// Don't overwrite an existing cookie
|
|
46
|
+
if (cookie) return next();
|
|
47
|
+
|
|
48
|
+
// get oly enc id. if we don't have one, bail
|
|
49
|
+
const omedaId = findOlyticsId(req, res);
|
|
50
|
+
if (!omedaId) return next();
|
|
51
|
+
|
|
52
|
+
// Look up idx user by encrypted id
|
|
53
|
+
const namespace = { provider: 'omeda', tenant: brandKey.toLowerCase(), type: 'customer' };
|
|
54
|
+
const identity = await idx.findUserByExternalId({ identifier: omedaId, namespace });
|
|
55
|
+
if (identity) {
|
|
56
|
+
idx.setIdentityCookie(identity.id);
|
|
57
|
+
return next();
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// If disabled, don't create a user from the incoming identity.
|
|
61
|
+
if (!createFromIdentity) return next();
|
|
62
|
+
|
|
63
|
+
const or = await omeda.query({ query, variables: { id: omedaId } });
|
|
64
|
+
const email = get(or, 'data.customerByEncryptedId.primaryEmailAddress.emailAddress');
|
|
65
|
+
if (email) {
|
|
66
|
+
// Upsert the user and add the external id to it.
|
|
67
|
+
const { id } = await idx.createAppUser({ email });
|
|
68
|
+
await idx.addExternalUserId({
|
|
69
|
+
userId: id,
|
|
70
|
+
identifier: { value: omedaId, type: 'encrypted' },
|
|
71
|
+
namespace,
|
|
72
|
+
});
|
|
73
|
+
idx.setIdentityCookie(id);
|
|
74
|
+
return next();
|
|
75
|
+
}
|
|
76
|
+
return next();
|
|
77
|
+
});
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
const { asyncRoute } = require('@mindful-web/utils');
|
|
2
|
+
const olyticsCookie = require('@mindful-web/marko-web-omeda/olytics/customer-cookie');
|
|
3
|
+
const findEncryptedId = require('../external-id/find-encrypted-customer-id');
|
|
4
|
+
|
|
5
|
+
module.exports = ({
|
|
6
|
+
brandKey,
|
|
7
|
+
}) => asyncRoute(async (req, res, next) => {
|
|
8
|
+
const { user } = await req.identityX.loadActiveContext();
|
|
9
|
+
|
|
10
|
+
if (!user || !user.id) return next();
|
|
11
|
+
const idxEncId = findEncryptedId({ externalIds: user.externalIds, brandKey });
|
|
12
|
+
const cookieEncId = olyticsCookie.parseFrom(req);
|
|
13
|
+
if (!idxEncId && cookieEncId && !res.headersSent) {
|
|
14
|
+
// an identity-x user without an encrypted id is logged in.
|
|
15
|
+
// clear any encrypted id cookies if they are set.
|
|
16
|
+
olyticsCookie.clearFrom(res);
|
|
17
|
+
return next();
|
|
18
|
+
}
|
|
19
|
+
// if theres a mismatch between the idx user encrypted id and the
|
|
20
|
+
// cookie id, reset the cookie id.
|
|
21
|
+
if (idxEncId !== cookieEncId && !res.headersSent) {
|
|
22
|
+
olyticsCookie.setTo(res, idxEncId);
|
|
23
|
+
}
|
|
24
|
+
return next();
|
|
25
|
+
});
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sets the `omeda_promo_code` cookie to identify the customer's promotion source if the
|
|
3
|
+
* `omeda_promo_code` URL parameter is present.
|
|
4
|
+
* '
|
|
5
|
+
* @param {String} omedaPromoCodeCookieName The name of the cookie to interact with
|
|
6
|
+
*/
|
|
7
|
+
module.exports = ({
|
|
8
|
+
omedaPromoCodeCookieName = 'omeda_promo_code',
|
|
9
|
+
} = {}) => (req, res, next) => {
|
|
10
|
+
const { [omedaPromoCodeCookieName]: promoCode } = req.query;
|
|
11
|
+
const incomingPromoCode = `${promoCode || ''}`.trim().toUpperCase();
|
|
12
|
+
|
|
13
|
+
// Set the promo source cookie
|
|
14
|
+
if (incomingPromoCode) {
|
|
15
|
+
const options = { maxAge: 60 * 60 * 24 * 365, httpOnly: false };
|
|
16
|
+
res.cookie(omedaPromoCodeCookieName, incomingPromoCode, options);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
next();
|
|
20
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
const tokenCookie = require('@mindful-web/marko-web-identity-x/utils/token-cookie');
|
|
2
|
+
const olyticsCookie = require('@mindful-web/marko-web-omeda/olytics/customer-cookie');
|
|
3
|
+
|
|
4
|
+
module.exports = () => (req, res, next) => {
|
|
5
|
+
const currentEncId = olyticsCookie.parseFrom(req);
|
|
6
|
+
const idxUserExists = tokenCookie.exists(req);
|
|
7
|
+
|
|
8
|
+
const { oly_enc_id: id, ...q } = req.query;
|
|
9
|
+
const incomingEncId = olyticsCookie.clean(id);
|
|
10
|
+
|
|
11
|
+
if (!idxUserExists && incomingEncId && incomingEncId !== currentEncId) {
|
|
12
|
+
// No IdentityX context, set the cookie and move on
|
|
13
|
+
olyticsCookie.setTo(res, incomingEncId);
|
|
14
|
+
next();
|
|
15
|
+
} else if (idxUserExists && incomingEncId && incomingEncId !== currentEncId) {
|
|
16
|
+
// Incoming id conflicts!
|
|
17
|
+
const params = (new URLSearchParams(q)).toString();
|
|
18
|
+
const redirectTo = `${req.path}${params ? `?${params}` : ''}`;
|
|
19
|
+
res.redirect(302, redirectTo);
|
|
20
|
+
} else {
|
|
21
|
+
next();
|
|
22
|
+
}
|
|
23
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Builds a map of user answers to custom select/boolean questions.
|
|
3
|
+
*/
|
|
4
|
+
module.exports = (user) => {
|
|
5
|
+
const answeredQuestionMap = new Map();
|
|
6
|
+
user.customSelectFieldAnswers.forEach((select) => {
|
|
7
|
+
if (!select.hasAnswered) return;
|
|
8
|
+
answeredQuestionMap.set(select.field.id, true);
|
|
9
|
+
});
|
|
10
|
+
user.customBooleanFieldAnswers.forEach((boolean) => {
|
|
11
|
+
if (!boolean.hasAnswered) return;
|
|
12
|
+
answeredQuestionMap.set(boolean.field.id, true);
|
|
13
|
+
});
|
|
14
|
+
user.customTextFieldAnswers.forEach((field) => {
|
|
15
|
+
if (!field.hasAnswered) return;
|
|
16
|
+
answeredQuestionMap.set(field.field.id, true);
|
|
17
|
+
});
|
|
18
|
+
return answeredQuestionMap;
|
|
19
|
+
};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
const gql = require('graphql-tag');
|
|
2
|
+
|
|
3
|
+
const query = gql`
|
|
4
|
+
query CustomerByEncryptedId($id: String!) {
|
|
5
|
+
customerByEncryptedId(input: { id: $id, errorOnNotFound: false }) {
|
|
6
|
+
id
|
|
7
|
+
firstName
|
|
8
|
+
lastName
|
|
9
|
+
title
|
|
10
|
+
companyName
|
|
11
|
+
primaryPostalAddress {
|
|
12
|
+
countryCode
|
|
13
|
+
regionCode
|
|
14
|
+
postalCode
|
|
15
|
+
street
|
|
16
|
+
extraAddress
|
|
17
|
+
city
|
|
18
|
+
}
|
|
19
|
+
primaryMobileNumber {
|
|
20
|
+
phoneNumber
|
|
21
|
+
}
|
|
22
|
+
primaryPhoneNumber {
|
|
23
|
+
phoneNumber
|
|
24
|
+
}
|
|
25
|
+
demographics {
|
|
26
|
+
demographic { id description }
|
|
27
|
+
value { id description }
|
|
28
|
+
valueText
|
|
29
|
+
writeInDesc
|
|
30
|
+
}
|
|
31
|
+
primaryEmailAddress {
|
|
32
|
+
optInStatus { deploymentTypeId status { id } }
|
|
33
|
+
}
|
|
34
|
+
subscriptions {
|
|
35
|
+
product { id deploymentTypeId }
|
|
36
|
+
receive
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
`;
|
|
41
|
+
|
|
42
|
+
module.exports = async ({ omedaGraphQLClient, encryptedCustomerId }) => {
|
|
43
|
+
const variables = { id: encryptedCustomerId };
|
|
44
|
+
const { data } = await omedaGraphQLClient.query({ query, variables });
|
|
45
|
+
return data.customerByEncryptedId;
|
|
46
|
+
};
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
const gql = require('graphql-tag');
|
|
2
|
+
const { getAsArray } = require('@mindful-web/object-path');
|
|
3
|
+
const isOmedaDeploymentTypeId = require('../external-id/is-deployment-type-id');
|
|
4
|
+
const isOmedaDemographicId = require('../external-id/is-demographic-id');
|
|
5
|
+
const isOmedaProductId = require('../external-id/is-product-id');
|
|
6
|
+
|
|
7
|
+
const query = gql`
|
|
8
|
+
query GetCustomFields {
|
|
9
|
+
fields {
|
|
10
|
+
edges {
|
|
11
|
+
node {
|
|
12
|
+
id
|
|
13
|
+
name
|
|
14
|
+
type
|
|
15
|
+
active
|
|
16
|
+
externalId {
|
|
17
|
+
id
|
|
18
|
+
namespace { provider tenant type }
|
|
19
|
+
identifier { value }
|
|
20
|
+
}
|
|
21
|
+
... on SelectField {
|
|
22
|
+
multiple
|
|
23
|
+
options {
|
|
24
|
+
id
|
|
25
|
+
externalIdentifier
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
... on BooleanField {
|
|
30
|
+
whenTrue { type value }
|
|
31
|
+
whenFalse { type value }
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
`;
|
|
38
|
+
|
|
39
|
+
module.exports = async ({
|
|
40
|
+
identityX,
|
|
41
|
+
brandKey,
|
|
42
|
+
}) => {
|
|
43
|
+
const { data } = await identityX.client.query({ query });
|
|
44
|
+
|
|
45
|
+
const omedaLinkedFields = {
|
|
46
|
+
demographic: [],
|
|
47
|
+
deploymentType: [],
|
|
48
|
+
product: [],
|
|
49
|
+
};
|
|
50
|
+
getAsArray(data, 'fields.edges').forEach((edge) => {
|
|
51
|
+
const { node: field } = edge;
|
|
52
|
+
const { externalId } = field;
|
|
53
|
+
if (!field.active || !externalId) return;
|
|
54
|
+
if (isOmedaDemographicId({ externalId, brandKey })) {
|
|
55
|
+
omedaLinkedFields.demographic.push(field);
|
|
56
|
+
}
|
|
57
|
+
if (field.type === 'boolean' && isOmedaDeploymentTypeId({ externalId, brandKey })) {
|
|
58
|
+
omedaLinkedFields.deploymentType.push(field);
|
|
59
|
+
}
|
|
60
|
+
if (field.type === 'boolean' && isOmedaProductId({ externalId, brandKey })) {
|
|
61
|
+
omedaLinkedFields.product.push(field);
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
return omedaLinkedFields;
|
|
66
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
const getAnsweredQuestionMap = require('./get-answered-question-map');
|
|
2
|
+
const getOmedaCustomerRecord = require('./get-omeda-customer-record');
|
|
3
|
+
const getOmedaLinkedFields = require('./get-omeda-linked-fields');
|
|
4
|
+
const setOmedaData = require('./set-omeda-data');
|
|
5
|
+
const setOmedaDemographics = require('./set-omeda-demographics');
|
|
6
|
+
const setOmedaDeploymentTypes = require('./set-omeda-deployment-types');
|
|
7
|
+
const setOmedaProducts = require('./set-omeda-products');
|
|
8
|
+
const updateIdentityX = require('./update-identity-x');
|
|
9
|
+
|
|
10
|
+
module.exports = {
|
|
11
|
+
getAnsweredQuestionMap,
|
|
12
|
+
getOmedaCustomerRecord,
|
|
13
|
+
getOmedaLinkedFields,
|
|
14
|
+
setOmedaData,
|
|
15
|
+
setOmedaDemographics,
|
|
16
|
+
setOmedaDeploymentTypes,
|
|
17
|
+
setOmedaProducts,
|
|
18
|
+
updateIdentityX,
|
|
19
|
+
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
const gql = require('graphql-tag');
|
|
2
|
+
const { get } = require('@mindful-web/object-path');
|
|
3
|
+
|
|
4
|
+
const SET_OMEDA_DATA = gql`
|
|
5
|
+
mutation SetOmedaData($input: SetAppUserUnverifiedDataMutationInput!) {
|
|
6
|
+
setAppUserUnverifiedData(input: $input) { id }
|
|
7
|
+
}
|
|
8
|
+
`;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Sets Omeda customer data (such as name, address, etc) to the IdentityX user fields.
|
|
12
|
+
*/
|
|
13
|
+
module.exports = async ({ identityX, user, omedaCustomer }) => {
|
|
14
|
+
const input = {
|
|
15
|
+
email: user.email,
|
|
16
|
+
|
|
17
|
+
givenName: omedaCustomer.firstName,
|
|
18
|
+
familyName: omedaCustomer.lastName,
|
|
19
|
+
organization: omedaCustomer.companyName,
|
|
20
|
+
organizationTitle: omedaCustomer.title,
|
|
21
|
+
|
|
22
|
+
countryCode: get(omedaCustomer, 'primaryPostalAddress.countryCode'),
|
|
23
|
+
regionCode: get(omedaCustomer, 'primaryPostalAddress.regionCode'),
|
|
24
|
+
postalCode: get(omedaCustomer, 'primaryPostalAddress.postalCode'),
|
|
25
|
+
street: get(omedaCustomer, 'primaryPostalAddress.street'),
|
|
26
|
+
addressExtra: get(omedaCustomer, 'primaryPostalAddress.extraAddress'),
|
|
27
|
+
city: get(omedaCustomer, 'primaryPostalAddress.city'),
|
|
28
|
+
mobileNumber: get(omedaCustomer, 'primaryMobileNumber.phoneNumber'),
|
|
29
|
+
phoneNumber: get(omedaCustomer, 'primaryPhoneNumber.phoneNumber'),
|
|
30
|
+
};
|
|
31
|
+
return identityX.client.mutate({
|
|
32
|
+
mutation: SET_OMEDA_DATA,
|
|
33
|
+
variables: { input },
|
|
34
|
+
});
|
|
35
|
+
};
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
const gql = require('graphql-tag');
|
|
2
|
+
const getAnsweredQuestionMap = require('./get-answered-question-map');
|
|
3
|
+
|
|
4
|
+
const SET_OMEDA_SELECT_FIELD_ANSWERS = gql`
|
|
5
|
+
mutation SetOmedaSelectFieldAnswers($input: UpdateAppUserCustomSelectAnswersMutationInput!) {
|
|
6
|
+
updateAppUserCustomSelectAnswers(input: $input) { id }
|
|
7
|
+
}
|
|
8
|
+
`;
|
|
9
|
+
|
|
10
|
+
const SET_OMEDA_BOOLEAN_FIELD_ANSWERS = gql`
|
|
11
|
+
mutation SetOmedaBooleanFieldAnswers($input: UpdateAppUserCustomBooleanAnswersMutationInput!) {
|
|
12
|
+
updateAppUserCustomBooleanAnswers(input: $input) { id }
|
|
13
|
+
}
|
|
14
|
+
`;
|
|
15
|
+
|
|
16
|
+
const SET_OMEDA_TEXT_FIELD_ANSWERS = gql`
|
|
17
|
+
mutation SetOmedaTextFieldAnswers($input: UpdateAppUserCustomTextAnswersMutationInput!) {
|
|
18
|
+
updateAppUserCustomTextAnswers(input: $input) { id }
|
|
19
|
+
}
|
|
20
|
+
`;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Sets IdentityX custom select answers based on the Omeda customer's demographics
|
|
24
|
+
*/
|
|
25
|
+
module.exports = async ({
|
|
26
|
+
identityX,
|
|
27
|
+
user,
|
|
28
|
+
omedaCustomer,
|
|
29
|
+
fields = [],
|
|
30
|
+
}) => {
|
|
31
|
+
const answeredQuestionMap = getAnsweredQuestionMap(user);
|
|
32
|
+
|
|
33
|
+
const omedaCustomerDemoValuesMap = omedaCustomer.demographics
|
|
34
|
+
.reduce((map, {
|
|
35
|
+
demographic,
|
|
36
|
+
value,
|
|
37
|
+
valueText,
|
|
38
|
+
writeInDesc,
|
|
39
|
+
}) => {
|
|
40
|
+
if (valueText) {
|
|
41
|
+
map.set(`${demographic.id}`, { valueText });
|
|
42
|
+
return map;
|
|
43
|
+
}
|
|
44
|
+
if (!value || !value.id) return map; // skip demos without value IDs
|
|
45
|
+
const id = `${demographic.id}`;
|
|
46
|
+
const ids = map.has(id) ? map.get(id).ids : new Set();
|
|
47
|
+
ids.add(`${value.id}`);
|
|
48
|
+
map.set(id, { ids, writeInDesc });
|
|
49
|
+
return map;
|
|
50
|
+
}, new Map());
|
|
51
|
+
|
|
52
|
+
const booleanAnswerMap = new Map();
|
|
53
|
+
const selectAnswerMap = new Map();
|
|
54
|
+
const textAnswerMap = new Map();
|
|
55
|
+
fields.forEach((field) => {
|
|
56
|
+
if (answeredQuestionMap.has(field.id)) return;
|
|
57
|
+
const { value: demoId } = field.externalId.identifier;
|
|
58
|
+
if (!omedaCustomerDemoValuesMap.has(demoId)) return;
|
|
59
|
+
const value = omedaCustomerDemoValuesMap.get(demoId);
|
|
60
|
+
const { ids: valueIdSet, valueText, writeInDesc } = value;
|
|
61
|
+
|
|
62
|
+
if (field.type === 'text' && valueText) {
|
|
63
|
+
textAnswerMap.set(field.id, valueText);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (field.type === 'select') {
|
|
67
|
+
field.options.forEach((option) => {
|
|
68
|
+
const { externalIdentifier } = option;
|
|
69
|
+
if (!externalIdentifier || !valueIdSet.has(externalIdentifier)) return;
|
|
70
|
+
const ids = selectAnswerMap.has(field.id) ? selectAnswerMap.get(field.id).ids : new Set();
|
|
71
|
+
ids.add(option.id);
|
|
72
|
+
selectAnswerMap.set(field.id, { ids, writeInDesc });
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (field.type === 'boolean') {
|
|
77
|
+
const { whenTrue, whenFalse } = field;
|
|
78
|
+
// What is this? Why are we setting it twice?
|
|
79
|
+
if (whenTrue.type === 'INTEGER' && valueIdSet.has(`${whenTrue.value}`)) {
|
|
80
|
+
booleanAnswerMap.set(field.id, true);
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
if (whenFalse.type === 'INTEGER' && valueIdSet.has(`${whenFalse.value}`)) {
|
|
84
|
+
booleanAnswerMap.set(field.id, false);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
await (async () => {
|
|
90
|
+
if (!selectAnswerMap.size) return;
|
|
91
|
+
const answers = [];
|
|
92
|
+
selectAnswerMap.forEach((value, fieldId) => {
|
|
93
|
+
const optionIds = [...value.ids];
|
|
94
|
+
answers.push({
|
|
95
|
+
fieldId,
|
|
96
|
+
optionIds,
|
|
97
|
+
...(value.writeInDesc && {
|
|
98
|
+
writeInValues: optionIds.map((id) => ({
|
|
99
|
+
optionId: id,
|
|
100
|
+
value: value.writeInDesc,
|
|
101
|
+
})),
|
|
102
|
+
}),
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
await identityX.client.mutate({
|
|
106
|
+
mutation: SET_OMEDA_SELECT_FIELD_ANSWERS,
|
|
107
|
+
variables: { input: { id: user.id, answers } },
|
|
108
|
+
context: { apiToken: identityX.config.getApiToken() },
|
|
109
|
+
});
|
|
110
|
+
})();
|
|
111
|
+
|
|
112
|
+
await (async () => {
|
|
113
|
+
if (!booleanAnswerMap.size) return;
|
|
114
|
+
const answers = [];
|
|
115
|
+
booleanAnswerMap.forEach((value, fieldId) => {
|
|
116
|
+
answers.push({ fieldId, value });
|
|
117
|
+
});
|
|
118
|
+
await identityX.client.mutate({
|
|
119
|
+
mutation: SET_OMEDA_BOOLEAN_FIELD_ANSWERS,
|
|
120
|
+
variables: { input: { id: user.id, answers } },
|
|
121
|
+
context: { apiToken: identityX.config.getApiToken() },
|
|
122
|
+
});
|
|
123
|
+
})();
|
|
124
|
+
|
|
125
|
+
await (async () => {
|
|
126
|
+
if (!textAnswerMap.size) return;
|
|
127
|
+
const answers = [];
|
|
128
|
+
textAnswerMap.forEach((value, fieldId) => {
|
|
129
|
+
answers.push({ fieldId, value });
|
|
130
|
+
});
|
|
131
|
+
await identityX.client.mutate({
|
|
132
|
+
mutation: SET_OMEDA_TEXT_FIELD_ANSWERS,
|
|
133
|
+
variables: { input: { id: user.id, answers } },
|
|
134
|
+
context: { apiToken: identityX.config.getApiToken() },
|
|
135
|
+
});
|
|
136
|
+
})();
|
|
137
|
+
};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
const gql = require('graphql-tag');
|
|
2
|
+
const { getAsArray } = require('@mindful-web/object-path');
|
|
3
|
+
const getAnsweredQuestionMap = require('./get-answered-question-map');
|
|
4
|
+
const isOmedaDeploymentType = require('../external-id/is-deployment-type-id');
|
|
5
|
+
|
|
6
|
+
const SET_OMEDA_BOOLEAN_FIELD_ANSWERS = gql`
|
|
7
|
+
mutation SetOmedaBooleanFieldAnswers($input: UpdateAppUserCustomBooleanAnswersMutationInput!) {
|
|
8
|
+
updateAppUserCustomBooleanAnswers(input: $input) { id }
|
|
9
|
+
}
|
|
10
|
+
`;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Sets IdX boolean answers based on the Omeda customer's opt-in status
|
|
14
|
+
*/
|
|
15
|
+
module.exports = async ({
|
|
16
|
+
identityX,
|
|
17
|
+
brandKey,
|
|
18
|
+
user,
|
|
19
|
+
omedaCustomer,
|
|
20
|
+
fields = [],
|
|
21
|
+
}) => {
|
|
22
|
+
const statusMap = getAsArray(omedaCustomer, 'primaryEmailAddress.optInStatus').reduce((map, { deploymentTypeId, status }) => {
|
|
23
|
+
const optedIn = status.id === 'IN';
|
|
24
|
+
map.set(`${deploymentTypeId}`, optedIn);
|
|
25
|
+
return map;
|
|
26
|
+
}, new Map());
|
|
27
|
+
|
|
28
|
+
const answeredQuestionMap = getAnsweredQuestionMap(user);
|
|
29
|
+
const answerMap = fields
|
|
30
|
+
.filter((field) => isOmedaDeploymentType({ externalId: field.externalId, brandKey }))
|
|
31
|
+
.reduce((map, field) => {
|
|
32
|
+
// Only set values that haven't already been answered.
|
|
33
|
+
if (answeredQuestionMap.has(field.id)) return map;
|
|
34
|
+
const { value } = field.externalId.identifier;
|
|
35
|
+
const receive = statusMap.get(`${value}`);
|
|
36
|
+
if (receive == null) return map;
|
|
37
|
+
map.set(field.id, receive);
|
|
38
|
+
return map;
|
|
39
|
+
}, new Map());
|
|
40
|
+
if (!answerMap.size) return;
|
|
41
|
+
|
|
42
|
+
const answers = [];
|
|
43
|
+
answerMap.forEach((value, fieldId) => {
|
|
44
|
+
answers.push({ fieldId, value });
|
|
45
|
+
});
|
|
46
|
+
await identityX.client.mutate({
|
|
47
|
+
mutation: SET_OMEDA_BOOLEAN_FIELD_ANSWERS,
|
|
48
|
+
variables: { input: { id: user.id, answers } },
|
|
49
|
+
context: { apiToken: identityX.config.getApiToken() },
|
|
50
|
+
});
|
|
51
|
+
};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
const gql = require('graphql-tag');
|
|
2
|
+
const { getAsArray } = require('@mindful-web/object-path');
|
|
3
|
+
const getAnsweredQuestionMap = require('./get-answered-question-map');
|
|
4
|
+
const isOmedaProductId = require('../external-id/is-product-id');
|
|
5
|
+
|
|
6
|
+
const SET_OMEDA_BOOLEAN_FIELD_ANSWERS = gql`
|
|
7
|
+
mutation SetOmedaBooleanFieldAnswers($input: UpdateAppUserCustomBooleanAnswersMutationInput!) {
|
|
8
|
+
updateAppUserCustomBooleanAnswers(input: $input) { id }
|
|
9
|
+
}
|
|
10
|
+
`;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Sets IdX boolean answers based on the Omeda customer's opt-in status
|
|
14
|
+
*/
|
|
15
|
+
module.exports = async ({
|
|
16
|
+
identityX,
|
|
17
|
+
brandKey,
|
|
18
|
+
user,
|
|
19
|
+
omedaCustomer,
|
|
20
|
+
fields = [],
|
|
21
|
+
}) => {
|
|
22
|
+
const statusMap = getAsArray(omedaCustomer, 'subscriptions').reduce((map, { product, receive }) => {
|
|
23
|
+
map.set(`${product.id}`, receive);
|
|
24
|
+
return map;
|
|
25
|
+
}, new Map());
|
|
26
|
+
|
|
27
|
+
const answeredQuestionMap = getAnsweredQuestionMap(user);
|
|
28
|
+
const answerMap = fields
|
|
29
|
+
.filter((field) => isOmedaProductId({ externalId: field.externalId, brandKey }))
|
|
30
|
+
.reduce((map, field) => {
|
|
31
|
+
// Only set values that haven't already been answered.
|
|
32
|
+
if (answeredQuestionMap.has(field.id)) return map;
|
|
33
|
+
const { value } = field.externalId.identifier;
|
|
34
|
+
const receive = statusMap.get(`${value}`);
|
|
35
|
+
if (receive == null) return map;
|
|
36
|
+
map.set(field.id, receive);
|
|
37
|
+
return map;
|
|
38
|
+
}, new Map());
|
|
39
|
+
if (!answerMap.size) return;
|
|
40
|
+
|
|
41
|
+
const answers = [];
|
|
42
|
+
answerMap.forEach((value, fieldId) => {
|
|
43
|
+
answers.push({ fieldId, value });
|
|
44
|
+
});
|
|
45
|
+
await identityX.client.mutate({
|
|
46
|
+
mutation: SET_OMEDA_BOOLEAN_FIELD_ANSWERS,
|
|
47
|
+
variables: { input: { id: user.id, answers } },
|
|
48
|
+
context: { apiToken: identityX.config.getApiToken() },
|
|
49
|
+
});
|
|
50
|
+
};
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
const setOmedaData = require('./set-omeda-data');
|
|
2
|
+
const setOmedaDemographics = require('./set-omeda-demographics');
|
|
3
|
+
const setOmedaDeploymentTypes = require('./set-omeda-deployment-types');
|
|
4
|
+
const setOmedaProducts = require('./set-omeda-products');
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Provides a standard interface to update IdentityX user data from Omeda. All updates to IdX user
|
|
8
|
+
* data should run through this utility.
|
|
9
|
+
*
|
|
10
|
+
* @note These updates should NEVER be run concurrently, to prevent a race condition in the IdX API.
|
|
11
|
+
*
|
|
12
|
+
* @param {object} args Arguments to pass along to update utilities
|
|
13
|
+
* @param {object} flags Keys indicate which updates should be executed.
|
|
14
|
+
* @param {object} flags.updateData If true, update the root fields (first/last name,etc)
|
|
15
|
+
* @param {object} flags.updateDemographics If true, update custom select fields
|
|
16
|
+
* @param {object} flags.updateDeploymentTypes If true, update boolean `deploymentType` fields
|
|
17
|
+
* @param {object} flags.updateProducts If true, update boolean `product` fields
|
|
18
|
+
*/
|
|
19
|
+
module.exports = async (
|
|
20
|
+
{
|
|
21
|
+
identityX,
|
|
22
|
+
brandKey,
|
|
23
|
+
user,
|
|
24
|
+
omedaCustomer,
|
|
25
|
+
omedaLinkedFields = {},
|
|
26
|
+
} = {},
|
|
27
|
+
{
|
|
28
|
+
updateData = false,
|
|
29
|
+
updateDemographics = false,
|
|
30
|
+
updateDeploymentTypes = false,
|
|
31
|
+
updateProducts = false,
|
|
32
|
+
} = {},
|
|
33
|
+
) => {
|
|
34
|
+
if (updateData) {
|
|
35
|
+
await setOmedaData({
|
|
36
|
+
identityX,
|
|
37
|
+
user,
|
|
38
|
+
omedaCustomer,
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
if (updateDemographics) {
|
|
42
|
+
await setOmedaDemographics({
|
|
43
|
+
identityX,
|
|
44
|
+
brandKey,
|
|
45
|
+
user,
|
|
46
|
+
omedaCustomer,
|
|
47
|
+
fields: omedaLinkedFields.demographic,
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
if (updateDeploymentTypes) {
|
|
51
|
+
await setOmedaDeploymentTypes({
|
|
52
|
+
identityX,
|
|
53
|
+
brandKey,
|
|
54
|
+
user,
|
|
55
|
+
omedaCustomer,
|
|
56
|
+
fields: omedaLinkedFields.deploymentType,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
if (updateProducts) {
|
|
60
|
+
await setOmedaProducts({
|
|
61
|
+
identityX,
|
|
62
|
+
brandKey,
|
|
63
|
+
user,
|
|
64
|
+
omedaCustomer,
|
|
65
|
+
fields: omedaLinkedFields.product,
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@mindful-web/marko-web-omeda-identity-x",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Marko Omeda+IdentityX integration tools",
|
|
5
|
+
"repository": "https://github.com/parameter1/mindful-web/tree/main/packages/marko-web-omeda-identity-x",
|
|
6
|
+
"author": "Josh Worden <josh@parameter1.com>",
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"scripts": {
|
|
9
|
+
"lint:fix": "yarn lint --fix",
|
|
10
|
+
"lint": "eslint --ext .js --ext .vue --max-warnings 5 ./",
|
|
11
|
+
"compile": "mindful-web-marko-compile compile",
|
|
12
|
+
"prepublish": "yarn compile --silent",
|
|
13
|
+
"test": "yarn compile --no-clean && yarn lint"
|
|
14
|
+
},
|
|
15
|
+
"dependencies": {
|
|
16
|
+
"@mindful-web/marko-web-identity-x": "^1.0.0",
|
|
17
|
+
"@mindful-web/marko-web-omeda": "^1.0.0",
|
|
18
|
+
"@mindful-web/object-path": "^1.0.0",
|
|
19
|
+
"@mindful-web/utils": "^1.0.0",
|
|
20
|
+
"@parameter1/joi": "^1.2.10",
|
|
21
|
+
"graphql": "^14.7.0",
|
|
22
|
+
"graphql-tag": "^2.12.6"
|
|
23
|
+
},
|
|
24
|
+
"peerDependencies": {
|
|
25
|
+
"@mindful-web/marko-core": "^0.0.0",
|
|
26
|
+
"@mindful-web/marko-web": "^0.0.0",
|
|
27
|
+
"express": "^4.18.2"
|
|
28
|
+
},
|
|
29
|
+
"publishConfig": {
|
|
30
|
+
"access": "public"
|
|
31
|
+
},
|
|
32
|
+
"gitHead": "0b77cab713eb5841202bb86c7119949866bc68b5"
|
|
33
|
+
}
|