Skip to main content

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 SDK mekari-account-web-sdk v0.3.0: the former Task 1 (msli fallback helper) and Task 7 (switch_user SSO-autologin flow) are deleted outright, not merely re-scoped — the SDK owns msli internally and has no switch_user status at all (see crm-fe.md ADR-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)

CheckResult
Test runnerjest, not vitest. Run-one: yarn test-file <path>; all: yarn test. Lint: yarn lint:js; build: yarn build. (verified in package.json scripts)
Test layouttests/ 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-sdkAbsent 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-rumPresent @^4.34.0. datadogRum imported in plugins/datadog-rum.js; addAction/addError not yet used anywhere in the repo.
Cited source filesAll 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 anchorsuserLogout:120, deleteSsoCookies:144, deleteUserLocalData:157, getCustomFeature:67, custom_features state:12, SET_CUSTOM_FEATURE:349. All confirmed.
nuxt.config.js pluginsBlock at line 58. Order: ~/plugins/auth (59) → ~/plugins/auto-token-refresh (60) → … ~/plugins/datadog-rum (66). New plugin registers after auto-token-refresh.
Refresh timerutils/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_account cookie" reconnaissance rows are removed: there is no toast surface (the fictional switch_user event it was tied to does not exist), and CRM never reads _mekari_account under 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.

TaskStoryStatusFE daysBE daysQA daysTotal
1. SDK plugin scaffold + centralized_session toggle gateS1, S2⚠️ Partially blocked1.50.52.0
2. SDK event handlers (logged_in/logged_out/server_down)S3⚠️ Partially blocked1.00.51.5
3. Logout → SSO sign_out redirectS5✅ Actionable1.00.51.5
4. Datadog RUM observabilityS7✅ Actionable0.500.5
5. Add mekari-account-web-sdk dependencyS1 (dep)🚫 Blocked0.500.5
6. current_company syncS4🚫 Blocked1.02.00.53.5
Grand total5.52.02.09.5
Actionable-now subtotal (Tasks 1–4)4.01.55.5

Confidence: medium (up from low). Three [critical] open questions (Q2–Q4 — down from four; Q1's switch_user gap 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) the currentUser (SSO id) source is unverified (Q2) — without it the Session constructor in Task 1 can't be filled, so Task 1 ships behind a stub; (b) the exact centralized_session feature code string is unverified (Q3); (c) current_company sync (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/currentUser stubbed. The former Task 1 (msli fallback helper, 2.0 md) and Task 7 (switch_user SSO-autologin flow, 3.5 md) are removed from the grand total entirely — not stubbed, not deferred — because the SDK owns msli internally and has no switch_user status. 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_session toggle is on for the user's company, CRM loads the mekari-account-web-sdk Session on 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

ActionFileWhat changes
createplugins/mekari-session.jsDefault-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)
extendnuxt.config.jsAdd '~/plugins/mekari-session' to plugins[] (line 58 block) after '~/plugins/auto-token-refresh'
createtests/plugins/mekari-session.spec.jsNew 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.js matches the flat plugins/ 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 in nuxt.config.js:58.

Implementation steps

  1. 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), and plugins/auth.js for $auth/store access via the Nuxt ctx. Open middleware/redirect-to-v3.js:48–49 for the exact store.state.user?.custom_features (:48) + features.some((f) => f.code === … && f.enabled) (:49) read shape. Confirm nuxt.config.js:58–71 plugin order. Note (from the RFC's SDK grounding) that Session is a singleton — a second new Session() call returns the first instance and ignores new options; re-init requires session.destroy().
  2. Write failing tests (red) — Create tests/plugins/ then tests/plugins/mekari-session.spec.js. Mock the ctx (store.state.user.custom_features, $auth.user) and inject a mocked Session factory. Assert: toggle off/absent → Session constructor (mocked) never called; toggle-read throws → no Session (fail-closed); toggle on → Session constructed once with the stubbed sso id and interval: 5 * 60 * 1000; session.on("event", …) called exactly once. Because the real SDK's Session is a singleton, reset/re-create the mocked factory between test cases rather than relying on a shared instance. Run yarn test-file tests/plugins/mekari-session.spec.js, confirm failure.
  3. Scaffold — Create plugins/mekari-session.js with the default-export init fn, a getToggle(ctx) helper using .some(...), and a getCurrentUserSsoId(ctx) stub returning ctx.$auth.user?.<field> with a // TODO Q2: confirm field comment.
  4. Wire state — Read the toggle from store.state.user?.custom_features (same shape as redirect-to-v3.js:48–49). Guard the whole body in try/catch → fail-closed.
  5. Implement behavior — When toggle on: // import { Session } from 'mekari-account-web-sdk' (leave import commented + a mockable factory seam until Task 5), construct new Session({ currentUser: ssoId, interval: 5 * 60 * 1000 }), then session.on("event", handleEvent) where handleEvent is a placeholder no-op wired up fully in Task 2. Do not add a window.addEventListener('message', …) or any event.origin check — there is no such hook in the real SDK.
  6. Go greenyarn test-file tests/plugins/mekari-session.spec.js until pass.
  7. Quality gateyarn lint:js && yarn build (build will pass only once the SDK import is stubbed/commented — uncomment in Task 5).

Acceptance criteria

  • Toggle absent/false → Session is not constructed; no console noise.
  • feature_enabled read error → fail-closed (no Session), no regression to existing auth.
  • Toggle on → Session constructed exactly once with currentUser = the resolved sso id and interval: 5 * 60 * 1000.
  • Plugin registers exactly one session.on("event", …) subscription; no per-status subscriptions.
  • Plugin does not add its own window message listener and does not attempt event.origin validation — 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.js after auto-token-refresh; does not call into setupAutoTokenRefresh.
  • (pending Q2) currentUser resolves from a confirmed /users/me field — currently stubbed.
  • (pending Q3) the centralized_session code string matches the real feature_enabled value.

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

DisciplineDays
Frontend1.5
Backend
QA0.5
Total2.0

Assumptions: reuses the datadog-rum plugin pattern and the corrected redirect-to-v3.js:48–49 feature-read shape; SDK import stubbed behind a factory seam so the shell builds before Task 5; currentUser and toggle code stubbed pending Q2/Q3. tests/plugins/ is net-new. Effort is lower than the original estimate (2.5 md) because the postMessage/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-sdk package — Task 5 (Q6)] for the real import.
  • [External: /users/me SSO-id field — Q2 (pending)] and [centralized_session code 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) on logged_out — which also covers an SSO account switch, since the real SDK cannot distinguish the two — and takes no destructive action on server_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_downfail-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

ActionFileWhat changes
extendplugins/mekari-session.jsImplement handleEvent(data) (the seam created in Task 1) with a switch (data.status) over exactly logged_in / logged_out / server_down
extendtests/plugins/mekari-session.spec.jsAdd per-status tests (3 statuses)

File path rule: store/user.js:userLogout (line 120) verified — reused as-is for the logged_out dispatch. No edit to store/user.js:deleteUserLocalData is needed (the earlier plan to clear an msli key there is removed — the SDK owns msli, CRM never writes it).

Implementation steps

  1. Explore the codebase area — Re-open plugins/mekari-session.js (from Task 1) and store/user.js: read userLogout (line 120) to see how sign-out is dispatched. No toast/notification research is needed — there is no toast surface in the corrected design.
  2. Write failing tests (red) — Extend tests/plugins/mekari-session.spec.js with one test per status: logged_in → no store dispatch, no navigation; logged_outuser/userLogout dispatched; server_down → no dispatch, no redirect, no side effect at all. Run, confirm red.
  3. Scaffold — Implement handleEvent(data) with a switch (data.status) over the three literal values.
  4. Wire state — Dispatch store.dispatch('user/userLogout') for the logged_out branch only.
  5. Implement behavior — Fill each branch per §2.4 Inbound table and the §2.6 state machine. The server_down branch is intentionally empty of side effects (fail-open, PRD 6.10) — do not add any msli/cookie fallback check.
  6. Go greenyarn test-file tests/plugins/mekari-session.spec.js until pass.
  7. Quality gateyarn lint:js && yarn build.

Acceptance criteria

  • logged_in → no store dispatch, no navigation.
  • logged_outuser/userLogout dispatched (redirect added in Task 3). This branch also covers an SSO account switch — the SDK delivers it as logged_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

DisciplineDays
Frontend1.0
Backend
QA0.5
Total1.5

Assumptions: reuses existing userLogout; no toast, no msli, no separate switch_user branch. 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_out branch 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_out after 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

ActionFileWhat changes
extendstore/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/createtests/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:userLogout verified at line 120, cleanup at 133–134. The external-redirect-via-window.location.href pattern is verified only at middleware/redirect-to-v3.js:65 (an earlier draft also cited middleware/version-switcher.js:13, which is not a verified location for this idiom and has been dropped). Test path tests/store/user.spec.js [verify exact filename in tests/store/].

Implementation steps

  1. Explore the codebase area — Open store/user.js and read userLogout (120) end-to-end: it POSTs ${USER_URL}/sign_out then dispatches deleteUserLocalData + deleteSsoCookies. Open middleware/redirect-to-v3.js:65 to copy the window.location.href cross-origin redirect idiom. Resolve Q7 first: grep all callers of userLogout (account-switch, embed layouts) to confirm none expect to stay in-app.
  2. Write failing tests (red) — In tests/store/user.spec.js: mock the sign_out request to resolve, set centralized_session on, assert window.location.href === 'https://account.mekari.com/sign_out' after userLogout; toggle off → assert no redirect. Mock window.location. Run, confirm red.
  3. Scaffold — Add a redirect line guarded by the toggle read in the userLogout success continuation.
  4. Wire state — Read the toggle from state.custom_features (same .some((f) => f.code === … && f.enabled) shape as Task 1) inside the action.
  5. Implement behavior — Set window.location.href only after cleanup; keep the existing .catch behaviour (proceed to cleanup even if POST errors) intact.
  6. Go greenyarn test-file tests/store/user.spec.js.
  7. Quality gateyarn lint:js && yarn build.

Acceptance criteria

  • After sign_out POST resolves and cleanup runs, window.location.href is set to https://account.mekari.com/sign_out when centralized_session is on.
  • When the toggle is off, userLogout behaves exactly as pre-RFC (no external redirect).
  • Existing .catch path (POST error → still clean up) is preserved.
  • (Q7) No existing in-app userLogout caller 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

DisciplineDays
Frontend1.0
Backend
QA0.5
Total1.5

Assumptions: reuses existing userLogout + verified window.location.href redirect 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_out branch 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_down errors 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

ActionFileWhat changes
extendplugins/mekari-session.jsImport datadogRum from @datadog/browser-rum; call datadogRum.addAction('mekari_session.event', { status }) in handleEvent, and datadogRum.addError(...) in the server_down branch only
extendtests/plugins/mekari-session.spec.jsAssert addAction/addError called with the expected payload (mock datadogRum)

File path rule: plugins/datadog-rum.js verified — it import { datadogRum } from '@datadog/browser-rum' (line 1) and gates init with if ($config.ddEnabled === 'true') (line 4). @datadog/browser-rum@^4.34.0 confirmed in package.json. Note: addAction/addError are not yet used anywhere in the repo — confirm the v4 API surface (datadogRum.addAction(name, context), datadogRum.addError(error, context)) when wiring.

Implementation steps

  1. Explore the codebase area — Open plugins/datadog-rum.js (verified) for the import { datadogRum } form and the service: 'qontak-crm-frontend' convention (line 9). Confirm RUM is initialised before the session plugin runs (datadog-rum at nuxt.config.js:66, session plugin registered after — verify ordering).
  2. Write failing tests (red) — Extend tests/plugins/mekari-session.spec.js: mock datadogRum, assert addAction('mekari_session.event', { status }) fires for every handled status and addError fires only on server_down. Run, confirm red.
  3. Scaffold — Add import { datadogRum } from '@datadog/browser-rum' to the plugin.
  4. Wire state — Pass { status: data.status } from handleEvent.
  5. Implement behavior — Add addAction to every branch of handleEvent; addError in the server_down branch only.
  6. Go greenyarn test-file tests/plugins/mekari-session.spec.js.
  7. Quality gateyarn lint:js && yarn build.

Acceptance criteria

  • Each handled SDK status emits datadogRum.addAction('mekari_session.event', { status }).
  • server_down additionally emits datadogRum.addError(...).
  • No postMessage-origin-mismatch error is attempted (not observable by CRM).
  • No new console.log introduced.

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

DisciplineDays
Frontend0.5
Backend
QA0
Total0.5

Assumptions: reuses existing @datadog/browser-rum dep + 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-sdk Session package 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

ActionFileWhat changes
extendpackage.json (+ yarn.lock)Add mekari-account-web-sdk as a git dependency pinned to an exact ref
extendplugins/mekari-session.jsUncomment/replace the stubbed import with the real mekari-account-web-sdk Session

File path rule: package.json verified (no mekari-account-web-sdk present). plugins/mekari-session.js is created in Task 1.

Implementation steps

  1. 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.
  2. 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).
  3. Replace the Task-1 import seam with import { Session } from 'mekari-account-web-sdk'.
  4. yarn install --frozen-lockfile passes; yarn build resolves the import.

Acceptance criteria

  • mekari-account-web-sdk present in package.json deps at a pinned git ref.
  • yarn install --frozen-lockfile passes.
  • yarn build resolves the mekari-account-web-sdk import.

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

DisciplineDays
Frontend0.5
Backend
QA0
Total0.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-sdk git 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

ActionFileWhat changes
createBE current_company endpoint[unverified — covered in BE RFC, link REQUIRED]
extendstore/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 SSO current_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

DisciplineDays
Frontend1.0
Backend2.0
QA0.5
Total3.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_company BE 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 storyGrounded statusEvidence (qontak.com)
CRM-S01 — expose sso_id on /users/meAlready done — verification, not buildapp/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_idService existsapp/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 rowReal gap, but reusable scaffolding existsRoute/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 workapp/models/user.rb Devise module list has no :timeoutable; no timeout_in in config/initializers/devise.rb.
CRM-S04 — Kong centralized_web_session metadataModel missingNo 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

  1. 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 currentUser stubbed. The former "start with the msli helper" recommendation no longer applies — that task is deleted.
  2. 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 currentUser stubbed; 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).
  3. Task 3 (logout → SSO redirect) is independent and should land alongside Task 2, because Task 2's logged_out branch reuses that redirect. It's safely toggle-gated, so it can merge before the SDK is even installed.
  4. 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.
  5. 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 a mekari-account-web-sdk ref → unblocks the real import in Task 5, turning Tasks 1/2/4 from stubbed to complete), Q2 (SSO-id field in /users/me → unblocks the Session constructor arg), Q3 (exact centralized_session code string), and Q4 (the current_company contract → unblocks Task 6). Also confirm Q8 (CSP delivery mechanism, Infosec) before pilot, and separately escalate Q10 (the SDK's event.origin validation 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 / TaskReason / 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 (msli fallback helper, 2.0 md) and Task 7 (switch_user proper SSO-autologin flow, 3.5 md) do not appear here because they are not blocked-but-eventually-buildable — they are deleted outright. The SDK owns msli internally (consumers must not touch it) and has no switch_user status at all (mekari-account-web-sdk v0.3.0, session.ts); see crm-fe.md ADR-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_session code — stubs Task 1's gate), Q7 (logout-caller regression audit — Task 3), Q8 (CSP delivery, Infosec — pilot gate). Q10 (SDK event.origin validation gap) is a cross-RFC infosec finding, not a task-blocking unknown for this breakdown.