214 lines
17 KiB
Markdown
214 lines
17 KiB
Markdown
# Architecture and Compatibility Contracts
|
|
|
|
This document describes the integrated modular implementation. Verification and
|
|
preexisting risks are tracked in [refactoring.md](refactoring.md).
|
|
|
|
## Sources, Outputs, and Runtime Boundaries
|
|
|
|
- Editable sources are factory modules under `src/`; the former monoliths were removed.
|
|
- `scripts/sources.cjs` explicitly lists modules for each context. `compose.cjs`
|
|
validates syntax, serializes CSS, wraps the modules in an IIFE and calls the
|
|
corresponding application factory/start method. `build.mjs` writes two packages.
|
|
- `dist/chromium` serves Chrome and Edge; `dist/firefox` serves Firefox. Do not edit outputs.
|
|
- Both manifests use V3. Chromium has a classic background service worker; Firefox
|
|
uses `background.scripts`. Neither entry point is declared as a module.
|
|
- Content scripts run at `document_idle`, in all matching frames on the two
|
|
configured Fusion welcome pages. Their code also checks the hostname/path.
|
|
- `src/auth/spa-page.js` is a separate top-frame-only SPA content script, copied
|
|
into both distributions. It handles only extension messages for DOM readiness
|
|
inspection and a fixed same-origin identity GET; it sends no requests on its own.
|
|
`services/spa/page-client.js` connects this receiver to the background recovery
|
|
flow and the existing debug envelope. No page-world script is injected.
|
|
- There are no extension UI pages, injected page-context scripts, custom CSP,
|
|
declared execution world, or web-accessible resources.
|
|
- Permissions remain `cookies` and `storage`, with host access limited to the
|
|
configured Fusion host and `spa.oracle.com`.
|
|
|
|
Content owns the DOM and per-frame state. Background owns privileged cookies,
|
|
temporary tabs, extension storage and SPA requests. These contexts communicate
|
|
using serializable runtime messages, not shared variables. Page localStorage is
|
|
different from extension storage and is not a safe place for credentials.
|
|
|
|
## Current Ownership and Side Effects
|
|
|
|
| Context | Responsibilities | State and effects |
|
|
| --- | --- | --- |
|
|
| Content | Fusion tile and main/preferences/consumption modals | DOM nodes, injected CSS, keyboard/pointer/resize listeners, MutationObserver |
|
|
| Content | Opportunity query and presentation | Filter sets, sorting, search, request versions, column formatters, dashboard animation |
|
|
| Content | Products UI | Current request ID, product map, expanded rows, progress, last update |
|
|
| Content | Consumption UI | Plan state, per-plan/period trend cache, chart navigation and selected series |
|
|
| Content | Plan projection details | Per-panel WeakMap, shared in-flight requests, nested tooltip DOM, temporary pointer/resize listeners cleaned up on dismiss/tab change/modal close |
|
|
| Content | Rate card | Pure column/filter/sort model, controller with per-plan in-flight sharing, animated drawer with skeleton/search/table; response guards and cleanup on close or plan switch |
|
|
| Content | Service Requests UI | Customer-specific in-flight queries, skeleton/table modal, stale-response guards; no persistent cache |
|
|
| Content | AI summary | Per-opportunity in-flight state and summary; fresh generation on expansion |
|
|
| Content | Authentication/debug | General and scoped token promises, SPA authorizations, request logs, visibility flag |
|
|
| Background | Message dispatcher | Synchronously registered listener, asynchronous replies using `return true` |
|
|
| Background | Products | Four workers, per-opportunity in-flight map, lazy cache read and serialized writes |
|
|
| Background | Cookie/session recovery | Inactive tabs, polling/retry delays, cleanup in `finally` |
|
|
| Background | SPA operations | HTTP/JSON parsing, account/plan extraction and debug envelopes |
|
|
|
|
Closing the main modal hides it rather than destroying it. Many requests can
|
|
continue while it is hidden. Version and request-ID checks suppress stale UI
|
|
updates; they do not abort network traffic. No AbortController is currently used.
|
|
Some listeners have window guards; there is no general disposal lifecycle.
|
|
|
|
## Authentication and API Routing
|
|
|
|
Never substitute one token family for another or assume an opaque token is a JWT.
|
|
Production tokens are held in memory, not deliberately persisted by the extension.
|
|
|
|
| Credential | Acquisition | Consumers | Lifetime/recovery |
|
|
| --- | --- | --- | --- |
|
|
| XSRF | Background cookie lookup, then content `document.cookie` fallback | `x-xsrf-token` for both tokenrelays | Cookie recovery tab on missing/invalid response; existing retry rules |
|
|
| Fusion general | GET `/fscmRestApi/tokenrelay` | Opportunity POST and product GET Bearer | Content promise/cache; no expiry-based refresh equivalent to Sales Common |
|
|
| Sales Common | GET `/fscmRestApi/tokenrelay?scopesfq=urn%3Aopc%3Aresource%3Afusion%3Aeeho%3Asales-common%2F` | AI summary Bearer | In-memory `expires_in`, shared in-flight acquisition; at most one summary retry after 401/403 |
|
|
| ACCP-TS | SPA consumer service `uiaasHeader.value` | Account summary authorization | Retrieved with the consumer service response; expiry recorded, not automatically acted on |
|
|
| OCICONS-TS | SPA consumer service `uiaasHeader.value` | Account plans, consumption trend, plan detail and rate card authorization | Same service response, distinct credential from ACCP-TS |
|
|
| SPA-ORANGE | SPA consumer service `uiaasHeader.value` | Customer service requests, fixed `custsr-auth` header with raw value, no Bearer prefix; ignores `uiaasHeader.label` | Optional at consumer acquisition; required only when opening Service Requests; existing in-memory session rules |
|
|
|
|
Fusion calls use `https://eeho.fa.us2.oraclecloud.com`; SPA calls use
|
|
`https://spa.oracle.com/oalcrm/web/api/`. Preserve `credentials: include` and
|
|
operation-specific headers rather than unifying all calls under a default token.
|
|
|
|
| Request | Method/path | Important contract |
|
|
| --- | --- | --- |
|
|
| requestOpportunities | POST `/crmRestApi/searchResources/11.13.18.05/custom-actions/queries` | RecordSet OR plus EffectiveDate AND, original fields, Preference transient, pages of 15 |
|
|
| requestOpptyProducts | GET `/crmRestApi/rest/rv:be91c002-2e5d-4ed2-a37e-e0c837bf141f/en/11.13.18.05:9/opportunities/{number}/child/ChildRevenue` | Encoded identifier, original query/fields/order, count/offset pagination, maximum 250 pages |
|
|
| requestGenerateAiSummary | POST `/api/sales-common/gen-ai/v1/summarizations/generate` | `{path,promptCode,object,puid}`; prompt `cx.sales.generate_opportunity_summary`; only summaryText displayed |
|
|
| requestConsumerTokenService | GET `g2m-consumer-application/consumerTokenService` | No-store; parse JSON; HTML 200 recovery via inactive SPA UI tab; maximum five HTTP attempts |
|
|
| requestAccountSummary | POST `provider-proxy/g2m-account/service/accountsummary` | ACCP-TS; `[{key:"regId",value:partyNumber}]` |
|
|
| requestCustomerServiceRequest | GET `provider-proxy/g2m-custsr-provider/service/oscSr/{accountSummaryId}` | SPA-ORANGE; summary `id`, not `registryId`; encoded filter `{hierarchy:"single",status:"open"}`, creationDate descending, pages of 40 using totalResults/count/offset |
|
|
| requestAccountPlans | POST `provider-proxy/g2m-consumption/service/v1/account/accountPlans/{id}` | OCICONS-TS; original hierarchy/products/language/planStatus payload |
|
|
| requestConsumptionService{Period} | GET `provider-proxy/g2m-consumption/service/v1/plan/trend/{subPlanNum}?quarter=All&timePeriod={period}` | G2m-Authorization; quarterly/monthly/weekly/daily; weekly default |
|
|
| requestConsumptionPlanDetail | GET `provider-proxy/g2m-consumption/service/v1/plan/plandetail/{subPlanNum}?active=true` | Raw OCICONS-TS in G2m-Authorization; array of lines with optional nested clines; no retry or persisted cache |
|
|
| requestRatecardByPlan | POST `provider-proxy/g2m-consumption/service/v1/ratecard/byPlan` | `{planNumber,recordsSize:10000}`, where planNumber is the selected plan's subPlanNum; raw OCICONS-TS in fixed G2m-Authorization; response must be an array of records; no retries, cursor pagination or persistent cache |
|
|
|
|
SPA parsing removes BOM and the existing anti-JSON-hijacking prefixes before
|
|
JSON.parse. Content uses response.json directly. Do not silently unify these behaviors.
|
|
POST retry policies require operation-specific review; do not add generic retries.
|
|
|
|
## Message Contracts
|
|
|
|
All names below have the `opportunitiesExtension.` prefix. Unknown messages return
|
|
false without a response. Supported background messages return true immediately
|
|
and later call sendResponse. `platform/background-api.js` centralizes the existing
|
|
callback/promise differences. `platform/content-messages.js` retains the original
|
|
cookie callback path as well as general runtime messaging; no browser behavior
|
|
was silently changed during extraction.
|
|
|
|
| Message | Input | Result |
|
|
| --- | --- | --- |
|
|
| getXsrfToken | type | ok, cookieName, token, matchedCookieNames, lookupDetails; error on failure |
|
|
| refreshXsrfCookie | type; sender tab context | ok, refreshed, cookieName, durationMs, error |
|
|
| requestOpptyProducts | requestId, accessToken, optyNumbers, cacheMaxAgeMs | ok, requestId, completed, total, failed, cacheHits; error envelope on dispatcher failure |
|
|
| opptyProductsProgress | Background to sender tab/frame | requestId, state, counters; per-item optyNumber/items/fromCache/cachedAt or error |
|
|
| requestConsumerTokenService | type; sender tab context | ok, authorizations.accountSummary/accountPlans/installBase/customerServiceRequests, debugRequests |
|
|
| requestCustomerServiceRequest | partyNumber, authorizations.accountSummary/customerServiceRequests | ok, items (consolidated response.objects), debugRequests; error envelope on failure |
|
|
| requestAccountPlans | partyNumber, authorizations | ok, accountPlanId, plans, debugRequests |
|
|
| requestConsumptionTrend | subPlanNum, period, authorization | ok, subPlanNum, period, items, debugRequests |
|
|
| requestConsumptionPlanDetail | subPlanNum, authorization | ok, subPlanNum, items (including clines), debugRequests; error envelope on failure |
|
|
| requestRatecardByPlan | planNumber, authorization | ok, planNumber, items, debugRequests; error envelope on failure |
|
|
|
|
SPA failures return `{ok:false,error,debugRequests}`. Progress states include
|
|
`running`, `complete` and `complete-with-errors`; content additionally tracks idle
|
|
and error states. A new content request ID rejects progress from an older job.
|
|
Do not expose a caller-provided arbitrary request URL through the background.
|
|
|
|
## Storage Contracts
|
|
|
|
| Location/key | Value |
|
|
| --- | --- |
|
|
| Page localStorage: `opportunities-extension-filter-preferences` | JSON object: period, stages, statuses, customerKeys, ownerKeys, customerSearch, ownerSearch, tableSearch; unset customer/owner selections may be null |
|
|
| Page localStorage: `opportunities-extension-theme` | `light` or `dark` |
|
|
| Page localStorage: `opportunities-extension-opportunity-type-preferences` | JSON object: opportunityTypeValues, opptyProductsCacheHours |
|
|
| Extension storage.local: `opportunitiesExtension.opptyProductsCache.v1` | Object indexed by OptyNumber, each entry `{fetchedAt,items}` |
|
|
|
|
Product TTL defaults to 24 hours and is capped at 30 days in background. Storage
|
|
failures are tolerated for the active view. Recreating background reloads stored
|
|
products but does not restore in-flight jobs. Cache TTL does not physically delete
|
|
expired entries. Account/container isolation limitations are tracked separately.
|
|
|
|
## UI Contracts
|
|
|
|
Preserve labels/IDs/classes, column order, URLs, clipboard behavior, sort/filter
|
|
semantics, formatting, theme colors, keyboard shortcuts and row expansion.
|
|
WON includes WON_PENDING without rewriting StatusCode. Owner uses first/last name
|
|
with the existing fallbacks. Changing reference resets customers/owners to all;
|
|
closing/reopening preserves search. Fiscal years begin in June; RENEWALS currently
|
|
has no date range and therefore does not load opportunities.
|
|
|
|
Preserve SVG chart data normalization, animation, tooltips and series selection.
|
|
AI text is inserted with textContent. Debug masking and DOM isolation have known
|
|
limitations; see the separate security backlog rather than assuming full redaction.
|
|
|
|
## Module Ownership and Dependencies
|
|
|
|
`content/main.js` and `background/main.js` only initialize. Each `application.js`
|
|
is a composition root: constructs feature-owned state and factories, wires explicit
|
|
function dependencies, then exposes `start`. The returned components are available
|
|
to isolated tests, not published on the page/global object in production.
|
|
|
|
Factories do not import or instantiate peers. Features receive service/auth ports;
|
|
services/auth receive transport/platform ports; pure models receive data. The
|
|
composition roots supply callback notifications for UI updates and debug events,
|
|
preserving existing interactions without module-import cycles. Shared messages
|
|
are the only source included in both contexts, with distinct instances/state.
|
|
Some stateful UI collaborators intentionally share a feature-owned state object;
|
|
these objects are created per application, never stored in a global registry.
|
|
|
|
| Original responsibility | Integrated module(s) | Reason for boundary |
|
|
| --- | --- | --- |
|
|
| content initialization/tile | `content/main.js`, `fusion-page.js` | Page lifecycle and route guard |
|
|
| modal/theme/CSS | `content/modal.js`, `theme.js`, `styles.js`, `styles/redwood.css` | DOM shell separate from operations and cascade |
|
|
| fiscal dates/query payload | `features/opportunities/fiscal-periods.js`, `services/fusion/opportunities.js` | Pure date rules vs HTTP operation |
|
|
| table/search/filters/formatting/dashboard | `features/opportunities/` | Related presentation responsibilities, explicit state |
|
|
| saved settings | `features/preferences/storage.js`, `view.js` | Serialization/fallbacks vs dialog |
|
|
| token families | `auth/content-session.js`, `sales-common.js`, `token-relay.js`, `spa-credentials.js` | Different credentials and lifetime policies |
|
|
| cookie lookup/recovery, SPA session | `auth/xsrf-*.js`, `consumer-session.js` | Privileged recovery separate from UI |
|
|
| product queue/cache | `features/products/background-jobs.js`, `cache.js`, `services/fusion/products.js` | Scheduling, persistence and pagination |
|
|
| product progress/details | `features/products/content-jobs.js`, `details.js` | Message consumer vs row rendering |
|
|
| AI summary | `features/ai-summary/content.js`, `state.js` | Per-opportunity lifecycle, safe text and auth port |
|
|
| consumption | `features/consumption/`, `services/spa/` | Plans, chart model/view/controller vs HTTP operations |
|
|
| debug | `features/debug/content-log.js`, `content-view.js` | Retention/formatting vs DOM presentation |
|
|
| background dispatcher | `background/messages.js`, `shared/messages.js` | Stable serializable message contracts |
|
|
| browser APIs | `platform/background-api.js`, `content-messages.js` | Only APIs actually used, not a universal wrapper |
|
|
|
|
The background composition deliberately has no DOM dependency. Listener registration
|
|
is synchronous, before any storage read or request. Worker restart reconstructs
|
|
volatile state and lazily reads the existing product cache; tokens are not persisted.
|
|
Content effects (DOM, fetch, storage, animation) enter through the environment at
|
|
the composition root. localStorage access stays lazy to retain blocked-storage
|
|
fallbacks. Tests may inject effects or individual callback ports without changing
|
|
source strings. Time-sensitive modules are tested with isolated clocks.
|
|
|
|
## Adding a Feature
|
|
|
|
1. Define its message/API/storage contract and synthetic expected behavior first.
|
|
2. Add a factory under the owning feature, or a service under the correct host.
|
|
Accept narrow function dependencies; do not call another factory inside it.
|
|
3. Add its source path to the appropriate array in `scripts/sources.cjs`. Source
|
|
inclusion and duplicate factory checks run automatically.
|
|
4. Construct it in that context's `application.js`, supplying auth, platform and
|
|
render callbacks explicitly. Extend `background/messages.js` only if crossing
|
|
contexts is necessary; do not expose arbitrary destinations.
|
|
5. Add a `tests/*.test.cjs` suite using `tests/support/application.cjs` for composed
|
|
integration or `vm.Script` for an individual pure factory. Use only synthetic
|
|
data and fail unexpected network requests.
|
|
6. Run tests, build and `--check`; inspect the local fixture and load both packages
|
|
in the target browsers. Record any browser not available for verification.
|
|
|
|
As a concrete example, adding a new consumption period should first define its
|
|
request/data/authorization contract and synthetic tests, then extend the period
|
|
allowlist and normalization/presentation through the existing trend message. It
|
|
must not add a URL-proxy message or reuse the general Fusion token. These edits
|
|
belong in the consumption feature and SPA service, not the bootstrap.
|
|
|
|
## Source vs Generated Code
|
|
|
|
Distribution `content.js` and `background.js` are generated bundles and may remain
|
|
large. No copy/export/execute JavaScript generator feature was found in the source.
|
|
AI summaries are text, not executable JavaScript. No empty generators subsystem
|
|
was added. Build serialization uses JSON.stringify for CSS, retains quotes,
|
|
backslashes, Unicode and line breaks, and is syntax-checked by vm.Script. Visual
|
|
fixtures now load composed scripts normally; they no longer eval rewritten sources.
|