RFC: Centralized Web Session — CRM Frontend Integration of @mekari/sdk Session
Document Conventions (do not remove)
Follows the Qontak RFC Template governance shell (metadata table + sections 1–6 + Comment logs) and is agent-execution-ready (§2 Repo Reading Guide, mermaid diagrams, §4 Execution Plan + Verification & Rollback Recipe). YAML frontmatter is the machine-readable index; the metadata table is the human record. Both agree on every shared field.
Scope guard. This RFC covers the CRM frontend repository only (
/Users/mekari/Documents/crm). The Mekari Session service (Golang), the dedicated Redis, the Kong/MAG gateway pathaccount.mekari.com/sm/*, and the SSO session store changes are owned by Account & Launchpad and live in other repos — they are READ-ONLY context here, captured to anchor the CRM integration. No backend code is specified in this RFC.
Metadata
| Field | Value | Notes |
|---|---|---|
| Status | RFC | Open for review; not yet Ready for agent execution (see §7) |
| Type / Sub-type | frontend / enhancement | CRM consumes a new shared SDK; no new product surface beyond a notification |
| Title | Centralized Web Session — CRM FE Integration of @mekari/sdk Session | — |
| Owner | Account & Launchpad (SDK) · CRM Frontend (this integration) | Cross-squad |
| Authors | Qontak CRM Frontend | — |
| Reviewers | A&L (SSO/SM), CRM FE tech lead | Cross-squad review required |
| Approver(s) | CRM FE tech leader, Infosec [REQUIRED] | Infosec gates CSP / SDK origin policy |
| Submitted | 2026-06-27 | ISO-8601 |
| Last updated | 2026-06-27 | ISO-8601 |
| Target release | 2026-Q3 | Pilot is Launchpad first (PRD §4.4); CRM follows in step 5 |
| Related documents | Centralized Web Session RFC (Confluence) | Driver |
| Discussion | [REQUIRED] | Slack thread |
Sections at a Glance
| § | Section | CRM-FE hint |
|---|---|---|
| 1 | Overview | Problem, success criteria, scope, Detail 1.A coverage matrices, 1.B decision index, 1.C per-story change map |
| 2 | Technical Design | Detail 2.0 Repo Reading Guide (Repo Map, anchors, source verification, patterns, reading order), infra topology, ADR blocks, no-DDL, API matrices, sequence + state + branch diagrams |
| 3 | High-Availability & Security | SDK origin/CSP policy, FE observability, msli fallback integrity |
| 4 | Backwards Compatibility & Rollout | Feature toggle centralized_session, Agent Execution Plan, Verification & Rollback Recipe |
| 5 | Concerns, Questions, Known Limitations | Severity-tagged open questions |
| 6 | Comment logs | Review trail |
| 7 | Ready for agent execution | Marker + failing gates |
1. Overview
1.1 Context & Problem
Mekari products each maintain their own session and do not react when the
user logs out, switches account, or goes idle on SSO. CRM is one such product:
its session lives in qcrm_access_token / qcrm_refresh_token cookies and an
@nuxtjs/auth Vuex strategy (schemes/crmAuthScheme.js), refreshed on a local
timer (utils/helpers/auth.js:setupAutoTokenRefresh). There is no signal
from SSO into CRM, so the PRD's stale-session bugs apply to CRM:
- A user who logs out on SSO stays logged in on CRM until the CRM token expires.
- A user who switches account on SSO still sees the previous company in CRM.
The shared @mekari/sdk Session SDK closes this gap: every CRM page loads the
SDK with the current user's SSO id; the SDK opens an iframe to
account.mekari.com/sm/current, the Session Manager validates/extends the SSO
session in its dedicated Redis, and the SDK dispatches logged_in,
logged_out, switch_user, or server_down back to CRM. CRM must react to
each consistently with its existing auth/session/routing/store patterns.
1.2 Success Criteria (CRM-FE slice)
- CRM loads
@mekari/sdkSessionon all authenticated pages with the currentuser_sso_id, gated behind acentralized_sessionfeature toggle. - CRM handles all four SDK events consistently with its current sign-out, routing, and store behavior.
- CRM refreshes SSO session duration on user activity via
session.refresh()(throttled). - When the Session Manager is unreachable, CRM falls back to the
mslilocalStorage timestamp +_mekari_accountcookie heuristic (2-hour idle window) and, if that fails, performs theserver_downsign-out.
1.3 Non-goals / Out of scope
- Any Session Manager / SSO / Redis / Kong-MAG backend work (owned by A&L).
- Auto revoke of access & refresh tokens on inactivity-driven logout (PRD out-of-scope).
- CRM backend (
api.mekari.com/ mobile API) changes forcurrent_companysync — dependency, not deliverable here (see §5 Q4). - New product UI surfaces beyond a "user has changed" notification toast.
1.4 Design References
n/a — no Figma. This is an integration RFC with one user-visible surface:
a toast on switch_user ("user has changed"). It reuses the existing
notification mechanism (see Detail 2.0 Patterns). If product wants a bespoke
interstitial, that is a separate design ask — raised as §5 Q5.
1.5 Related Documents
| Doc | Kind | Link | Used for |
|---|---|---|---|
| Centralized Web Session RFC | authoritative (driver) | Confluence | SDK contract, event names, flows, rollout |
Detail 1.A — Coverage Matrices
PRD Section Coverage
| PRD section | Covered in RFC | Notes |
|---|---|---|
| 1. Overview / known issues | §1.1 | CRM-relevant stale-session cases mapped |
| Success Criteria | §1.2 | FE-slice subset; SDK/Redis criteria are A&L |
| Out of Scope | §1.3 | Inherited |
| Dependencies (SDK, SM service, Redis) | §2.0 / §5 | SDK consumed; SM+Redis are A&L deps |
| 2. Technical Design — Current/Proposal | §2.1–§2.2 | CRM current auth vs SDK proposal |
| How to use the SDK | §2.4 (Inbound) / §2.2 | Mapped to CRM plugin |
Local Storage (msli) | §2.2 / §2.3 | New FE artifact |
Cookies (_mekari_account) | §2.0 | Managed by SM/SSO, read by SDK iframe — CRM does not write it |
| Session Manager SDK contract / events | §2.4 (Inbound events) | Four events handled |
| FE Product Integration Flows — SDK Flow | §2.5 (seq) | Implemented as plugin |
| FE Flows — Web Session Flow | §2.5 / §5 Q1 | CRM is token-based; mapping unresolved |
| FE Flows — OAuth2 Authorization Code Flow | §5 Q1 | CRM does not use authz-code today |
| User Logout From Product | §2.5 / §2.2 | Reuse userLogout + redirect to sign_out |
| User Switch Account | §2.6 (state) / §5 Q1 | switch_user mapping is a critical gap |
| Database Model (no changes) | §2.3 | Confirmed no DDL |
| 3. HA & Security | §3 | FE: CSP/origin + observability |
| 4. Rollout Plan | §4 | CRM = step 5 (post-Launchpad pilot) |
| 5. Open Questions | §5 | CSP vs referrer/origin + CRM-specific |
| FE Implementation Scope (per product repo) | §1.C / §4 | This RFC's core |
UI / Consumer Surface Coverage
| Surface | Type | Read endpoint | Notes |
|---|---|---|---|
switch_user notification toast | UI | n/a — covered by writes | Reuses notification pattern (Detail 2.0) |
| Sign-out redirect (logged_out / server_down) | navigation | n/a | Reuses userLogout + account.mekari.com/sign_out |
| Every authenticated CRM page (SDK host) | runtime | n/a | SDK mounted globally via plugin |
Role Coverage
| PRD role | CRM handling | Notes |
|---|---|---|
| Authenticated CRM user (any role) | SDK loads with their user_sso_id | Session events role-agnostic |
| banned / suspended / freezed / expired | Existing crm-user middleware redirects unchanged | SDK runs after auth guard; no role-specific session branch in PRD |
Per-status authorization is unchanged by this RFC — see §2 Role × Endpoint.
Detail 1.B — Decisions Closed (index → §2 ADR blocks)
| # | Decision | Chosen | Pre-baked? | ADR |
|---|---|---|---|---|
| D1 | SDK delivery into CRM | npm @mekari/sdk + new Nuxt plugin | partial (PRD shows import) | §2 ADR-1 |
| D2 | Feature gating | Reuse custom_features w/ code centralized_session | yes (PRD names toggle) | §2 ADR-2 |
| D3 | SDK mount point + event wiring | New plugins/mekari-session.js, wired to $auth + store/user | no | §2 ADR-3 |
| D4 | logged_out / server_down action | Reuse existing userLogout → redirect to account.mekari.com/sign_out | no | §2 ADR-4 |
| D5 | switch_user action (token model) | Interim: treat as full sign-out + re-login; SSO-autologin path deferred | no — see §5 Q1 | §2 ADR-5 |
| D6 | msli fallback storage | localStorage timestamp, new helper | yes (PRD) | §2 ADR-6 |
| D7 | current_company sync | Deferred — BE dependency, CRM uses teams not SSO current_company | no — see §5 Q4 | §2 ADR-7 |
Detail 1.C — Per-Story Change Map
| Story (from PRD FE Implementation Scope) | Layer scope | Changes | Acceptance criteria | RFC anchors |
|---|---|---|---|---|
S1 Add @mekari/sdk Session, load with user_sso_id | FE-only | package.json dep; plugins/mekari-session.js (new); registered in nuxt.config.js:plugins | Unit test: plugin instantiates Session with user_sso_id from $auth.user; yarn test green | §2.2, §4.C chunk 2–3 |
S2 Gate behind centralized_session toggle | FE-only | Read store.state.user.custom_features for code centralized_session; plugin no-ops when off | Test: SDK not constructed when feature absent/false | §2 ADR-2, §4.C chunk 3 |
| S3 Handle 4 SDK events | FE-only (Runtime/behavior) | Event handlers in plugin → dispatch user/userLogout, toast, redirect | Test per event: logged_in→noop+set msli; logged_out→sign-out; switch_user→sign-out+toast; server_down→sign-out | §2.4 Inbound, §2.5, §2.6, §4.C chunk 4 |
| S4 current-company sync | FE + BE | Blocked — no CRM current_company endpoint found; teams ≠ SSO company | n/a — covered in BE RFC [REQUIRED link] / deferred | §2 ADR-7, §5 Q4 |
S5 Wire product logout to account.mekari.com/sign_out | FE-only | Extend store/user.js:userLogout success path to redirect to SSO sign_out | Test: after sign_out POST resolves, window.location → account.mekari.com/sign_out | §2.5, §4.C chunk 5 |
S6 msli fallback + server_down sign-out | FE-only | New utils/helpers/mekari-session.js: read/write msli, 2h compare, _mekari_account presence | Test: msli<2h & cookie valid → logged_in; msli>2h or no cookie → logged_out; else server_down | §2 ADR-6, §2.5, §4.C chunk 4 |
| S7 Observability | FE-only (Config) | Datadog RUM custom action/error on event + server_down (reuse @datadog/browser-rum) | Datadog action mekari_session.event visible; yarn lint:js green | §3.2, §4.C chunk 6 |
Every artifact above re-appears in §2 / §4. Detail 1.C is the index, not the source of truth.
2. Technical Design
2.0 Repo Reading Guide (read before writing any code)
Repo Map (slice this RFC touches)
flowchart LR
subgraph CRM[FE: local/crm — Nuxt 2 SPA]
NCONF["nuxt.config.js<br/>plugins[] (modify)"]
PLUG["plugins/mekari-session.js<br/>(new)"]
HELP["utils/helpers/mekari-session.js<br/>msli fallback (new)"]
AUTH["schemes/crmAuthScheme.js<br/>(read — auth model)"]
AHELP["utils/helpers/auth.js<br/>(read — refresh timer)"]
USTORE["store/user.js<br/>userLogout (modify)"]
UFEAT["store/user.js<br/>custom_features (read)"]
DDR["plugins/datadog-rum.js<br/>(read — RUM pattern)"]
EP["assets/variables/endpoints.js<br/>(read/extend)"]
end
subgraph EXT[READ-ONLY context — owned by A&L]
SDK["@mekari/sdk Session"]
SM["account.mekari.com/sm/current<br/>(Session Manager + iframe)"]
end
NCONF --> PLUG
PLUG --> HELP
PLUG --> SDK
SDK -. iframe .-> SM
PLUG --> USTORE
PLUG --> UFEAT
PLUG --> DDR
USTORE --> EP
AUTH -. informs .-> PLUG
AHELP -. informs .-> PLUG
Existing Code Anchors
| # | Path | What to learn |
|---|---|---|
| 1 | schemes/crmAuthScheme.js | CRM auth model: LocalScheme subclass; tokens in qcrm_access_token/qcrm_refresh_token cookies; logout() → requestWith(logout) + $auth.reset(). CRM is token-based, not authz-code. |
| 2 | utils/helpers/auth.js | refreshToken(), setupAutoTokenRefresh() (10-min pre-expiry timer), clearAutoTokenRefresh(). The SDK must not fight this timer. |
| 3 | store/user.js:120 (userLogout) | Logout action: POST ${USER_URL}/sign_out, then deleteUserLocalData + deleteSsoCookies + $auth.reset(). Reuse for logged_out/server_down. |
| 4 | store/user.js:144 (deleteSsoCookies) | Which cookies CRM clears on logout (crm_sso_*, chat_sso_*, global_sso_*). |
| 5 | store/user.js:67 (getCustomFeature) | Feature toggle source: GET /users/me/feature_enabled → custom_features: [{code, enabled}]. Gate centralized_session here. |
| 6 | middleware/crm-user.js | Auth guard order: runs getUserData then status redirects. SDK mounts after this. |
| 7 | plugins/datadog-rum.js | RUM init pattern + env gate (DD_ENABLED). Reuse for session observability. |
| 8 | nuxt.config.js:58 (plugins) | Plugin registration order — ~/plugins/auth runs first; add ~/plugins/mekari-session after auth + auto-token-refresh. |
| 9 | assets/variables/endpoints.js | Endpoint constant pattern (USER_URL, CRM_V28). Add SSO/SM constants here if needed. |
| 10 | adapters/http/utils.js (processAuthHeaders) | How crm_sso_token becomes X-Auth-Token under ENABLE_SEAMLESS_AUTH. Context for SSO-token interplay. |
Source Verification
| Anchor / Pattern / Contract | Evidence (verified) |
|---|---|
| CRM auth = LocalScheme token model | schemes/crmAuthScheme.js:12 export default class CrmAuthScheme extends LocalScheme; login() POST + Cookies.set('qcrm_refresh_token', …) :33 |
| logout flow | schemes/crmAuthScheme.js:234 async logout() → this.$auth.reset() :242; store/user.js:120 userLogout({ dispatch }), POST ${USER_URL}/sign_out :131 |
| sign-out cookie cleanup | store/user.js:144 deleteSsoCookies() removes crm_sso_token/chat_sso_token/global_sso_token :145–155 |
| feature toggle mechanism | store/user.js:67 getCustomFeature(); state custom_features store/user.js:12–13; mutation SET_CUSTOM_FEATURE :350 |
| feature-check shape | middleware/redirect-to-v3.js:49 reads store.state.user.custom_features, matches feature.code === … |
| refresh timer | utils/helpers/auth.js:157 setupAutoTokenRefresh; :13 REFRESH_BEFORE_EXPIRY_MINUTES = 10; :248 clearAutoTokenRefresh |
| plugin pattern + order | nuxt.config.js:58–72 plugins[], '~/plugins/auth' first :59 |
| RUM pattern | plugins/datadog-rum.js:1 import { datadogRum }; :4 if (process.env.DD_ENABLED === 'true') |
| endpoint constants | assets/variables/endpoints.js:1 USER_URL, :5 CRM_V28 |
no @mekari/sdk present | searched package.json deps — only @mekari/pixel found; @mekari/sdk absent (verified by explorer sweep) |
| cookie domain | schemes/crmAuthScheme.js:8 COOKIE_DOMAIN_ENV = process.env.COOKIE_DOMAIN || '.qontak.com' |
CRM user_sso_id field | UNVERIFIED — /users/me response not confirmed to expose an SSO id; external_company_id exists (plugins/mixpanel.js:25) but is company, not user SSO id → §5 Q2 |
| CRM current_company endpoint | UNVERIFIED / absent — only team endpoints found (store/user.js:244,261,274); no current_company → §5 Q4 |
centralized_session feature code | UNVERIFIED — code format is CP-QONTAKCRM-YYYY-NNNN (utils/helpers/package-features.js), but feature_enabled also uses string codes (e.g. use_central_contact_data). Exact code TBD → §5 Q3 |
Patterns to Follow
| Concern | Reference file | Note |
|---|---|---|
| Third-party SDK init via plugin | plugins/datadog-rum.js | Conditional env/feature gate; default-export init fn receiving Nuxt ctx |
Plugin accessing $auth/store | plugins/auth.js | window.$auth = app.$auth pattern; ctx access |
| Feature-flag read | middleware/redirect-to-v3.js:49 | store.state.user.custom_features.find(f => f.code === X) |
| Sign-out orchestration | store/user.js:120 userLogout | Reuse, don't reinvent |
| External redirect | middleware/version-switcher.js:13, middleware/redirect-to-v3.js:65 | window.location.href for cross-origin |
| Cookie access | js-cookie (Cookies.get/set) used everywhere | Use for _mekari_account read only (do not write it) |
| User notification toast | [REQUIRED: confirm component] | Per components/_general/ + user-notifications skill — verify exact toast API before drafting S3 toast |
Reading Order for the Agent
schemes/crmAuthScheme.js— understand the auth/session lifecycle.utils/helpers/auth.js— refresh timer; do not conflict.store/user.js(userLogout,deleteSsoCookies,getCustomFeature).middleware/crm-user.js— guard order.nuxt.config.js(plugins) — registration point + order.plugins/datadog-rum.js— SDK init + RUM pattern.assets/variables/endpoints.js— endpoint constant style.adapters/http/utils.js— SSO-token header interplay.- PRD (Confluence) — SDK event contract + flows.
utils/helpers/package-features.js— feature code conventions.
Existing API check
| Call | Tag | Justification |
|---|---|---|
GET account.mekari.com/sm/current (via SDK iframe) | new (external, A&L-owned) | Not a CRM endpoint; CRM only loads the SDK which opens the iframe |
GET /users/me/feature_enabled | reused | store/user.js:67 — read centralized_session toggle |
POST /api/mobile/v2.7/users/sign_out | reused | store/user.js:131 — existing CRM sign-out |
account.mekari.com/sign_out redirect | new (external, A&L-owned) | New cross-origin redirect target on logout |
current_company sync | new-with-justification / deferred | No CRM endpoint exists; PRD names an SSO endpoint hit by product BE. CRM FE cannot satisfy without BE work → §5 Q4 |
2.1 Current state (CRM)
CRM authenticates via POST /authentication/authenticate
(crmAuthScheme.login), persists qcrm_access_token (7d) +
qcrm_refresh_token (365d) cookies on .qontak.com, and auto-refreshes 10 min
before expiry. Under ENABLE_SEAMLESS_AUTH, it also reads
crm_sso_token/global_sso_token and injects X-Auth-Token. There is no
inbound SSO session signal. Logout (store/user.js:userLogout) POSTs
/users/sign_out, clears cookies/localStorage, and resets $auth — it does
not redirect to SSO.
2.2 Proposal (CRM)
Add a new Nuxt plugin plugins/mekari-session.js that, when the
centralized_session toggle is on:
- Reads the current
user_sso_idfrom$auth.user([REQUIRED field — §5 Q2]). - Constructs
new Session({ current_user: user_sso_id })from@mekari/sdk. - Subscribes to SDK events and maps them to CRM actions (§2.4 / §2.6).
- Calls
session.refresh()(throttled) on user activity to extend the SSO session. - On any SDK failure, uses the
mslilocalStorage fallback (utils/helpers/mekari-session.js) before decidingserver_down.
The plugin loads after ~/plugins/auth (so $auth.user is available) and
must not interfere with setupAutoTokenRefresh.
2.3 Database Model
n/a — no database changes. CRM FE introduces client-side state only:
| Artifact | Store | Value | Lifecycle |
|---|---|---|---|
msli | localStorage | timestamp of last logged_in confirmation | written on logged_in; removed on logged_out/switch_user; read as ≤2h fallback when SM unreachable |
_mekari_account | cookie (.qontak.com) | SSO session id | read-only in CRM — written by SSO/SM; CRM only checks presence in fallback |
There are no status enums owned by CRM here; the session status lifecycle (§2.6) is a transient client state, not persisted.
2.4 APIs / Contracts
Outbound (CRM → others)
| Method | Endpoint | Auth | Owner | Tag | Failure behavior |
|---|---|---|---|---|---|
| GET (via SDK iframe) | account.mekari.com/sm/current | _mekari_account cookie | A&L | new (external) | SDK retry/backoff; exhausted → server_down event |
| POST | /api/mobile/v2.7/users/sign_out | Authorization: $auth.getToken('crm') | CRM BE | reused | .catch → still proceed to client cleanup (existing behavior) |
| GET | /api/mobile/v2.7/users/me/feature_enabled | bearer | CRM BE | reused | toggle read; on error treat feature as off (fail-closed → SDK not loaded) |
| Redirect | account.mekari.com/sign_out | cookie | SSO (A&L) | new (external) | window.location.href after local cleanup |
| GET | …/current_company | client_credentials / me | SSO (A&L) | deferred | §5 Q4 — not implemented in this RFC |
Inbound (SDK events → CRM)
The SDK dispatches via dispatchEvent; CRM subscribes through
session.on("event", (data, error) => …) (PRD usage example).
| Event | Payload | CRM action |
|---|---|---|
logged_in | { status, user_sso_id } (matches given id) | set msli = now; no navigation; emit RUM action |
logged_out | session absent | run sign-out (ADR-4): store/user/userLogout → redirect account.mekari.com/sign_out |
switch_user | session exists, different user_sso_id | remove msli; toast "user has changed"; interim sign-out + re-login (ADR-5, §5 Q1) |
server_down | SDK retries exhausted | msli fallback first; if it resolves logged_out → sign-out; else suggested sign-out (ADR-4) |
2.5 Sequence Diagrams
Happy path — page load, session valid
sequenceDiagram
autonumber
participant U as User Browser
participant CRM as CRM SPA (plugin)
participant FT as feature_enabled (CRM BE)
participant SDK as @mekari/sdk Session
participant IF as iframe → account.mekari.com/sm/current
participant SM as Session Manager
participant R as SSO Redis
U->>CRM: load authenticated page
CRM->>FT: GET /users/me/feature_enabled
FT-->>CRM: centralized_session = true
CRM->>SDK: new Session({ current_user: user_sso_id })
SDK->>IF: inject iframe (_mekari_account cookie)
IF->>SM: GET /sm/current (cache ≤5s)
SM->>R: validate + update last_request_at (idle <2h)
R-->>SM: session ok, user_sso_id
SM-->>IF: render page w/ user_sso_id
IF-->>SDK: postMessage(user_sso_id)
SDK-->>CRM: event logged_in (id matches)
CRM->>CRM: localStorage msli = now
Note over CRM: no navigation; RUM action mekari_session.event=logged_in
Failure path — Session Manager unreachable (server_down)
sequenceDiagram
autonumber
participant CRM as CRM SPA (plugin)
participant SDK as @mekari/sdk Session
participant IF as iframe → /sm/current
participant LS as localStorage(msli) + cookie(_mekari_account)
CRM->>SDK: new Session({ current_user })
SDK->>IF: inject iframe
IF--xSDK: timeout / no postMessage (backoff retries)
SDK-->>CRM: event server_down
CRM->>LS: read msli + _mekari_account
alt msli age < 2h AND _mekari_account present
CRM->>CRM: treat as logged_in (degraded), keep session
Note over CRM: RUM error mekari_session.fallback=logged_in
else msli age > 2h OR cookie invalid
CRM->>CRM: server_down sign-out (ADR-4)
Note over CRM: dispatch userLogout → redirect account.mekari.com/sign_out
end
Failure path — logout from CRM (cross-origin)
sequenceDiagram
autonumber
participant U as User
participant CRM as CRM SPA
participant BE as CRM BE (/users/sign_out)
participant SSO as account.mekari.com/sign_out
U->>CRM: click logout
CRM->>BE: POST /users/sign_out (Authorization)
BE-->>CRM: 200 (or error → still proceed)
CRM->>CRM: deleteUserLocalData + deleteSsoCookies + $auth.reset()
CRM->>SSO: window.location.href = account.mekari.com/sign_out
SSO-->>U: destroy SSO session + mapping → redirect to sign in
2.6 Session State Machine (client-side)
stateDiagram-v2
[*] --> Unknown
Unknown --> LoggedIn: event logged_in (id match)
Unknown --> LoggedOut: event logged_out
Unknown --> Switched: event switch_user
Unknown --> Degraded: event server_down + msli<2h & cookie ok
Unknown --> LoggedOut: event server_down + fallback fails
LoggedIn --> LoggedOut: event logged_out
LoggedIn --> Switched: event switch_user
LoggedIn --> Degraded: event server_down (fallback ok)
Degraded --> LoggedIn: next page event logged_in
Degraded --> LoggedOut: next page event logged_out / fallback expires
Switched --> SignOut: interim (ADR-5) destroy session
LoggedOut --> SignOut
SignOut --> [*]: redirect account.mekari.com/sign_out
LoggedIn --> LoggedIn: session.refresh() (throttled, on activity)
2.7 Branch & Skip Catalog
| Branch | Trigger | Owner | Action |
|---|---|---|---|
| Toggle off | centralized_session false/absent | CRM FE | SDK not loaded; legacy behavior (skip) |
| Toggle read error | feature_enabled fails | CRM FE | Fail-closed: skip SDK (no regression) |
logged_in id match | normal | CRM FE | No-op + msli refresh |
| Degraded keep-alive | server_down + msli<2h + cookie ok | CRM FE | Keep session (no sign-out) |
2.8 Technical Decisions (ADR)
Minimum-coverage map: Storage→ADR-6; Sync/async→ADR-3; Caching→ADR-6 (msli 2h
- SM 5s, SM-owned); Third-party→ADR-1; Consistency→ADR-3; Multi-tenancy→ADR-5 (
user_sso_idcompare); Reuse/new→ADR-4.
ADR-1 — SDK delivery into CRM
- Context: PRD provides
@mekari/sdkSession(CJS+ESM) and an SDK URLaccount.mekari.com/sm/sdk.js. CRM is Nuxt 2 SPA, plugins for third-party SDKs (plugins/datadog-rum.js). - Options: (a) npm
@mekari/sdkimported in a Nuxt plugin; (b)<script src=account.mekari.com/sm/sdk.js>runtime tag. - Decision: (a) npm package +
plugins/mekari-session.js. - Rationale: matches the PRD usage example (
import { Session }), the existing plugin pattern, tree-shaking, and testability (mockable import). Pre-baked by author (import form). - Consequences: new pinned dependency; must confirm package exists in
Mekari npm registry (§5 Q6). The SDK still injects the iframe to
account.mekari.comat runtime, so origin/CSP policy (§3.1) still applies. - Reversibility: high — remove plugin + dep; toggle off instantly.
ADR-2 — Feature gating
- Context: PRD gates behavior behind toggle
centralized_session. CRM hascustom_featuresfrom/users/me/feature_enabled. - Options: (a) reuse
custom_features; (b) new env flag; (c) newpackageFeaturesentry. - Decision: (a) reuse
custom_features, check codecentralized_session. - Rationale: server-driven, per-company/app rollout (PRD step 5) matches
feature_enabledsemantics; no new mechanism. Pre-baked. - Consequences: exact code string must be confirmed (§5 Q3); fail-closed on read error.
- Reversibility: high — server toggles off.
ADR-3 — SDK mount point & event wiring
- Context: SDK must run on all authenticated pages with
$auth.useravailable, without fightingsetupAutoTokenRefresh. - Options: (a) Nuxt plugin after
~/plugins/auth; (b) per-page mixin; (c) middleware. - Decision: (a) plugin, registered after
auth+auto-token-refreshinnuxt.config.js:plugins. - Rationale: single global init (like RUM), guaranteed
$authready, one event subscription. Async/event-driven (no blocking); consistency model is eventual — session re-checked per page load + on activity refresh. - Consequences: SPA route changes don't reload the plugin; need an
on-activity / per-navigation
session.refresh()hook (router afterEach). - Reversibility: high.
ADR-4 — logged_out / server_down action
- Context: PRD: product sign-out flow on
logged_out; suggested sign-out onserver_down. - Options: (a) reuse
store/user/userLogout; (b) bespoke teardown. - Decision: (a) reuse
userLogout, then redirect toaccount.mekari.com/sign_out. - Rationale:
userLogoutalready clears cookies/localStorage/$auth; only the SSO redirect is new (S5). No duplication. - Consequences: adds cross-origin redirect to logout; verify it doesn't break existing in-app logout callers (§5 Q7).
- Reversibility: medium — redirect target behind same toggle.
ADR-5 — switch_user action under CRM's token model
- Context: PRD web-session flow for
switch_user= destroy product session → redirect to product sign-in → SSO autologin (valid_mekari_account) → back → new product session → get current company. CRM has no SSO-autologin redirect today (crm-userredirects tocrmV1Host/login), and CRM is token-based, not authz-code. - Options: (a) full implementation of SSO autologin round-trip (needs BE +
SSO callback CRM does not have); (b) interim: treat
switch_useras sign-out + toast "user has changed", let user re-login as the new account; (c) silently swap tokens (rejected — no mechanism, security risk). - Decision: (b) interim sign-out + toast; (a) deferred pending SSO autologin contract.
- Rationale: (a) requires an SSO callback route + token re-issue path that does not exist in CRM and is unspecified for the token model — implementing it blind would hallucinate the contract. (b) is correct and safe.
- Consequences: worse UX than PRD's seamless switch (user re-enters credentials or hits SSO login). Must be called out to product.
- Reversibility: high — upgrade to (a) when SSO autologin path is defined.
- Status: depends on §5 Q1
[critical].
ADR-6 — msli fallback storage
- Context: PRD:
mslilocalStorage timestamp as SM-down fallback. - Options: (a) localStorage timestamp +
_mekari_accountpresence check; (b) cookie; (c) Vuex (lost on reload). - Decision: (a), in new
utils/helpers/mekari-session.js. - Rationale: survives reloads, matches PRD, pure + unit-testable.
- Consequences: localStorage clears must be added to
deleteUserLocalData(clearmslion logout). - Reversibility: high.
ADR-7 — current_company sync
- Context: PRD products sync current company after session via SSO
current_company(BE, client_credentials). CRM uses teams (/users/crm_teams,/crm/teams), and nocurrent_companyendpoint or field was found. - Options: (a) implement once CRM BE exposes current_company; (b) map to CRM team context; (c) defer.
- Decision: (c) defer — out of FE-deliverable scope; needs BE RFC.
- Rationale: no in-repo contract exists; inventing one violates anti-hallucination. Whether CRM even has a per-company switch equivalent is unconfirmed.
- Consequences: after
switch_userinterim sign-out + re-login, company context resolves through CRM's normal login — acceptable for interim. - Reversibility: n/a (deferred).
- Status: §5 Q4
[critical].
2.9 Role × Endpoint Authorization
| Role | feature_enabled | sign_out | SDK iframe |
|---|---|---|---|
| Any authenticated user | read own toggle | own session | own _mekari_account |
| banned/suspended/freezed/expired | guarded by crm-user before SDK | same | unchanged |
n/a — no new authorization surface; SDK is per-user via user_sso_id compare.
3. High-Availability & Security
3.1 Security — SDK origin policy
The SDK injects an iframe to account.mekari.com and exchanges postMessage.
CRM (the SDK host) must be hardened. PRD offers two mechanisms (PRD §3 Security
Implications), and CRM currently has no CSP configured (nuxt.config.js
head has meta/link only — verified). CRM-side requirements:
postMessageorigin validation (mandatory): the plugin must verifyevent.origin === 'https://account.mekari.com'before trusting any SDK message. (If the SDK already enforces this internally, confirm and document.)- CSP [REQUIRED decision — §5 Q8]: add
frame-src/child-src https://account.mekari.comandscript-srcfor the SDK origin. Since CRM is SPA-only with no server headers innuxt.config.js, CSP must be delivered by the CDN/edge (out of repo) or a<meta http-equiv>— mechanism unconfirmed. - Never write
_mekari_accountfrom CRM; read presence only.
3.2 Observability (FE)
Reuse @datadog/browser-rum (plugins/datadog-rum.js). Emit:
- RUM custom action
mekari_session.eventwith{ event, matched }. - RUM error on
server_downand onpostMessageorigin mismatch. - Structured
console-free logging via existing RUM (no newconsole.log).
Metric naming follows existing
service: qontak-crm-frontendRUM convention (plugins/datadog-rum.js:9). Backend p95/RPS alerts (PRD §3) are A&L-owned.
3.3 HA / Performance (FE slice)
CRM adds one async, non-blocking SDK init per page; the toggle and msli fallback
ensure CRM degrades gracefully if /sm/current is slow/down (no blocking
render). The 6k RPS / 50ms latency targets (PRD §3) are SM-side, not CRM-FE.
4. Backwards Compatibility and Rollout Plan
4.A Rollout
| Stage | Audience | Go/No-go evidence |
|---|---|---|
| 0. Build behind toggle | none (toggle off) | Plugin + dep merged; centralized_session off everywhere; no behavior change |
| 1. Internal pilot | CRM internal company | Toggle on for 1 company; RUM mekari_session.event flowing; zero sign-out-loop errors |
| 2. Gradual | % of companies | error rate flat; no spike in server_down sign-outs |
| 3. GA | all | toggle default on |
CRM rollout is after the PRD's Launchpad pilot (PRD §4 step 5) — CRM's
domain must be added to the SDK CSP frame-ancestors/origin whitelist by A&L
first.
4.B Backwards compatibility & Verification (pre-merge)
Pre-merge commands (in order, sourced from package.json scripts):
yarn lint:js # eslint . --ext .js,.vue --cache
yarn test # jest test -u
yarn test-file tests/plugins/mekari-session.spec.js
yarn test-file tests/utils/helpers/mekari-session.spec.js
yarn build # nuxt build (verify SDK import resolves)
lint:js,test,test-file(jest),build(nuxt build) verified inpackage.jsonscripts (explorer-confirmed).
4.C Agent Execution Plan
| # | Chunk | Files | Commands | Acceptance criteria (assertable) |
|---|---|---|---|---|
| 1 | Add dependency | package.json (+ yarn.lock) | yarn add @mekari/sdk (pin) | @mekari/sdk in deps; yarn install --frozen-lockfile passes — blocked on §5 Q6 (registry availability) |
| 2 | msli fallback helper | utils/helpers/mekari-session.js (new); tests/utils/helpers/mekari-session.spec.js (new) | yarn test-file tests/utils/helpers/mekari-session.spec.js | Tests: msli<2h+cookie→logged_in; >2h or no cookie→logged_out; missing→server_down |
| 3 | SDK plugin + toggle gate | plugins/mekari-session.js (new); nuxt.config.js (plugins[]); tests/plugins/mekari-session.spec.js (new) | yarn test-file tests/plugins/mekari-session.spec.js; yarn lint:js | Test: toggle off → no Session; on → Session built w/ user_sso_id; event.origin validated |
| 4 | Event handlers | plugins/mekari-session.js; store/user.js (clear msli in deleteUserLocalData) | yarn test | Per-event tests pass (logged_in/out/switch/server_down) per §2.4; msli cleared on logout |
| 5 | Logout → SSO redirect | store/user.js:userLogout (success path) | yarn test-file tests/store/user.spec.js | Test: after sign_out resolves, window.location.href set to account.mekari.com/sign_out |
| 6 | Observability | plugins/mekari-session.js (RUM calls) | yarn lint:js; yarn build | RUM action mekari_session.event emitted in handler unit test (mock datadogRum) |
Chunk order respects deps: helper (2) before plugin (3); plugin before handlers (4); store edits (4,5) tested in isolation.
4.D Verification & Rollback Recipe
Post-deploy signals:
- Datadog RUM action
mekari_session.eventpresent for piloted company. - No spike in CRM sign-out / login redirects vs baseline (RUM
view.error/session count). server_downfallback rate within expected (mekari_session.fallback).
Rollback (numbered, agent-executable):
- Set
centralized_sessiontoggle off for affected company/all (server-sidefeature_enabled) — instant, no deploy. Primary lever. - If code-level revert needed: revert the PR adding
plugins/mekari-session.jsnuxt.config.jsplugin entry; redeploy.
- Confirm CRM auth/logout behaves as pre-RFC (token login works, in-app logout
no longer redirects to
account.mekari.com/sign_out). - Verify RUM session/error counts return to baseline.
5. Concern, Questions, or Known Limitations
| # | Severity | Question / limitation | Blocks |
|---|---|---|---|
| Q1 | [critical] | switch_user proper flow: CRM is token-based, not OAuth2 authz-code, and has no SSO-autologin callback route. Which PRD flow applies to CRM, and what is the SSO-autologin contract for token products? Interim is sign-out+re-login (ADR-5). | §7 yes |
| Q2 | [critical] | Source of user_sso_id in CRM: does /users/me (crmAuthScheme.fetchUser) expose a user SSO id? Only external_company_id (company) verified. SDK input cannot be filled without this. | §7 yes |
| Q3 | [critical] | Exact centralized_session feature code/string in feature_enabled (string code vs CP-QONTAKCRM-YYYY-NNNN). | §7 yes |
| Q4 | [critical] | current_company sync for CRM: no endpoint/field exists; CRM uses teams. Is there a CRM company-context equivalent, and is a BE RFC needed? (ADR-7 deferred) | §7 yes |
| Q5 | [important] | switch_user toast: confirm exact CRM notification component/API (components/_general/ + user-notifications skill) before drafting. | chunk 4 |
| Q6 | [important] | Is @mekari/sdk published to the Mekari/private npm registry CRM can install from? CJS+ESM build confirmed by PRD, registry not. | chunk 1 |
| Q7 | [important] | All existing in-app callers of userLogout — will adding the account.mekari.com/sign_out redirect break any flow expecting to stay in-app (e.g. account-switch, embed layout)? | chunk 5 |
| Q8 | [important] | CSP delivery: CRM has no server/header CSP (nuxt.config.js). CSP vs referrer/origin (PRD open Q) + where the header is set (edge/CDN vs meta). Infosec decision. | §3.1 |
| Q9 | [nice-to-have] | session.refresh() throttle interval ("TBD" in PRD) — define for CRM (per-navigation vs time-based). | — |
6. Comment logs
| Date | Author | Comment |
|---|---|---|
| 2026-06-27 | CRM Frontend (draft) | Initial CRM-FE integration draft grounded against schemes/crmAuthScheme.js, store/user.js, utils/helpers/auth.js, nuxt.config.js. 4 critical unknowns block execution (token model vs authz-code, user_sso_id source, toggle code, current_company). |
7. Ready for agent execution
Ready for agent execution: no
Failing gates:
- B2 / F (contracts): SDK input
user_sso_idunverified in CRM/users/me(Q2). Cannot constructSessioncorrectly. - D2 (decision closure):
switch_userreal flow unresolved for CRM's token model (Q1);current_companydeferred without a BE contract (Q4). - C2 (anti-hallucination):
centralized_sessionexact code not verified (Q3) — left as placeholder rather than invented.
Resolve Q1–Q4 ([critical]) to flip the marker to yes. Chunks 2, 3, 6 (msli
helper, plugin scaffold + toggle gate, observability) are independently
executable today; chunks 1, 4, 5 depend on the critical answers.