@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
@@ -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
+ }