@mindful-web/marko-web-identity-x 1.79.7 → 1.79.8

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/CLAUDE.md CHANGED
@@ -105,7 +105,55 @@ conflict-guarded; non-fatal via `config.get('onHookError')`) → `impersonateApp
105
105
  `onAuthenticationSuccess`, same order/hooks as email-link so Omeda rapid-identify + subscription
106
106
  sync run identically. `profile-gate` computes `requiresUserInput` by checking required fields are
107
107
  non-empty (deliberately not `forceProfileReVerification`). Responds `{ ok, requiresUserInput,
108
- redirectTo, user, entity, … }`; the client redirects to `/user/profile` when input is required.
108
+ redirectTo, user, entity, additionalEventData, … }`; the client redirects to `/user/profile` when
109
+ input is required.
110
+
111
+ **`additionalEventData` and the auto-signup union.** Tenant Omeda formatters *assign*
112
+ `additionalEventData.autoSignups` in place, and that object is the only channel by which the browser
113
+ learns a subscription was created (see analytics below). Where they assign it varies, and three
114
+ repos do it twice:
115
+
116
+ | Writes `autoSignups` in | Repos |
117
+ | --- | --- |
118
+ | `onLoginLinkSentFormatter` only | ab-media, allured, encore360, pmmi |
119
+ | `onAuthenticationSuccessFormatter` only | fusable, cox-matthews |
120
+ | **Both** | industrial (both pkgs), ironmarkets, watt |
121
+
122
+ So the route gives **each hook its own object** and merges the two afterwards, taking the union of
123
+ `autoSignups` deduped by `productId`. Sharing one reference (the obvious move, and what
124
+ `routes/authenticate.js` does — it only ever calls one hook) would let the second hook's assignment
125
+ clobber the first, silently dropping a whole set of subscribe events on the both-hook repos. In the
126
+ email-link flow those hooks fire on **separate requests** (login vs authenticate) and each reports
127
+ its own set, so the union is what preserves existing behaviour. Do **not** "simplify" this back to a
128
+ shared object.
129
+
130
+ `requiresUserInput` goes to `onAuthenticationSuccess` **only**: the onLoginLinkSent formatters read a
131
+ truthy value as "skip the Omeda subscription lookup" and then opt the user into every configured
132
+ deployment type without deduping, firing a phantom auto-signup for an existing Omeda customer who
133
+ merely lacks an idx required field. The email-link flow never exposes it there either —
134
+ `routes/login.js` 400s before any hook runs.
135
+
136
+ `idxAutoSignupCount` rides on the transaction attributes so "sign-in worked, Omeda updated, GA4
137
+ recorded nothing" — the exact shape of the July 2026 bug — is a visible number rather than something
138
+ a stakeholder finds in a report weeks later.
139
+
140
+ **Analytics parity (`components/google-init.marko`).** The Vue flows get `dataLayer` + p1events for
141
+ free from the EventBus bridge in `browser/index.js`; the One-Tap callback is plain inline JS on a
142
+ `<script>` tag, so it has to emit the same events by hand. Three things it must keep in step with
143
+ `browser/authenticate.vue` + `browser/mixins/*`:
144
+ - **`identity-x-authenticated`** — payload mirrors `global-event-emitter.js`, which sets
145
+ `actionSource`, `loginSource` **and** `source` to the same value. GTM variables in the wild read
146
+ whichever one they were written against; emitting only `actionSource` silently breaks the rest.
147
+ - **`identity-x-auto-signup`** — one event per `additionalEventData.autoSignups` entry, shape
148
+ `{ entity, ...autoSignup, event }` per `global-auto-signup-event-emitter.js`. **This — not
149
+ `identity-x-authenticated` — is what the sites' GA4 "subscribe" conversion triggers on.** The
150
+ Omeda opt-in happens server-side either way, so a missing event here looks like "One Tap doesn't
151
+ subscribe anyone" when the subscription exists and only the tracking is gone. Also mirrored to
152
+ p1events as `Subscription / Subscribe`.
153
+ - **Navigation is deferred ~300ms** after the pushes. The email-link flow navigates in the same tick
154
+ but does so from a fully-loaded `/user/authenticate`; One Tap can resolve far earlier in a page's
155
+ life, before GTM has dispatched. `window.dataLayer` is also **assigned, not guarded** (`= … || []`)
156
+ so a sign-in that beats a deferred container still queues its events instead of dropping them.
109
157
 
110
158
  **Tags** (`components/marko.json`, config read from `req.identityX.config`):
111
159
  - `<marko-web-identity-x-google-init>` — loads the GSI client + defines `window.handleGoogleOneTapCredential`;
@@ -122,7 +170,7 @@ meter / gate.
122
170
  **Telemetry.** Two seams. Neither needs site configuration:
123
171
  - `config.addRequestAttributes` — called with a flat object as the route progresses
124
172
  (`idxGoogleStep`, `idxUserId`, `idxCreatedNewUser`, `idxRequiresUserInput`, `idxGoogleSurface`,
125
- `idxGoogleMissingFieldsBehavior`). **Defaults to `utils/add-custom-attributes.js`**, which tags
173
+ `idxGoogleMissingFieldsBehavior`, `idxAutoSignupCount`). **Defaults to `utils/add-custom-attributes.js`**, which tags
126
174
  the current New Relic transaction, so the attributes ride on the `Transaction` *and* any
127
175
  `TransactionError` it produces — a 500 says which step failed instead of being a bare GraphQL
128
176
  message. That default resolves the agent **only if the app already loaded it** (checks
@@ -2,7 +2,7 @@
2
2
  "use strict";
3
3
 
4
4
  var marko_template = module.exports = require("marko/dist/html").t(__filename),
5
- marko_componentType = "/@mindful-web/marko-web-identity-x$1.79.7/components/access.marko",
5
+ marko_componentType = "/@mindful-web/marko-web-identity-x$1.79.8/components/access.marko",
6
6
  marko_component = require("./access.marko"),
7
7
  marko_renderer = require("marko/dist/runtime/components/renderer"),
8
8
  module_objectPath_module = require("@mindful-web/object-path"),
@@ -53,7 +53,7 @@ marko_template._ = marko_renderer(render, {
53
53
  }, marko_component);
54
54
 
55
55
  marko_template.meta = {
56
- id: "/@mindful-web/marko-web-identity-x$1.79.7/components/access.marko",
56
+ id: "/@mindful-web/marko-web-identity-x$1.79.8/components/access.marko",
57
57
  component: "./access.marko",
58
58
  tags: [
59
59
  "@mindful-web/marko-core/components/resolve.marko"
@@ -2,7 +2,7 @@
2
2
  "use strict";
3
3
 
4
4
  var marko_template = module.exports = require("marko/dist/html").t(__filename),
5
- marko_componentType = "/@mindful-web/marko-web-identity-x$1.79.7/components/comment-stream.marko",
5
+ marko_componentType = "/@mindful-web/marko-web-identity-x$1.79.8/components/comment-stream.marko",
6
6
  marko_component = require("./comment-stream.marko"),
7
7
  marko_renderer = require("marko/dist/runtime/components/renderer"),
8
8
  module_objectPath_module = require("@mindful-web/object-path"),
@@ -91,7 +91,7 @@ marko_template._ = marko_renderer(render, {
91
91
  }, marko_component);
92
92
 
93
93
  marko_template.meta = {
94
- id: "/@mindful-web/marko-web-identity-x$1.79.7/components/comment-stream.marko",
94
+ id: "/@mindful-web/marko-web-identity-x$1.79.8/components/comment-stream.marko",
95
95
  component: "./comment-stream.marko",
96
96
  tags: [
97
97
  "@mindful-web/marko-web/components/browser-component.marko",
@@ -2,7 +2,7 @@
2
2
  "use strict";
3
3
 
4
4
  var marko_template = module.exports = require("marko/dist/html").t(__filename),
5
- marko_componentType = "/@mindful-web/marko-web-identity-x$1.79.7/components/context.marko",
5
+ marko_componentType = "/@mindful-web/marko-web-identity-x$1.79.8/components/context.marko",
6
6
  marko_component = require("./context.marko"),
7
7
  marko_renderer = require("marko/dist/runtime/components/renderer"),
8
8
  marko_dynamicTag = require("marko/dist/runtime/helpers/dynamic-tag"),
@@ -49,7 +49,7 @@ marko_template._ = marko_renderer(render, {
49
49
  }, marko_component);
50
50
 
51
51
  marko_template.meta = {
52
- id: "/@mindful-web/marko-web-identity-x$1.79.7/components/context.marko",
52
+ id: "/@mindful-web/marko-web-identity-x$1.79.8/components/context.marko",
53
53
  component: "./context.marko",
54
54
  tags: [
55
55
  "@mindful-web/marko-core/components/resolve.marko"
@@ -2,7 +2,7 @@
2
2
  "use strict";
3
3
 
4
4
  var marko_template = module.exports = require("marko/dist/html").t(__filename),
5
- marko_componentType = "/@mindful-web/marko-web-identity-x$1.79.7/components/form-access.marko",
5
+ marko_componentType = "/@mindful-web/marko-web-identity-x$1.79.8/components/form-access.marko",
6
6
  marko_component = require("./form-access.marko"),
7
7
  marko_renderer = require("marko/dist/runtime/components/renderer"),
8
8
  module_defaultValue = require("@mindful-web/marko-core/utils/default-value"),
@@ -179,7 +179,7 @@ marko_template._ = marko_renderer(render, {
179
179
  }, marko_component);
180
180
 
181
181
  marko_template.meta = {
182
- id: "/@mindful-web/marko-web-identity-x$1.79.7/components/form-access.marko",
182
+ id: "/@mindful-web/marko-web-identity-x$1.79.8/components/form-access.marko",
183
183
  component: "./form-access.marko",
184
184
  tags: [
185
185
  "@mindful-web/marko-web/components/browser-component.marko",
@@ -2,7 +2,7 @@
2
2
  "use strict";
3
3
 
4
4
  var marko_template = module.exports = require("marko/dist/html").t(__filename),
5
- marko_componentType = "/@mindful-web/marko-web-identity-x$1.79.7/components/form-authenticate.marko",
5
+ marko_componentType = "/@mindful-web/marko-web-identity-x$1.79.8/components/form-authenticate.marko",
6
6
  marko_component = require("./form-authenticate.marko"),
7
7
  marko_renderer = require("marko/dist/runtime/components/renderer"),
8
8
  module_objectPath_module = require("@mindful-web/object-path"),
@@ -81,7 +81,7 @@ marko_template._ = marko_renderer(render, {
81
81
  }, marko_component);
82
82
 
83
83
  marko_template.meta = {
84
- id: "/@mindful-web/marko-web-identity-x$1.79.7/components/form-authenticate.marko",
84
+ id: "/@mindful-web/marko-web-identity-x$1.79.8/components/form-authenticate.marko",
85
85
  component: "./form-authenticate.marko",
86
86
  tags: [
87
87
  "@mindful-web/marko-web/components/browser-component.marko",
@@ -2,7 +2,7 @@
2
2
  "use strict";
3
3
 
4
4
  var marko_template = module.exports = require("marko/dist/html").t(__filename),
5
- marko_componentType = "/@mindful-web/marko-web-identity-x$1.79.7/components/form-change-email.marko",
5
+ marko_componentType = "/@mindful-web/marko-web-identity-x$1.79.8/components/form-change-email.marko",
6
6
  marko_component = require("./form-change-email.marko"),
7
7
  marko_renderer = require("marko/dist/runtime/components/renderer"),
8
8
  module_objectPath_module = require("@mindful-web/object-path"),
@@ -59,7 +59,7 @@ marko_template._ = marko_renderer(render, {
59
59
  }, marko_component);
60
60
 
61
61
  marko_template.meta = {
62
- id: "/@mindful-web/marko-web-identity-x$1.79.7/components/form-change-email.marko",
62
+ id: "/@mindful-web/marko-web-identity-x$1.79.8/components/form-change-email.marko",
63
63
  component: "./form-change-email.marko",
64
64
  tags: [
65
65
  "@mindful-web/marko-web/components/browser-component.marko",
@@ -2,7 +2,7 @@
2
2
  "use strict";
3
3
 
4
4
  var marko_template = module.exports = require("marko/dist/html").t(__filename),
5
- marko_componentType = "/@mindful-web/marko-web-identity-x$1.79.7/components/form-login.marko",
5
+ marko_componentType = "/@mindful-web/marko-web-identity-x$1.79.8/components/form-login.marko",
6
6
  marko_component = require("./form-login.marko"),
7
7
  marko_renderer = require("marko/dist/runtime/components/renderer"),
8
8
  module_objectPath_module = require("@mindful-web/object-path"),
@@ -92,7 +92,7 @@ marko_template._ = marko_renderer(render, {
92
92
  }, marko_component);
93
93
 
94
94
  marko_template.meta = {
95
- id: "/@mindful-web/marko-web-identity-x$1.79.7/components/form-login.marko",
95
+ id: "/@mindful-web/marko-web-identity-x$1.79.8/components/form-login.marko",
96
96
  component: "./form-login.marko",
97
97
  tags: [
98
98
  "@mindful-web/marko-web/components/browser-component.marko",
@@ -2,7 +2,7 @@
2
2
  "use strict";
3
3
 
4
4
  var marko_template = module.exports = require("marko/dist/html").t(__filename),
5
- marko_componentType = "/@mindful-web/marko-web-identity-x$1.79.7/components/form-logout.marko",
5
+ marko_componentType = "/@mindful-web/marko-web-identity-x$1.79.8/components/form-logout.marko",
6
6
  marko_renderer = require("marko/dist/runtime/components/renderer"),
7
7
  module_defaultValue = require("@mindful-web/marko-core/utils/default-value"),
8
8
  defaultValue = module_defaultValue.default || module_defaultValue,
@@ -29,7 +29,7 @@ marko_template._ = marko_renderer(render, {
29
29
  });
30
30
 
31
31
  marko_template.meta = {
32
- id: "/@mindful-web/marko-web-identity-x$1.79.7/components/form-logout.marko",
32
+ id: "/@mindful-web/marko-web-identity-x$1.79.8/components/form-logout.marko",
33
33
  tags: [
34
34
  "@mindful-web/marko-web/components/browser-component.marko"
35
35
  ]
@@ -2,7 +2,7 @@
2
2
  "use strict";
3
3
 
4
4
  var marko_template = module.exports = require("marko/dist/html").t(__filename),
5
- marko_componentType = "/@mindful-web/marko-web-identity-x$1.79.7/components/form-profile.marko",
5
+ marko_componentType = "/@mindful-web/marko-web-identity-x$1.79.8/components/form-profile.marko",
6
6
  marko_component = require("./form-profile.marko"),
7
7
  marko_renderer = require("marko/dist/runtime/components/renderer"),
8
8
  module_objectPath_module = require("@mindful-web/object-path"),
@@ -84,7 +84,7 @@ marko_template._ = marko_renderer(render, {
84
84
  }, marko_component);
85
85
 
86
86
  marko_template.meta = {
87
- id: "/@mindful-web/marko-web-identity-x$1.79.7/components/form-profile.marko",
87
+ id: "/@mindful-web/marko-web-identity-x$1.79.8/components/form-profile.marko",
88
88
  component: "./form-profile.marko",
89
89
  tags: [
90
90
  "@mindful-web/marko-web/components/browser-component.marko",
@@ -2,7 +2,7 @@
2
2
  "use strict";
3
3
 
4
4
  var marko_template = module.exports = require("marko/dist/html").t(__filename),
5
- marko_componentType = "/@mindful-web/marko-web-identity-x$1.79.7/components/form-register.marko",
5
+ marko_componentType = "/@mindful-web/marko-web-identity-x$1.79.8/components/form-register.marko",
6
6
  marko_renderer = require("marko/dist/runtime/components/renderer"),
7
7
  marko_assign = require("marko/dist/runtime/helpers/assign"),
8
8
  marko_web_identity_x_form_login_template = require("./form-login.marko"),
@@ -25,7 +25,7 @@ marko_template._ = marko_renderer(render, {
25
25
  });
26
26
 
27
27
  marko_template.meta = {
28
- id: "/@mindful-web/marko-web-identity-x$1.79.7/components/form-register.marko",
28
+ id: "/@mindful-web/marko-web-identity-x$1.79.8/components/form-register.marko",
29
29
  tags: [
30
30
  "./form-login.marko"
31
31
  ]
@@ -101,6 +101,9 @@ $ const initScript = `
101
101
  return;
102
102
  }
103
103
  authenticated = true;
104
+ var eventData = data.additionalEventData || {};
105
+ var autoSignups = eventData.autoSignups || [];
106
+
104
107
  if (window.p1events) {
105
108
  // De-anonymize the session to the app user, then record the
106
109
  // authentication — mirrors the identity-x conversion events.
@@ -111,30 +114,89 @@ $ const initScript = `
111
114
  label: eventLabel,
112
115
  props: { idxEntity: data.entity, actionSource: 'google-one-tap' },
113
116
  });
117
+ // One 'Subscription / Subscribe' event per newsletter the server
118
+ // opted this user into, matching what browser/index.js emits for the
119
+ // 'identity-x-auto-signup' bus event in the email-link flow.
120
+ for (var pi = 0; pi < autoSignups.length; pi += 1) {
121
+ window.p1events('track', {
122
+ category: 'Subscription',
123
+ action: 'Subscribe',
124
+ props: {
125
+ idxEntity: data.entity,
126
+ actionSource: autoSignups[pi].actionSource || data.loginSource,
127
+ subscriptionEntity: autoSignups[pi].subscriptionEntity,
128
+ },
129
+ });
130
+ }
114
131
  }
115
- // Push to dataLayer for GTM/GA4 integration, matching the shape
116
- // produced by the standard email-link authenticate flow so existing
117
- // GTM triggers/variables pick it up. The actionSource value
118
- // ('google-one-tap') can be used to populate subscription_source
119
- // or other conversion metadata in GA4.
120
- if (window.dataLayer) {
121
- window.dataLayer.push({
132
+
133
+ // Push to dataLayer for GTM/GA4. Assign rather than guard: GTM can be
134
+ // deferred behind the script loader, and a One-Tap sign-in that lands
135
+ // before the container initializes would otherwise drop its events
136
+ // silently. Pushing into a plain array is safe — GTM replays whatever
137
+ // is already there when it loads.
138
+ window.dataLayer = window.dataLayer || [];
139
+
140
+ // Mirror the payload the standard email-link authenticate flow builds
141
+ // (browser/authenticate.vue + mixins/global-event-emitter.js) so GTM
142
+ // triggers and dataLayer variables resolve identically. That flow sets
143
+ // actionSource, loginSource AND source to the same value; a variable
144
+ // reading any one of the three has to work here too.
145
+ var idxAuthPayload = {
146
+ event: 'identity-x-authenticated',
147
+ 'identity-x': {
122
148
  event: 'identity-x-authenticated',
123
- 'identity-x': {
124
- event: 'identity-x-authenticated',
125
- id: data.user.id,
126
- email: data.user.email,
127
- entity: data.entity,
128
- actionSource: data.loginSource,
129
- },
149
+ ok: true,
150
+ applicationId: data.applicationId,
151
+ user: data.user,
152
+ id: data.user.id,
153
+ email: data.user.email,
154
+ verifiedCount: data.user.verifiedCount,
155
+ entity: data.entity,
156
+ requiresUserInput: data.requiresUserInput,
157
+ createdNewUser: eventData.createdNewUser,
158
+ additionalEventData: eventData,
159
+ actionSource: data.loginSource,
160
+ loginSource: data.loginSource,
161
+ source: data.loginSource,
162
+ },
163
+ };
164
+ window.dataLayer.push(idxAuthPayload);
165
+
166
+ // The 'subscribe' conversion keys off 'identity-x-auto-signup', not
167
+ // 'identity-x-authenticated' — one event per newsletter product the
168
+ // Omeda hook subscribed the user to. Shape matches
169
+ // mixins/global-auto-signup-event-emitter.js -> browser/index.js:
170
+ // { entity, ...autoSignup, event }.
171
+ for (var i = 0; i < autoSignups.length; i += 1) {
172
+ var signupData = { entity: data.entity };
173
+ var autoSignup = autoSignups[i];
174
+ for (var key in autoSignup) {
175
+ if (Object.prototype.hasOwnProperty.call(autoSignup, key)) {
176
+ signupData[key] = autoSignup[key];
177
+ }
178
+ }
179
+ signupData.event = 'identity-x-auto-signup';
180
+ window.dataLayer.push({
181
+ event: 'identity-x-auto-signup',
182
+ 'identity-x': signupData,
130
183
  });
131
184
  }
185
+
186
+ var href;
132
187
  if (data.requiresUserInput) {
133
188
  var destination = data.redirectTo || window.location.pathname;
134
- window.location.href = '/user/profile?returnTo=' + encodeURIComponent(destination);
189
+ href = '/user/profile?returnTo=' + encodeURIComponent(destination);
135
190
  } else {
136
- window.location.href = data.redirectTo || window.location.href;
191
+ href = data.redirectTo || window.location.href;
137
192
  }
193
+ // Give GTM a moment to dispatch the tags above before tearing down the
194
+ // document. The email-link flow navigates in the same tick, but it does
195
+ // so from /user/authenticate after a full page load, where the container
196
+ // is long since warm; a One-Tap sign-in can resolve much earlier in a
197
+ // page's life. A short delay is imperceptible next to the sign-in
198
+ // round-trip and cheaper than losing the conversion.
199
+ window.setTimeout(function() { window.location.href = href; }, 300);
138
200
  })
139
201
  .catch(function(e) {
140
202
  fail('An error occurred during Google sign-in. Please try again.', e);
@@ -2,7 +2,7 @@
2
2
  "use strict";
3
3
 
4
4
  var marko_template = module.exports = require("marko/dist/html").t(__filename),
5
- marko_componentType = "/@mindful-web/marko-web-identity-x$1.79.7/components/google-init.marko",
5
+ marko_componentType = "/@mindful-web/marko-web-identity-x$1.79.8/components/google-init.marko",
6
6
  marko_renderer = require("marko/dist/runtime/components/renderer"),
7
7
  marko_attr = require("marko/dist/runtime/html/helpers/attr"),
8
8
  marko_str = require("marko/dist/runtime/helpers/to-string");
@@ -118,6 +118,9 @@ function render(input, out, __component, component, state) {
118
118
  return;
119
119
  }
120
120
  authenticated = true;
121
+ var eventData = data.additionalEventData || {};
122
+ var autoSignups = eventData.autoSignups || [];
123
+
121
124
  if (window.p1events) {
122
125
  // De-anonymize the session to the app user, then record the
123
126
  // authentication — mirrors the identity-x conversion events.
@@ -128,30 +131,89 @@ function render(input, out, __component, component, state) {
128
131
  label: eventLabel,
129
132
  props: { idxEntity: data.entity, actionSource: 'google-one-tap' },
130
133
  });
134
+ // One 'Subscription / Subscribe' event per newsletter the server
135
+ // opted this user into, matching what browser/index.js emits for the
136
+ // 'identity-x-auto-signup' bus event in the email-link flow.
137
+ for (var pi = 0; pi < autoSignups.length; pi += 1) {
138
+ window.p1events('track', {
139
+ category: 'Subscription',
140
+ action: 'Subscribe',
141
+ props: {
142
+ idxEntity: data.entity,
143
+ actionSource: autoSignups[pi].actionSource || data.loginSource,
144
+ subscriptionEntity: autoSignups[pi].subscriptionEntity,
145
+ },
146
+ });
147
+ }
131
148
  }
132
- // Push to dataLayer for GTM/GA4 integration, matching the shape
133
- // produced by the standard email-link authenticate flow so existing
134
- // GTM triggers/variables pick it up. The actionSource value
135
- // ('google-one-tap') can be used to populate subscription_source
136
- // or other conversion metadata in GA4.
137
- if (window.dataLayer) {
138
- window.dataLayer.push({
149
+
150
+ // Push to dataLayer for GTM/GA4. Assign rather than guard: GTM can be
151
+ // deferred behind the script loader, and a One-Tap sign-in that lands
152
+ // before the container initializes would otherwise drop its events
153
+ // silently. Pushing into a plain array is safe — GTM replays whatever
154
+ // is already there when it loads.
155
+ window.dataLayer = window.dataLayer || [];
156
+
157
+ // Mirror the payload the standard email-link authenticate flow builds
158
+ // (browser/authenticate.vue + mixins/global-event-emitter.js) so GTM
159
+ // triggers and dataLayer variables resolve identically. That flow sets
160
+ // actionSource, loginSource AND source to the same value; a variable
161
+ // reading any one of the three has to work here too.
162
+ var idxAuthPayload = {
163
+ event: 'identity-x-authenticated',
164
+ 'identity-x': {
139
165
  event: 'identity-x-authenticated',
140
- 'identity-x': {
141
- event: 'identity-x-authenticated',
142
- id: data.user.id,
143
- email: data.user.email,
144
- entity: data.entity,
145
- actionSource: data.loginSource,
146
- },
166
+ ok: true,
167
+ applicationId: data.applicationId,
168
+ user: data.user,
169
+ id: data.user.id,
170
+ email: data.user.email,
171
+ verifiedCount: data.user.verifiedCount,
172
+ entity: data.entity,
173
+ requiresUserInput: data.requiresUserInput,
174
+ createdNewUser: eventData.createdNewUser,
175
+ additionalEventData: eventData,
176
+ actionSource: data.loginSource,
177
+ loginSource: data.loginSource,
178
+ source: data.loginSource,
179
+ },
180
+ };
181
+ window.dataLayer.push(idxAuthPayload);
182
+
183
+ // The 'subscribe' conversion keys off 'identity-x-auto-signup', not
184
+ // 'identity-x-authenticated' — one event per newsletter product the
185
+ // Omeda hook subscribed the user to. Shape matches
186
+ // mixins/global-auto-signup-event-emitter.js -> browser/index.js:
187
+ // { entity, ...autoSignup, event }.
188
+ for (var i = 0; i < autoSignups.length; i += 1) {
189
+ var signupData = { entity: data.entity };
190
+ var autoSignup = autoSignups[i];
191
+ for (var key in autoSignup) {
192
+ if (Object.prototype.hasOwnProperty.call(autoSignup, key)) {
193
+ signupData[key] = autoSignup[key];
194
+ }
195
+ }
196
+ signupData.event = 'identity-x-auto-signup';
197
+ window.dataLayer.push({
198
+ event: 'identity-x-auto-signup',
199
+ 'identity-x': signupData,
147
200
  });
148
201
  }
202
+
203
+ var href;
149
204
  if (data.requiresUserInput) {
150
205
  var destination = data.redirectTo || window.location.pathname;
151
- window.location.href = '/user/profile?returnTo=' + encodeURIComponent(destination);
206
+ href = '/user/profile?returnTo=' + encodeURIComponent(destination);
152
207
  } else {
153
- window.location.href = data.redirectTo || window.location.href;
208
+ href = data.redirectTo || window.location.href;
154
209
  }
210
+ // Give GTM a moment to dispatch the tags above before tearing down the
211
+ // document. The email-link flow navigates in the same tick, but it does
212
+ // so from /user/authenticate after a full page load, where the container
213
+ // is long since warm; a One-Tap sign-in can resolve much earlier in a
214
+ // page's life. A short delay is imperceptible next to the sign-in
215
+ // round-trip and cheaper than losing the conversion.
216
+ window.setTimeout(function() { window.location.href = href; }, 300);
155
217
  })
156
218
  .catch(function(e) {
157
219
  fail('An error occurred during Google sign-in. Please try again.', e);
@@ -177,5 +239,5 @@ marko_template._ = marko_renderer(render, {
177
239
  });
178
240
 
179
241
  marko_template.meta = {
180
- id: "/@mindful-web/marko-web-identity-x$1.79.7/components/google-init.marko"
242
+ id: "/@mindful-web/marko-web-identity-x$1.79.8/components/google-init.marko"
181
243
  };
@@ -2,7 +2,7 @@
2
2
  "use strict";
3
3
 
4
4
  var marko_template = module.exports = require("marko/dist/html").t(__filename),
5
- marko_componentType = "/@mindful-web/marko-web-identity-x$1.79.7/components/google-sign-in-button.marko",
5
+ marko_componentType = "/@mindful-web/marko-web-identity-x$1.79.8/components/google-sign-in-button.marko",
6
6
  marko_renderer = require("marko/dist/runtime/components/renderer"),
7
7
  helpers_escape_xml = require("marko/dist/runtime/html/helpers/escape-xml"),
8
8
  marko_escapeXml = helpers_escape_xml.x,
@@ -40,5 +40,5 @@ marko_template._ = marko_renderer(render, {
40
40
  });
41
41
 
42
42
  marko_template.meta = {
43
- id: "/@mindful-web/marko-web-identity-x$1.79.7/components/google-sign-in-button.marko"
43
+ id: "/@mindful-web/marko-web-identity-x$1.79.8/components/google-sign-in-button.marko"
44
44
  };
@@ -2,7 +2,7 @@
2
2
  "use strict";
3
3
 
4
4
  var marko_template = module.exports = require("marko/dist/html").t(__filename),
5
- marko_componentType = "/@mindful-web/marko-web-identity-x$1.79.7/components/identify.marko",
5
+ marko_componentType = "/@mindful-web/marko-web-identity-x$1.79.8/components/identify.marko",
6
6
  marko_component = require("./identify.marko"),
7
7
  marko_renderer = require("marko/dist/runtime/components/renderer"),
8
8
  module_objectPath_module = require("@mindful-web/object-path"),
@@ -50,7 +50,7 @@ marko_template._ = marko_renderer(render, {
50
50
  }, marko_component);
51
51
 
52
52
  marko_template.meta = {
53
- id: "/@mindful-web/marko-web-identity-x$1.79.7/components/identify.marko",
53
+ id: "/@mindful-web/marko-web-identity-x$1.79.8/components/identify.marko",
54
54
  component: "./identify.marko",
55
55
  tags: [
56
56
  "@mindful-web/marko-web-gtm/components/push.marko",
@@ -2,7 +2,7 @@
2
2
  "use strict";
3
3
 
4
4
  var marko_template = module.exports = require("marko/dist/html").t(__filename),
5
- marko_componentType = "/@mindful-web/marko-web-identity-x$1.79.7/components/non-auth-identify.marko",
5
+ marko_componentType = "/@mindful-web/marko-web-identity-x$1.79.8/components/non-auth-identify.marko",
6
6
  marko_component = require("./non-auth-identify.marko"),
7
7
  marko_renderer = require("marko/dist/runtime/components/renderer"),
8
8
  marko_dynamicTag = require("marko/dist/runtime/helpers/dynamic-tag"),
@@ -45,7 +45,7 @@ marko_template._ = marko_renderer(render, {
45
45
  }, marko_component);
46
46
 
47
47
  marko_template.meta = {
48
- id: "/@mindful-web/marko-web-identity-x$1.79.7/components/non-auth-identify.marko",
48
+ id: "/@mindful-web/marko-web-identity-x$1.79.8/components/non-auth-identify.marko",
49
49
  component: "./non-auth-identify.marko",
50
50
  tags: [
51
51
  "@mindful-web/marko-core/components/resolve.marko"
@@ -2,7 +2,7 @@
2
2
  "use strict";
3
3
 
4
4
  var marko_template = module.exports = require("marko/dist/html").t(__filename),
5
- marko_componentType = "/@mindful-web/marko-web-identity-x$1.79.7/components/subscribe.marko",
5
+ marko_componentType = "/@mindful-web/marko-web-identity-x$1.79.8/components/subscribe.marko",
6
6
  marko_component = require("./subscribe.marko"),
7
7
  marko_renderer = require("marko/dist/runtime/components/renderer"),
8
8
  module_objectPath_module = require("@mindful-web/object-path"),
@@ -73,7 +73,7 @@ marko_template._ = marko_renderer(render, {
73
73
  }, marko_component);
74
74
 
75
75
  marko_template.meta = {
76
- id: "/@mindful-web/marko-web-identity-x$1.79.7/components/subscribe.marko",
76
+ id: "/@mindful-web/marko-web-identity-x$1.79.8/components/subscribe.marko",
77
77
  component: "./subscribe.marko",
78
78
  tags: [
79
79
  "@mindful-web/marko-web/components/browser-component.marko",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mindful-web/marko-web-identity-x",
3
- "version": "1.79.7",
3
+ "version": "1.79.8",
4
4
  "description": "Marko Wrapper for IdentityX",
5
5
  "repository": "https://github.com/parameter1/mindful-web/tree/main/packages/marko-web-identity-x",
6
6
  "author": "Josh Worden <josh@parameter1.com>",
@@ -38,5 +38,5 @@
38
38
  "publishConfig": {
39
39
  "access": "public"
40
40
  },
41
- "gitHead": "cd09a3d725bbcb04b8a1ffae70164981bbe1305e"
41
+ "gitHead": "aafeaa99860062c3faa70b67bab0e69b49d13632"
42
42
  }
package/routes/google.js CHANGED
@@ -1,6 +1,6 @@
1
1
  const gql = require('graphql-tag');
2
2
  const { OAuth2Client } = require('google-auth-library');
3
- const { asyncRoute } = require('@mindful-web/utils');
3
+ const { asArray, asyncRoute } = require('@mindful-web/utils');
4
4
  const IMPERSONATE_APP_USER = require('../api/mutations/impersonate-app-user');
5
5
  const tokenCookie = require('../utils/token-cookie');
6
6
  const contextCookie = require('../utils/context-cookie');
@@ -208,6 +208,28 @@ module.exports = asyncRoute(async (req, res) => {
208
208
  }
209
209
  addAttrs({ idxRequiresUserInput: requiresUserInput });
210
210
 
211
+ // Tenant Omeda formatters write `autoSignups` into their `additionalEventData`
212
+ // in place — one entry per newsletter product they opt the user into — and
213
+ // that object is what goes out in the response body. It is the only channel by
214
+ // which the browser learns a subscription was created; without it no
215
+ // `identity-x-auto-signup` event reaches GTM/GA4 (the "subscribe" conversion)
216
+ // or p1events, and One Tap looks like it subscribes nobody even though the
217
+ // Omeda opt-in ran fine.
218
+ //
219
+ // Give each hook its OWN object and merge after, rather than sharing one
220
+ // reference, because tenants disagree about where the write lives AND several
221
+ // write in both places:
222
+ // onLoginLinkSentFormatter only ....... ab-media, allured, encore360, pmmi
223
+ // onAuthenticationSuccessFormatter only fusable, cox-matthews
224
+ // BOTH ................................ industrial, ironmarkets, watt
225
+ // Every tenant *assigns* `autoSignups` rather than appending, so a shared
226
+ // reference would let the second hook clobber the first — silently dropping a
227
+ // whole set of subscribe events for the three both-hook repos. In the
228
+ // email-link flow the two hooks fire on separate requests (login vs
229
+ // authenticate) and each reports its own set, so the union is what preserves
230
+ // existing behaviour here.
231
+ const linkSentEventData = { createdNewUser };
232
+
211
233
  // Fire onLoginLinkSent before onAuthenticationSuccess so that hooks wired to
212
234
  // it (e.g. Omeda rapid-identify and subscription sync) run for Google users
213
235
  // the same way they do for email-link users. This also populates
@@ -218,12 +240,22 @@ module.exports = asyncRoute(async (req, res) => {
218
240
  req,
219
241
  source: LOGIN_SOURCE,
220
242
  user,
243
+ additionalEventData: linkSentEventData,
221
244
  });
222
245
 
223
246
  // Re-fetch so onAuthenticationSuccess sees the externalIds written by
224
247
  // the onLoginLinkSent hook (Omeda rapid-identify).
225
248
  const userAfterLinkSent = await identityX.findUserById(appUser.id);
226
249
 
250
+ // `requiresUserInput` is exposed to this hook only. The onLoginLinkSent
251
+ // formatters read a truthy value as "skip the Omeda subscription lookup" and
252
+ // then opt the user into every configured deployment type without deduping
253
+ // against what they already hold — a phantom auto-signup, and an inflated GA4
254
+ // subscribe count, for an existing Omeda customer who merely lacks an idx
255
+ // required field. The email-link flow never exposes it there either:
256
+ // `routes/login.js` 400s with `requiresUserInput` before any hook runs.
257
+ const authEventData = { createdNewUser, requiresUserInput };
258
+
227
259
  setStep('hooks-authentication-success');
228
260
  await callHooksFor(identityX, 'onAuthenticationSuccess', {
229
261
  req,
@@ -231,9 +263,38 @@ module.exports = asyncRoute(async (req, res) => {
231
263
  user: userAfterLinkSent,
232
264
  authToken,
233
265
  loginSource: LOGIN_SOURCE,
234
- additionalEventData: { createdNewUser, requiresUserInput },
266
+ additionalEventData: authEventData,
235
267
  });
236
268
 
269
+ // Union of both hooks' auto-signups, deduped by product so a tenant that
270
+ // opts into the same newsletter from both hooks yields one subscribe event,
271
+ // not two.
272
+ const seenProducts = new Set();
273
+ const autoSignups = [
274
+ ...asArray(linkSentEventData.autoSignups),
275
+ ...asArray(authEventData.autoSignups),
276
+ ]
277
+ .filter((signup) => {
278
+ const key = signup.productId != null
279
+ ? `product:${signup.productId}`
280
+ : `entity:${signup.subscriptionEntity}`;
281
+ if (seenProducts.has(key)) return false;
282
+ seenProducts.add(key);
283
+ return true;
284
+ });
285
+
286
+ const additionalEventData = {
287
+ ...linkSentEventData,
288
+ ...authEventData,
289
+ ...(autoSignups.length && { autoSignups }),
290
+ };
291
+
292
+ // A new user with zero auto-signups is the shape this whole bug had: the
293
+ // sign-in succeeds, Omeda is updated, and GA4 records no conversion. Surface
294
+ // the count so a regression is a visible number on the transaction rather
295
+ // than something a stakeholder notices in a report weeks later.
296
+ addAttrs({ idxAutoSignupCount: autoSignups.length });
297
+
237
298
  setStep('complete');
238
299
  res.json({
239
300
  ok: true,
@@ -242,6 +303,7 @@ module.exports = asyncRoute(async (req, res) => {
242
303
  applicationId: config.getAppId(),
243
304
  user: userAfterLinkSent,
244
305
  loginSource: LOGIN_SOURCE,
306
+ additionalEventData,
245
307
  entity,
246
308
  });
247
309
  });