Task Breakdown — Centralized Web Session (CRM Frontend Integration of mekari-account-web-sdk Session)
Source RFC:
crm/centralized-web-session.md(status: RFC — not yet Ready for agent execution) Slicing mode: Vertical (1 task = 1 story end-to-end: UI + integration + tests; merging rule applied) Blocked-task handling: Full-picture — blocked tasks shown inline with unblock conditions; full-scope Skipped table at end. Target repo (verified):/Users/mekari/Documents/crm(Nuxt 2 SPA). Test runner: jest (yarn test=jest test -u; single file =yarn test-file <path>). Lint:yarn lint:js. Build:yarn build(nuxt build). Reconciled 2026-07-02 (latest review) against the real SDKmekari-account-web-sdkv0.3.0: the former Task 1 (mslifallback helper) and Task 7 (switch_userSSO-autologin flow) are deleted outright, not merely re-scoped — the SDK ownsmsliinternally and has noswitch_userstatus at all (seecrm-fe.mdADR-5/ADR-6 resolution notes). All remaining tasks are renumbered below; effort totals are recomputed in the Effort Summary.
Reconnaissance notes (grounding for every path below)
| Check | Result |
|---|---|
| Test runner | jest, not vitest. Run-one: yarn test-file <path>; all: yarn test. Lint: yarn lint:js; build: yarn build. (verified in package.json scripts) |
| Test layout | tests/ mirrors source dirs (tests/store/, tests/utils/helpers/, tests/middleware/, …). Specs are *.spec.js, not co-located, not TypeScript. |
tests/plugins/ | Does not exist yet — must be created for the plugin spec. Verified: no tests/plugins/ dir, no existing plugin test anywhere. |
mekari-account-web-sdk | Absent from package.json (only @mekari/pixel@^1.1.14). Net-new git dependency (npm install git+https://...#<version>, no npm-registry distribution) — install blocked on git access/pinned-ref confirmation, not registry availability (RFC Q6, reframed from the earlier "private npm registry" question). |
@datadog/browser-rum | Present @^4.34.0. datadogRum imported in plugins/datadog-rum.js; addAction/addError not yet used anywhere in the repo. |
| Cited source files | All verified present: schemes/crmAuthScheme.js, utils/helpers/auth.js, store/user.js, middleware/crm-user.js, plugins/datadog-rum.js, nuxt.config.js, assets/variables/endpoints.js, adapters/http/utils.js, plugins/auth.js, middleware/redirect-to-v3.js, plugins/mixpanel.js, utils/helpers/package-features.js. |
store/user.js anchors | userLogout:120, deleteSsoCookies:144, deleteUserLocalData:157, getCustomFeature:67, custom_features state:12, SET_CUSTOM_FEATURE:349. All confirmed. |
nuxt.config.js plugins | Block at line 58. Order: ~/plugins/auth (59) → ~/plugins/auto-token-refresh (60) → … ~/plugins/datadog-rum (66). New plugin registers after auto-token-refresh. |
| Refresh timer | utils/helpers/auth.js: setupAutoTokenRefresh:157, REFRESH_BEFORE_EXPIRY_MINUTES=10:13, clearAutoTokenRefresh:248. SDK must not fight this. The SDK has no refresh() method of its own — periodic re-validation is the interval constructor option, unrelated to this timer. |
currentUser (SSO id) | BE-verified present, FE wiring unconfirmed (Q2 narrowed). BE grounding (qontak.com): sso_id is serialized on the mobile v2.8 /users/me (app/views/api/mobile/v2dot8/users/me.json.jbuilder:77), the users.sso_id column + index exist (db/schema.rb:5176,5206), and MekariSso::MigrateSingleUserService backfills it — so PRD story CRM-S01 is effectively already done (verification, not build). Q2 therefore narrows from "does the field exist?" to "confirm the Nuxt $auth.user payload surfaces sso_id" for the plugin to read. Do not conflate the SDK's currentUser option with the unrelated Vuex state store/user.js current_user: [] (~:15). |
The former "Toast component" and "
_mekari_accountcookie" reconnaissance rows are removed: there is no toast surface (the fictionalswitch_userevent it was tied to does not exist), and CRM never reads_mekari_accountunder the corrected design — that cookie is exchanged only between the SDK's iframe and the Session Manager.
Effort Summary
Vertical mode — one row per story task. Days are man-days. QA = 20–25% of dev effort for user-facing behaviour, min 0.5; 0 for internal-only.
| Task | Story | Status | FE days | BE days | QA days | Total |
|---|---|---|---|---|---|---|
1. SDK plugin scaffold + centralized_session toggle gate | S1, S2 | ⚠️ Partially blocked | 1.5 | — | 0.5 | 2.0 |
2. SDK event handlers (logged_in/logged_out/server_down) | S3 | ⚠️ Partially blocked | 1.0 | — | 0.5 | 1.5 |
3. Logout → SSO sign_out redirect | S5 | ✅ Actionable | 1.0 | — | 0.5 | 1.5 |
| 4. Datadog RUM observability | S7 | ✅ Actionable | 0.5 | — | 0 | 0.5 |
5. Add mekari-account-web-sdk dependency | S1 (dep) | 🚫 Blocked | 0.5 | — | 0 | 0.5 |
6. current_company sync | S4 | 🚫 Blocked | 1.0 | 2.0 | 0.5 | 3.5 |
| Grand total | 5.5 | 2.0 | 2.0 | 9.5 | ||
| Actionable-now subtotal (Tasks 1–4) | 4.0 | — | 1.5 | 5.5 |
Confidence: medium (up from low). Three
[critical]open questions (Q2–Q4 — down from four; Q1'sswitch_usergap is resolved outright by the real SDK contract, not merely deprioritized) still gate the RFC's own "Ready for agent execution: no" marker. The biggest movers: (a) thecurrentUser(SSO id) source is unverified (Q2) — without it theSessionconstructor in Task 1 can't be filled, so Task 1 ships behind a stub; (b) the exactcentralized_sessionfeature code string is unverified (Q3); (c)current_companysync (Task 6) has no in-repo contract — and now confirmed the SDK itself has no company API either, so it cannot be resolved by further SDK reading. Tasks 3, 4 are genuinely actionable today; Tasks 1 and 2 are buildable as shells now with the SDK call/currentUserstubbed. The former Task 1 (mslifallback helper, 2.0 md) and Task 7 (switch_userSSO-autologin flow, 3.5 md) are removed from the grand total entirely — not stubbed, not deferred — because the SDK ownsmsliinternally and has noswitch_userstatus. Net change from the prior estimate: grand total drops from 17.0 md to 9.5 md (removed 2.0 + 3.5 = 5.5 md of dead work; the surviving plugin/handler tasks also shrank slightly — origin-validation and msli-grace logic that turned out to be unbuildable/unnecessary are gone — for a further ~2.0 md reduction across Tasks 1–2).
Task 1: [FE] SDK plugin scaffold + centralized_session toggle gate (S1, S2)
When the
centralized_sessiontoggle is on for the user's company, CRM loads themekari-account-web-sdkSessionon every authenticated page with the current user's SSO id; when the toggle is off (or unreadable), CRM behaves exactly as today.
Status: ⚠️ Partially blocked — the plugin shell, toggle gate, registration order, and the SDK subscription seam are all buildable now with the Session constructor and currentUser stubbed. Blocked pieces: the real new Session({...}) import awaits Task 5 (mekari-account-web-sdk not installed, Q6); the SSO-id field source is unverified (Q2); the exact toggle code string is unverified (Q3). Build the shell with these stubbed and a clearly-marked TODO.
Design reference: n/a — no Figma (RFC §1.4; this task has no visible UI surface — there is no toast anywhere in the corrected design).
What to build
A new Nuxt plugin that runs after ~/plugins/auth + ~/plugins/auto-token-refresh, reads the centralized_session toggle from store.state.user?.custom_features, no-ops when the toggle is off/absent/errored (fail-closed), and when on, constructs the SDK Session with { currentUser, interval: 5 * 60 * 1000 } and registers a single session.on("event", handleEvent) subscription seam (the handler body is filled in Task 2). There is no postMessage/event.origin guard for this plugin to add — the real SDK owns its own window message listener internally and checks only event.data.source, never event.origin, with no consumer hook to intercept it (crm-fe.md §3.1 — this is an upstream infosec finding against the SDK owner, not something CRM code can implement).
Implementation Plan
| Action | File | What changes |
|---|---|---|
| create | plugins/mekari-session.js | Default-export init fn (ctx); reads toggle via ctx.store.state.user?.custom_features?.some((f) => f.code === 'centralized_session' && f.enabled); stub getCurrentUserSsoId(ctx) (TODO Q2); guarded Session construction with { currentUser, interval: 5 * 60 * 1000 } (stubbed import until Task 5); a single session.on("event", handleEvent) subscription seam (handleEvent implemented in Task 2) |
| extend | nuxt.config.js | Add '~/plugins/mekari-session' to plugins[] (line 58 block) after '~/plugins/auto-token-refresh' |
| create | tests/plugins/mekari-session.spec.js | New file (and tests/plugins/ dir — does not exist yet); tests toggle-on/off/error gating, Session constructed once with currentUser + interval, no Session when off |
File path rule:
plugins/mekari-session.jsmatches the flatplugins/convention (siblings:datadog-rum.js,mixpanel.js,auth.js).tests/plugins/must be created — it does not exist in the repo today (verified).'~/plugins/mekari-session'registration string matches the existing'~/plugins/...'entries innuxt.config.js:58.
Implementation steps
- Explore the codebase area — Open
plugins/datadog-rum.js(verified) for the conditional-gate + default-export-init-fn pattern (if ($config.ddEnabled === 'true'),plugins/datadog-rum.js:4), andplugins/auth.jsfor$auth/storeaccess via the Nuxt ctx. Openmiddleware/redirect-to-v3.js:48–49for the exactstore.state.user?.custom_features(:48) +features.some((f) => f.code === … && f.enabled)(:49) read shape. Confirmnuxt.config.js:58–71plugin order. Note (from the RFC's SDK grounding) thatSessionis a singleton — a secondnew Session()call returns the first instance and ignores new options; re-init requiressession.destroy(). - Write failing tests (red) — Create
tests/plugins/thentests/plugins/mekari-session.spec.js. Mock the ctx (store.state.user.custom_features,$auth.user) and inject a mockedSessionfactory. Assert: toggle off/absent →Sessionconstructor (mocked) never called; toggle-read throws → noSession(fail-closed); toggle on →Sessionconstructed once with the stubbed sso id andinterval: 5 * 60 * 1000;session.on("event", …)called exactly once. Because the real SDK'sSessionis a singleton, reset/re-create the mocked factory between test cases rather than relying on a shared instance. Runyarn test-file tests/plugins/mekari-session.spec.js, confirm failure. - Scaffold — Create
plugins/mekari-session.jswith the default-export init fn, agetToggle(ctx)helper using.some(...), and agetCurrentUserSsoId(ctx)stub returningctx.$auth.user?.<field>with a// TODO Q2: confirm fieldcomment. - Wire state — Read the toggle from
store.state.user?.custom_features(same shape asredirect-to-v3.js:48–49). Guard the whole body intry/catch→ fail-closed. - Implement behavior — When toggle on:
// import { Session } from 'mekari-account-web-sdk'(leave import commented + a mockable factory seam until Task 5), constructnew Session({ currentUser: ssoId, interval: 5 * 60 * 1000 }), thensession.on("event", handleEvent)wherehandleEventis a placeholder no-op wired up fully in Task 2. Do not add awindow.addEventListener('message', …)or anyevent.origincheck — there is no such hook in the real SDK. - Go green —
yarn test-file tests/plugins/mekari-session.spec.jsuntil pass. - Quality gate —
yarn lint:js && yarn build(build will pass only once the SDK import is stubbed/commented — uncomment in Task 5).
Acceptance criteria
- Toggle absent/false →
Sessionis not constructed; no console noise. -
feature_enabledread error → fail-closed (noSession), no regression to existing auth. - Toggle on →
Sessionconstructed exactly once withcurrentUser= the resolved sso id andinterval: 5 * 60 * 1000. - Plugin registers exactly one
session.on("event", …)subscription; no per-status subscriptions. - Plugin does not add its own
windowmessagelistener and does not attemptevent.originvalidation — that capability does not exist in the real SDK and is tracked as an upstream finding, not a CRM task (RFC §3.1, Q10). - Plugin is registered in
nuxt.config.jsafterauto-token-refresh; does not call intosetupAutoTokenRefresh. - (pending Q2)
currentUserresolves from a confirmed/users/mefield — currently stubbed. - (pending Q3) the
centralized_sessioncode string matches the realfeature_enabledvalue.
Test strategy
Plugin unit test with a hand-rolled ctx mock and a mocked Session factory. Key mock: the mekari-account-web-sdk Session (jest jest.mock once the dep lands, or an injected factory until then) — because the real constructor is a singleton, mock it as a fresh factory per test rather than a shared instance. Key assertions: gating matrix (off/error/on) and exactly-once subscription. Stub getCurrentUserSsoId to a fixture value so the constructor arg is assertable without Q2 resolved.
Effort estimate
| Discipline | Days |
|---|---|
| Frontend | 1.5 |
| Backend | — |
| QA | 0.5 |
| Total | 2.0 |
Assumptions: reuses the
datadog-rumplugin pattern and the correctedredirect-to-v3.js:48–49feature-read shape; SDK import stubbed behind a factory seam so the shell builds before Task 5;currentUserand toggle code stubbed pending Q2/Q3.tests/plugins/is net-new. Effort is lower than the original estimate (2.5 md) because thepostMessage/origin-validation guard that was previously scoped here does not exist to build.
Run to verify
yarn test-file tests/plugins/mekari-session.spec.js && yarn lint:js
Depends on
- None to start — this is the recommended first FE task (the former msli-helper task no longer exists).
- [External:
mekari-account-web-sdkpackage — Task 5 (Q6)] for the real import. - [External:
/users/meSSO-id field — Q2 (pending)] and [centralized_sessioncode string — Q3 (pending)].
Task 2: [FE] SDK event handlers — logged_in / logged_out / server_down (S3)
CRM reacts correctly to every SSO session signal: no-ops on
logged_in, signs out (and redirects to SSO) onlogged_out— which also covers an SSO account switch, since the real SDK cannot distinguish the two — and takes no destructive action onserver_down(fail-open).
Status: ⚠️ Partially blocked — the handler is buildable now against mocked SDK events. Blocked: the real SDK event subscription depends on Task 5.
Design reference: n/a — no Figma; no UI surface exists in the corrected design (the earlier "user has changed" toast was tied to a switch_user event that does not exist in the real SDK — see crm-fe.md ADR-5 resolution note; RFC §5 Q1/Q5 are resolved as moot).
What to build
The single session.on("event", handleEvent) callback inside plugins/mekari-session.js, switching on data.status: logged_in → no-op (RUM action emitted in Task 4, no navigation, no store write); logged_out → dispatch user/userLogout then redirect (the redirect itself is implemented in Task 3) — this branch also fires for a genuine SSO account switch, since the real SDK exposes no way to tell the two apart; server_down → fail-open (PRD constraint 6.10): no dispatch, no redirect, no destructive action of any kind. There is no msli handling anywhere — the SDK owns that key internally and CRM must not touch it.
Implementation Plan
| Action | File | What changes |
|---|---|---|
| extend | plugins/mekari-session.js | Implement handleEvent(data) (the seam created in Task 1) with a switch (data.status) over exactly logged_in / logged_out / server_down |
| extend | tests/plugins/mekari-session.spec.js | Add per-status tests (3 statuses) |
File path rule:
store/user.js:userLogout(line 120) verified — reused as-is for thelogged_outdispatch. No edit tostore/user.js:deleteUserLocalDatais needed (the earlier plan to clear anmslikey there is removed — the SDK ownsmsli, CRM never writes it).
Implementation steps
- Explore the codebase area — Re-open
plugins/mekari-session.js(from Task 1) andstore/user.js: readuserLogout(line 120) to see how sign-out is dispatched. No toast/notification research is needed — there is no toast surface in the corrected design. - Write failing tests (red) — Extend
tests/plugins/mekari-session.spec.jswith one test per status:logged_in→ no store dispatch, no navigation;logged_out→user/userLogoutdispatched;server_down→ no dispatch, no redirect, no side effect at all. Run, confirm red. - Scaffold — Implement
handleEvent(data)with aswitch (data.status)over the three literal values. - Wire state — Dispatch
store.dispatch('user/userLogout')for thelogged_outbranch only. - Implement behavior — Fill each branch per §2.4 Inbound table and the §2.6 state machine. The
server_downbranch is intentionally empty of side effects (fail-open, PRD 6.10) — do not add any msli/cookie fallback check. - Go green —
yarn test-file tests/plugins/mekari-session.spec.jsuntil pass. - Quality gate —
yarn lint:js && yarn build.
Acceptance criteria
-
logged_in→ no store dispatch, no navigation. -
logged_out→user/userLogoutdispatched (redirect added in Task 3). This branch also covers an SSO account switch — the SDK delivers it aslogged_out, with no separate handling possible or needed. -
server_down→ no dispatch, no redirect, no destructive action of any kind (fail-open). - No
msli(or any localStorage/cookie) read/write anywhere in the plugin.
Test strategy
Drive the mocked Session to emit each status and assert the resulting store dispatch (or explicit absence of one). Key mock: a fake session emitter + a spy on store.dispatch. Key assertion: server_down produces zero dispatches/side effects.
Effort estimate
| Discipline | Days |
|---|---|
| Frontend | 1.0 |
| Backend | — |
| QA | 0.5 |
| Total | 1.5 |
Assumptions: reuses existing
userLogout; no toast, no msli, no separateswitch_userbranch. Effort is lower than the original estimate (2.5 md) because the handler is now 3 simple branches instead of 4 events plus msli-cleanup wiring.
Run to verify
yarn test-file tests/plugins/mekari-session.spec.js && yarn lint:js
Depends on
- [Task 1] (plugin shell + subscription seam), [Task 3] (the redirect that the
logged_outbranch reuses). - [External:
mekari-account-web-sdk— Task 5] for the real subscription.
Task 3: [FE] Wire product logout to account.mekari.com/sign_out (S5)
When a CRM user logs out (in-app or via an SDK sign-out event), the SSO session is also destroyed — the browser is redirected to
account.mekari.com/sign_outafter CRM's local cleanup completes.
Status: ✅ Actionable — extends an existing, verified action; no SDK or currentUser needed. The only caveat (Q7) is a regression-check, not a blocker.
Design reference: n/a — navigation only, no Figma.
What to build
Extend the success path of store/user.js:userLogout so that, after the sign_out POST resolves and local cleanup (deleteUserLocalData + deleteSsoCookies + $auth.reset()) runs, the browser is redirected via window.location.href = 'https://account.mekari.com/sign_out'. Guard the redirect behind the centralized_session toggle so legacy logout is unchanged when the feature is off. Note: this URL is consumer-driven and unverified externally — the SDK has no logout() method, so its correctness is entirely CRM's/A&L's responsibility, not something the SDK contract guarantees.
Implementation Plan
| Action | File | What changes |
|---|---|---|
| extend | store/user.js (userLogout:120, success path after :133–134) | After local cleanup, if centralized_session on, window.location.href = 'https://account.mekari.com/sign_out' |
| extend/create | tests/store/user.spec.js (verify filename) | Assert window.location.href set to the SSO sign_out URL after sign_out resolves; assert no redirect when toggle off |
File path rule:
store/user.js:userLogoutverified at line 120, cleanup at 133–134. The external-redirect-via-window.location.hrefpattern is verified only atmiddleware/redirect-to-v3.js:65(an earlier draft also citedmiddleware/version-switcher.js:13, which is not a verified location for this idiom and has been dropped). Test pathtests/store/user.spec.js[verify exact filename in tests/store/].
Implementation steps
- Explore the codebase area — Open
store/user.jsand readuserLogout(120) end-to-end: it POSTs${USER_URL}/sign_outthen dispatchesdeleteUserLocalData+deleteSsoCookies. Openmiddleware/redirect-to-v3.js:65to copy thewindow.location.hrefcross-origin redirect idiom. Resolve Q7 first: grep all callers ofuserLogout(account-switch, embed layouts) to confirm none expect to stay in-app. - Write failing tests (red) — In
tests/store/user.spec.js: mock thesign_outrequest to resolve, setcentralized_sessionon, assertwindow.location.href === 'https://account.mekari.com/sign_out'afteruserLogout; toggle off → assert no redirect. Mockwindow.location. Run, confirm red. - Scaffold — Add a redirect line guarded by the toggle read in the
userLogoutsuccess continuation. - Wire state — Read the toggle from
state.custom_features(same.some((f) => f.code === … && f.enabled)shape as Task 1) inside the action. - Implement behavior — Set
window.location.hrefonly after cleanup; keep the existing.catchbehaviour (proceed to cleanup even if POST errors) intact. - Go green —
yarn test-file tests/store/user.spec.js. - Quality gate —
yarn lint:js && yarn build.
Acceptance criteria
- After
sign_outPOST resolves and cleanup runs,window.location.hrefis set tohttps://account.mekari.com/sign_outwhencentralized_sessionis on. - When the toggle is off,
userLogoutbehaves exactly as pre-RFC (no external redirect). - Existing
.catchpath (POST error → still clean up) is preserved. - (Q7) No existing in-app
userLogoutcaller is broken by the redirect (verified by caller audit, behind the toggle).
Test strategy
Store-action unit test with mocked request and mocked window.location. Key mock: the sign_out HTTP call (resolve + reject cases). Key assertion: redirect URL set exactly once, only when toggle on, only after cleanup.
Effort estimate
| Discipline | Days |
|---|---|
| Frontend | 1.0 |
| Backend | — |
| QA | 0.5 |
| Total | 1.5 |
Assumptions: reuses existing
userLogout+ verifiedwindow.location.hrefredirect idiom; redirect gated by the toggle so it's a no-regression change; Q7 is a caller audit, not new code.
Run to verify
yarn test-file tests/store/user.spec.js && yarn lint:js
Depends on
- None to start (independent of the SDK). Task 2's
logged_outbranch reuses this redirect, so land it before/with Task 2.
Task 4: [FE] Datadog RUM observability for session events (S7)
Operators can see session-event volume and
server_downerrors in Datadog for the piloted company, so the rollout can be monitored and rolled back on signal.
Status: ✅ Actionable — reuses the existing @datadog/browser-rum dependency; internal-only instrumentation.
Design reference: n/a — observability config, no UI.
What to build
Emit a RUM custom action mekari_session.event with { status } from the handler in plugins/mekari-session.js, and a RUM error on server_down. No console.log. Not an error on postMessage origin mismatch — CRM cannot observe this; the SDK owns the message listener internally and never exposes mismatches to consumers (RFC §3.1/§3.2).
Implementation Plan
| Action | File | What changes |
|---|---|---|
| extend | plugins/mekari-session.js | Import datadogRum from @datadog/browser-rum; call datadogRum.addAction('mekari_session.event', { status }) in handleEvent, and datadogRum.addError(...) in the server_down branch only |
| extend | tests/plugins/mekari-session.spec.js | Assert addAction/addError called with the expected payload (mock datadogRum) |
File path rule:
plugins/datadog-rum.jsverified — itimport { datadogRum } from '@datadog/browser-rum'(line 1) and gates init withif ($config.ddEnabled === 'true')(line 4).@datadog/browser-rum@^4.34.0confirmed inpackage.json. Note:addAction/addErrorare not yet used anywhere in the repo — confirm the v4 API surface (datadogRum.addAction(name, context),datadogRum.addError(error, context)) when wiring.
Implementation steps
- Explore the codebase area — Open
plugins/datadog-rum.js(verified) for theimport { datadogRum }form and theservice: 'qontak-crm-frontend'convention (line 9). Confirm RUM is initialised before the session plugin runs (datadog-rum atnuxt.config.js:66, session plugin registered after — verify ordering). - Write failing tests (red) — Extend
tests/plugins/mekari-session.spec.js: mockdatadogRum, assertaddAction('mekari_session.event', { status })fires for every handled status andaddErrorfires only onserver_down. Run, confirm red. - Scaffold — Add
import { datadogRum } from '@datadog/browser-rum'to the plugin. - Wire state — Pass
{ status: data.status }fromhandleEvent. - Implement behavior — Add
addActionto every branch ofhandleEvent;addErrorin theserver_downbranch only. - Go green —
yarn test-file tests/plugins/mekari-session.spec.js. - Quality gate —
yarn lint:js && yarn build.
Acceptance criteria
- Each handled SDK status emits
datadogRum.addAction('mekari_session.event', { status }). -
server_downadditionally emitsdatadogRum.addError(...). - No
postMessage-origin-mismatch error is attempted (not observable by CRM). - No new
console.logintroduced.
Test strategy
Mock the @datadog/browser-rum module; spy on addAction/addError. Key assertion: action name + payload shape per status; error emitted only on server_down.
Effort estimate
| Discipline | Days |
|---|---|
| Frontend | 0.5 |
| Backend | — |
| QA | 0 |
| Total | 0.5 |
Assumptions: reuses existing
@datadog/browser-rumdep + RUM init; internal-only (QA 0); thin layer over Task 2's handler.
Run to verify
yarn test-file tests/plugins/mekari-session.spec.js && yarn lint:js && yarn build
Depends on
- [Task 1] (plugin shell) and [Task 2] (the handler to instrument).
Task 5: [BE] Add mekari-account-web-sdk dependency 🚫 (S1 dependency)
The
mekari-account-web-sdkSessionpackage is installed at a pinned git ref so the plugin (Task 1/2) can import it for real and the build resolves it.
Status: 🚫 Blocked — unblock condition: confirm CI/CD and local-dev git access/credentials to bitbucket.org/mid-kelola-indonesia/mekari-account-web-sdk and pin an exact version ref (RFC §5 Q6 [important]; reframed from the earlier, incorrect "is it published to a private npm registry" — the real SDK has no npm-registry distribution at all). The package is absent from package.json today (verified — only @mekari/pixel). Until then, Tasks 1/2/4 run against a stubbed/commented import.
Design reference: n/a — dependency change, no UI.
What to build
Run npm install git+https://<user>:<pass>@bitbucket.org/mid-kelola-indonesia/mekari-account-web-sdk#<version> (pinned ref), commit package.json + yarn.lock, then uncomment the real import { Session } from 'mekari-account-web-sdk' in plugins/mekari-session.js (the factory seam left in Task 1).
Implementation Plan
| Action | File | What changes |
|---|---|---|
| extend | package.json (+ yarn.lock) | Add mekari-account-web-sdk as a git dependency pinned to an exact ref |
| extend | plugins/mekari-session.js | Uncomment/replace the stubbed import with the real mekari-account-web-sdk Session |
File path rule:
package.jsonverified (nomekari-account-web-sdkpresent).plugins/mekari-session.jsis created in Task 1.
Implementation steps
- Verify git access (Q6) — Confirm CI/CD and local-dev credentials can resolve
git+https://...@bitbucket.org/mid-kelola-indonesia/mekari-account-web-sdk. If access is denied, the task stays blocked — escalate to A&L. npm install git+https://<user>:<pass>@bitbucket.org/mid-kelola-indonesia/mekari-account-web-sdk#<version>(pin an exact tag/commit, not a floating branch).- Replace the Task-1 import seam with
import { Session } from 'mekari-account-web-sdk'. yarn install --frozen-lockfilepasses;yarn buildresolves the import.
Acceptance criteria
-
mekari-account-web-sdkpresent inpackage.jsondeps at a pinned git ref. -
yarn install --frozen-lockfilepasses. -
yarn buildresolves themekari-account-web-sdkimport.
Test strategy
No new unit test — covered by Tasks 1/2/4 once the real module replaces the mock; the gate is yarn build resolving the import.
Effort estimate
| Discipline | Days |
|---|---|
| Frontend | 0.5 |
| Backend | — |
| QA | 0 |
| Total | 0.5 |
Assumptions: labelled
[BE]/infra-ish (dependency + git access), trivial once Q6 confirms access; no code logic.
Run to verify
yarn install --frozen-lockfile && yarn build
Depends on
- [External:
mekari-account-web-sdkgit access + pinned ref — Q6 (pending)]. Unblocks the real imports in Tasks 1, 2, 4.
Task 6: [FE+BE] current_company sync after session 🚫 (S4)
After a session is (re-)established, CRM reflects the user's current SSO company so the product context matches SSO.
Status: 🚫 Blocked — unblock condition: resolve RFC §5 Q4 [critical] + ADR-7: no CRM current_company endpoint or field exists (only team endpoints: store/user.js:244,261,274), and whether CRM even has a per-company-switch equivalent is unconfirmed. Needs a BE RFC/contract before any FE work. Deferred entirely in this RFC. Confirmed during SDK grounding: the real SDK's public API (constructor, destroy(), on(), off()) has no company surface either, so this cannot be resolved by further SDK investigation — it is a pure CRM/BE dependency. BE-repo grounding (qontak.com, Rails 5.2): CRM has no per-session "current company" — company is derived structurally from the user's team (app/models/user.rb:236,841) and session[:company] is a display string only (users_controller.rb:1802,1869); no code calls SSO for a current/active company. What already exists and shrinks (but does not eliminate) the 2.0 BE: the SSO token/HTTP scaffolding — MekariSso::AuthService, RevokeTokenService, UserService#get_me, GetCompanyService (SSO_API_URL/v1/owned_companies/), and the sso_id linkage — so this is "build a current-company concept + fetch-from-SSO + session wiring on top of existing SSO plumbing," not integrate-SSO-from-zero.
Design reference: n/a — deferred.
What to build (once unblocked)
FE consumption of a (to-be-defined) CRM current_company endpoint/field to set company context after logged_in/logged_out-driven re-login. The endpoint itself is BE work (separate RFC).
Implementation Plan
| Action | File | What changes |
|---|---|---|
| create | BE current_company endpoint | [unverified — covered in BE RFC, link REQUIRED] |
| extend | store/user.js | [unverified] set company context from the new endpoint |
File path rule: all paths
[unverified — check repo / BE RFC pending]. CRM uses teams, not SSOcurrent_company— no contract to anchor against today.
Acceptance criteria
- (pending Q4 + BE RFC) CRM company context matches the SSO current company after session establishment.
Effort estimate
| Discipline | Days |
|---|---|
| Frontend | 1.0 |
| Backend | 2.0 |
| QA | 0.5 |
| Total | 3.5 |
Assumptions: coarse — BE endpoint with business logic (2–3 BE days range, taken at 2.0) plus FE wiring; no in-repo contract (Q4), and confirmed no SDK contract either. Estimate is a placeholder for deferred scope.
Depends on
- [External: CRM
current_companyBE contract / BE RFC — Q4[critical](pending)], [Task 1], [Task 2].
CRM BE prerequisites (PRD Bundle B) — grounded against qontak.com
These are BE-side stories tracked in the PRD, not FE tasks in this breakdown, but latest review grounding against the real Rails 5.2 backend changes their status — recorded here so the FE plan reflects reality:
| PRD story | Grounded status | Evidence (qontak.com) |
|---|---|---|
CRM-S01 — expose sso_id on /users/me | Already done — verification, not build | app/views/api/mobile/v2dot8/users/me.json.jbuilder:77 serializes sso_id; column + index at db/schema.rb:5176,5206. Narrows Q2 to confirming $auth.user surfaces it FE-side. |
CRM-S02 — backfill users.sso_id | Service exists | app/services/mekari_sso/migrate_single_user_service.rb; batch via app/workers/mekari_sso/migrate_user_worker.rb:10. ⚠️ signature drift — the worker calls new(access_token, user_id, caller_method) (3 args) but the service initialize(user_id, caller_method) takes 2; reconcile before a production backfill run. |
CRM-S03 — SSO logout webhook destroys the sessions row | Real gap, but reusable scaffolding exists | Route/controller/service/flag all present (config/routes.rb:283, webhooks/mekari/sso_controller.rb, sync_from_sso_service.rb, flag sso_webhook_receiver), but SyncFromSsoService only updates profile fields — it does not delete the sessions row. A sessions_invalidated_at column was added (db/migrate/20260626000001_*) but is referenced nowhere, and a destroy_web_session helper that deletes a sessions row by id already exists (api/mobile/v2dot8/users_controller.rb:231-238) and can be reused. So the story is "wire the existing helper into the webhook," not build from scratch. |
CRM-S05 — Devise timeoutable (4h idle) | Confirmed absent — real work | app/models/user.rb Devise module list has no :timeoutable; no timeout_in in config/initializers/devise.rb. |
CRM-S04 — Kong centralized_web_session metadata | Model missing | No KongOauth2Applications model/table and no centralized_web_session string in the repo; UNIFIED_SSO_CLIENT_ID env + a Kong-served mekari_kong/v2.8/users#me endpoint do exist. |
Ordering rationale
- Start with Task 1 (SDK plugin scaffold + toggle gate). It is the decision engine for everything downstream and is buildable today as a shell (toggle gating, registration order, subscription seam) with only the SDK import and
currentUserstubbed. The former "start with the msli helper" recommendation no longer applies — that task is deleted. - Task 2 (event handlers) follows Task 1 — same file, and it fills the subscription seam Task 1 creates. Both are buildable now with the SDK import and
currentUserstubbed; together they are the critical path and the bulk of the remaining FE effort (2.5 FE days, down from 4.5 in the pre-correction estimate). - Task 3 (logout → SSO redirect) is independent and should land alongside Task 2, because Task 2's
logged_outbranch reuses that redirect. It's safely toggle-gated, so it can merge before the SDK is even installed. - Task 4 (RUM) is a thin final layer over Tasks 1/2 — do it last among the actionable set; it's needed for the rollout's go/no-go signal but blocks nothing.
- Critical path to "done" runs through the external blockers, not CRM code. The three remaining
[critical]OQs (Q2, Q3, Q4 — down from four) are the real gate (the RFC's own §7 marker is "no"). Push A&L/Infosec on: Q6 (confirm git access + pin amekari-account-web-sdkref → unblocks the real import in Task 5, turning Tasks 1/2/4 from stubbed to complete), Q2 (SSO-id field in/users/me→ unblocks theSessionconstructor arg), Q3 (exactcentralized_sessioncode string), and Q4 (thecurrent_companycontract → unblocks Task 6). Also confirm Q8 (CSP delivery mechanism, Infosec) before pilot, and separately escalate Q10 (the SDK'sevent.originvalidation gap) to Infosec/A&L — it is an upstream finding, not a task in this breakdown. Net: ~5.5 man-days of CRM FE work (Tasks 1–4) can proceed today behind the toggle, but cannot be fully wired/shipped until Q6 + Q2 + Q3 land.
Skipped stories
Full-scope mode — every 🚫 Blocked task with its unblock condition (detail is in each task body above):
| Story / Task | Reason / unblock condition |
|---|---|
Task 5 — Add mekari-account-web-sdk dep (S1 dep) | Blocked on Q6 [important] — confirm CI/CD and local-dev git access/credentials to bitbucket.org/mid-kelola-indonesia/mekari-account-web-sdk and pin an exact ref. Package absent from package.json today. |
Task 6 — current_company sync (S4) | Blocked on Q4 [critical] + ADR-7 — no CRM current_company endpoint/field exists (CRM uses teams), and the SDK itself has no company API; needs a BE RFC/contract first. Deferred. |
Removed, not skipped: the former Task 1 (
mslifallback helper, 2.0 md) and Task 7 (switch_userproper SSO-autologin flow, 3.5 md) do not appear here because they are not blocked-but-eventually-buildable — they are deleted outright. The SDK ownsmsliinternally (consumers must not touch it) and has noswitch_userstatus at all (mekari-account-web-sdkv0.3.0,session.ts); seecrm-fe.mdADR-6 and ADR-5 resolution notes.Additional non-blocking unknowns that constrain the actionable tasks (not skipped, but flagged in-task): Q2 (SSO-id source — stubs Task 1's constructor arg), Q3 (exact
centralized_sessioncode — stubs Task 1's gate), Q7 (logout-caller regression audit — Task 3), Q8 (CSP delivery, Infosec — pilot gate). Q10 (SDKevent.originvalidation gap) is a cross-RFC infosec finding, not a task-blocking unknown for this breakdown.