@webex/contact-center 3.12.0-next.83 → 3.12.0-next.85
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.sdd/manifest.json +876 -0
- package/AGENTS.md +65 -409
- package/ai-docs/ARCHITECTURE.md +168 -0
- package/ai-docs/CONTRACTS.md +46 -0
- package/ai-docs/GETTING_STARTED.md +168 -0
- package/ai-docs/GLOSSARY.md +43 -0
- package/ai-docs/README.md +25 -18
- package/ai-docs/REVIEW_CHECKLIST.md +41 -0
- package/ai-docs/RULES.md +187 -198
- package/ai-docs/SECURITY.md +52 -0
- package/ai-docs/SERVICE_STATE.md +48 -0
- package/ai-docs/SPEC_INDEX.md +65 -0
- package/ai-docs/adr/0001-spec-source-policy.md +55 -0
- package/ai-docs/adr/README.md +8 -0
- package/ai-docs/adr/_adr-template.md +31 -0
- package/ai-docs/contact-center-spec.md +341 -0
- package/ai-docs/features/generated-spec-conformance-fidelity-remediation/spec/feature-spec.md +117 -0
- package/ai-docs/features/residual-warning-coverage-completion/spec/feature-spec.md +203 -0
- package/ai-docs/features/validator-code-fidelity-drift-fix/spec/feature-spec.md +315 -0
- package/ai-docs/templates/README.md +1 -1
- package/ai-docs/templates/new-method/04-validation.md +1 -1
- package/ai-docs/templates/new-service/05-validation.md +1 -1
- package/dist/webex.js +1 -1
- package/package.json +8 -8
- package/src/metrics/ai-docs/AGENTS.md +2 -0
- package/src/metrics/ai-docs/ARCHITECTURE.md +2 -0
- package/src/metrics/ai-docs/metrics-spec.md +854 -0
- package/src/services/agent/ai-docs/AGENTS.md +3 -1
- package/src/services/agent/ai-docs/ARCHITECTURE.md +2 -0
- package/src/services/agent/ai-docs/agent-spec.md +504 -0
- package/src/services/ai-docs/AGENTS.md +4 -2
- package/src/services/ai-docs/services-spec.md +492 -0
- package/src/services/config/ai-docs/AGENTS.md +3 -1
- package/src/services/config/ai-docs/ARCHITECTURE.md +2 -0
- package/src/services/config/ai-docs/config-spec.md +669 -0
- package/src/services/core/ai-docs/AGENTS.md +4 -2
- package/src/services/core/ai-docs/ARCHITECTURE.md +2 -0
- package/src/services/core/ai-docs/core-spec.md +783 -0
- package/src/services/task/ai-docs/AGENTS.md +2 -0
- package/src/services/task/ai-docs/ARCHITECTURE.md +11 -2
- package/src/services/task/ai-docs/task-spec.md +1319 -0
- package/src/services/task/state-machine/ai-docs/AGENTS.md +38 -75
- package/src/services/task/state-machine/ai-docs/ARCHITECTURE.md +6 -4
- package/src/services/task/state-machine/ai-docs/task-state-machine-spec.md +2115 -0
- package/src/utils/AGENTS.md +3 -1
- package/src/utils/ai-docs/utils-spec.md +381 -0
- package/umd/contact-center.min.js +2 -2
- package/umd/contact-center.min.js.map +1 -1
|
@@ -0,0 +1,783 @@
|
|
|
1
|
+
# Core — SPEC
|
|
2
|
+
|
|
3
|
+
> Start here → root [`AGENTS.md`](../../../../AGENTS.md) · router [`SPEC_INDEX.md`](../../../../ai-docs/SPEC_INDEX.md) · system [`ARCHITECTURE.md`](../../../../ai-docs/ARCHITECTURE.md). This is the module's canonical specification.
|
|
4
|
+
|
|
5
|
+
## Metadata
|
|
6
|
+
|
|
7
|
+
| Field | Value |
|
|
8
|
+
|---|---|
|
|
9
|
+
| Module id | `core` |
|
|
10
|
+
| Source path(s) | `src/services/core` |
|
|
11
|
+
| Doc kind | Module spec |
|
|
12
|
+
| Coverage score | Partial (manifest-authoritative); 15/15 required document fields present |
|
|
13
|
+
| Generated from | `module-spec` @ SDLC template library `0.2.1` |
|
|
14
|
+
| generated_by / approved_by / updated_at | Codex generator / developer-approved follow-up review remediation / 2026-07-21 |
|
|
15
|
+
| Validation status | Follow-up validation passed (independent Claude fallback, 2026-07-21); coverage remains Partial |
|
|
16
|
+
|
|
17
|
+
## Evidence Rules
|
|
18
|
+
Every requirement cites stable source and test file paths. Code/tests are the behavioral referee; routed source text supplies explicit intent and rationale. Missing or contradictory evidence blocks promotion.
|
|
19
|
+
|
|
20
|
+
## Source Material Register
|
|
21
|
+
| Source material | Scope | Decision | Detail location or disposition |
|
|
22
|
+
|---|---|---|---|
|
|
23
|
+
| Reviewed prior module guides and architecture material | overview / architecture / API / tests | used and code-checked | Content is placed by meaning throughout this specification; exact routing remains in the manifest. |
|
|
24
|
+
|
|
25
|
+
## Overview
|
|
26
|
+
Core is one of nine confirmed Contact Center SDK modules. Own authenticated HTTP, realtime WebSocket lifecycle, AQM request correlation, reconnect/keepalive behavior, and shared error normalization. Existing reviewed documentation is migrated by meaning and code/tests remain the behavioral referee.
|
|
27
|
+
|
|
28
|
+
The Core service provides the foundational infrastructure layer that all other services depend on:
|
|
29
|
+
|
|
30
|
+
- **WebSocket Communication**: Real-time bidirectional messaging with the contact center backend, including automatic reconnection and keepalive management
|
|
31
|
+
|
|
32
|
+
- **HTTP Request Handling**: Authenticated REST API calls to WCC API Gateway with built-in error handling and log upload support
|
|
33
|
+
|
|
34
|
+
- **AQM Request/Response Pattern**: A structured pattern used by the routing and contact layers to send HTTP requests to the contact center backend and correlate responses/failures via WebSocket notifications
|
|
35
|
+
|
|
36
|
+
- **Error Handling & Logging**: Standardized error extraction, logging via `LoggerProxy`, and log upload utilities that all services use for consistent error reporting
|
|
37
|
+
|
|
38
|
+
| Component | File | Description |
|
|
39
|
+
|---|---|---|
|
|
40
|
+
| `WebSocketManager` | [`WebSocketManager.ts`](../websocket/WebSocketManager.ts) | Manages the WebSocket connection lifecycle including initialization, message dispatch, and graceful shutdown. Emits `message` events for incoming data and `socketClose` when the connection drops while reconnect is allowed. |
|
|
41
|
+
| `ConnectionService` | [`connection-service.ts`](../websocket/connection-service.ts) | Orchestrates reconnection logic and keepalive heartbeats on top of `WebSocketManager`. Detects connection loss/recovery and emits `connectionLost` details; ContactCenter owns any `silentRelogin()` policy. |
|
|
42
|
+
| `WebexRequest` | [`WebexRequest.ts`](../WebexRequest.ts) | Singleton HTTP client that forwards service/resource/method/body options to the authenticated host request API and provides `uploadLogs` diagnostics. |
|
|
43
|
+
| `AqmReqs` | [`aqm-reqs.ts`](../aqm-reqs.ts) | Factory for creating request methods that send HTTP requests and wait for correlated WebSocket notifications (success or failure). Used by routing and task services to implement their API methods. |
|
|
44
|
+
| `Utils` | [`Utils.ts`](../Utils.ts) | Shared utility functions including `getErrorDetails()` for standardized error handling, `generateTaskErrorObject()` for task-specific errors, and `createErrDetailsObject()` for constructing error detail objects. |
|
|
45
|
+
| `Err` | [`Err.ts`](../Err.ts) | Error class definitions. `Err.Details` carries structured error metadata (status, type, trackingId) for consistent error propagation. |
|
|
46
|
+
| `constants` | [`constants.ts`](../constants.ts) | Timeout values, interval durations, participant types, interaction states, and method name constants used throughout the core layer. Any new constants for core should be defined here. |
|
|
47
|
+
|
|
48
|
+
## Purpose / Responsibility
|
|
49
|
+
Own authenticated HTTP, realtime WebSocket lifecycle, AQM request correlation, reconnect/keepalive behavior, and shared error normalization.
|
|
50
|
+
|
|
51
|
+
## Stack
|
|
52
|
+
TypeScript/JavaScript, WebSocket, Web Worker keepalive, EventEmitter, Webex request client, Jest 27.
|
|
53
|
+
|
|
54
|
+
## Folder / Package Structure
|
|
55
|
+
```text
|
|
56
|
+
src/services/core/
|
|
57
|
+
├── Err.ts
|
|
58
|
+
├── GlobalTypes.ts
|
|
59
|
+
├── Utils.ts
|
|
60
|
+
├── WebexRequest.ts
|
|
61
|
+
├── aqm-reqs.ts
|
|
62
|
+
├── constants.ts
|
|
63
|
+
├── types.ts
|
|
64
|
+
├── websocket/
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
```text
|
|
68
|
+
services/core/
|
|
69
|
+
├── aqm-reqs.ts # AQM request handler
|
|
70
|
+
├── constants.ts # Core constants
|
|
71
|
+
├── Err.ts # Error classes
|
|
72
|
+
├── GlobalTypes.ts # Failure, Msg<T>, etc.
|
|
73
|
+
├── types.ts # Request/response types
|
|
74
|
+
├── Utils.ts # Utility functions
|
|
75
|
+
├── WebexRequest.ts # HTTP client
|
|
76
|
+
└── websocket/
|
|
77
|
+
├── WebSocketManager.ts # Main WS handler
|
|
78
|
+
├── connection-service.ts # Connection lifecycle
|
|
79
|
+
├── keepalive.worker.js # Keepalive worker
|
|
80
|
+
└── types.ts # WS types
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Key Files (source of truth)
|
|
84
|
+
| File | Holds |
|
|
85
|
+
|---|---|
|
|
86
|
+
| `src/services/core/WebexRequest.ts` | Authoritative Core implementation or contract source. |
|
|
87
|
+
| `src/services/core/aqm-reqs.ts` | Authoritative Core implementation or contract source. |
|
|
88
|
+
| `src/services/core/Utils.ts` | Authoritative Core implementation or contract source. |
|
|
89
|
+
| `src/services/core/Err.ts` | Authoritative Core implementation or contract source. |
|
|
90
|
+
| `src/services/core/websocket/WebSocketManager.ts` | Authoritative Core implementation or contract source. |
|
|
91
|
+
| `src/services/core/websocket/connection-service.ts` | Authoritative Core implementation or contract source. |
|
|
92
|
+
|
|
93
|
+
## Public Surface
|
|
94
|
+
| Contract ID | Type | Surface | Purpose | Compatibility / deprecation | Schema / detail link | Root index |
|
|
95
|
+
|---|---|---|---|---|---|---|
|
|
96
|
+
| `core.surface` | SDK / event / internal API | Internal WebexRequest, WebSocketManager, ConnectionService, AqmReqs, and shared error/request types and helpers. | Stable module consumption boundary. | Additive changes by default; breaking package exports require a major-version transition. | `src/services/core/WebexRequest.ts` | `../../../../ai-docs/CONTRACTS.md` |
|
|
97
|
+
|
|
98
|
+
Compatibility notes:
|
|
99
|
+
- Do not remove or reinterpret exported symbols/events without a documented consumer migration.
|
|
100
|
+
|
|
101
|
+
| Event | Data | Description |
|
|
102
|
+
|---|---|---|
|
|
103
|
+
| `message` | string (JSON) | WebSocket message received |
|
|
104
|
+
| `socketClose` | - | Socket closed while reconnect is allowed |
|
|
105
|
+
|
|
106
|
+
Generic message wrapper used throughout the SDK. All WebSocket messages and AQM responses conform to this shape:
|
|
107
|
+
|
|
108
|
+
```typescript
|
|
109
|
+
export type Msg<T = any> = {
|
|
110
|
+
type: string; // Message/Event type identifier
|
|
111
|
+
orgId: string; // Organization identifier
|
|
112
|
+
trackingId: string; // Unique tracking identifier
|
|
113
|
+
data: T; // Message/Event payload data
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
// Usage — the payload type goes into `data`
|
|
117
|
+
export type LoginSuccess = Msg<{
|
|
118
|
+
agentId: string;
|
|
119
|
+
status: string;
|
|
120
|
+
// ...
|
|
121
|
+
}>;
|
|
122
|
+
// Resulting shape: { type, orgId, trackingId, data: { agentId, status, ... } }
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### ConnectionService internal surface — not public contracts
|
|
126
|
+
|
|
127
|
+
The only public method in the following list is `setConnectionProp`. The remaining methods are private implementation details documented for ownership and maintenance; they are not public API contracts.
|
|
128
|
+
|
|
129
|
+
1. `setConnectionProp(prop: ConnectionProp): void` (public)
|
|
130
|
+
|
|
131
|
+
- **Purpose**: Updates connection-level runtime settings used by timers (mainly recovery timeout behavior).
|
|
132
|
+
|
|
133
|
+
- **Params**: `prop` - new connection config object.
|
|
134
|
+
|
|
135
|
+
- **Returns**: `void`.
|
|
136
|
+
|
|
137
|
+
- **Usage**: Called by higher layers when timeout behavior must be tuned after initialization.
|
|
138
|
+
|
|
139
|
+
2. `setupEventListeners(): void` (private)
|
|
140
|
+
|
|
141
|
+
- **Purpose**: Wires `WebSocketManager` events to internal handlers (`'message' -> onPing`, `'socketClose' -> onSocketClose`).
|
|
142
|
+
|
|
143
|
+
- **Params**: none.
|
|
144
|
+
|
|
145
|
+
- **Returns**: `void`.
|
|
146
|
+
|
|
147
|
+
- **Usage**: Invoked from the constructor once, during `ConnectionService` setup.
|
|
148
|
+
|
|
149
|
+
3. `onPing(event: any): void` (private)
|
|
150
|
+
|
|
151
|
+
- **Purpose**: Handles every incoming socket message, resets timers, updates keepalive/recovery flags, and emits recovery events when state changes.
|
|
152
|
+
|
|
153
|
+
- **Params**: `event` - raw message payload (JSON string) received from the socket event stream.
|
|
154
|
+
|
|
155
|
+
- **Returns**: `void`.
|
|
156
|
+
|
|
157
|
+
- **Usage**: Triggered automatically by the `'message'` listener.
|
|
158
|
+
|
|
159
|
+
4. `onSocketClose(): void` (private)
|
|
160
|
+
|
|
161
|
+
- **Purpose**: Starts the reconnect interval when socket close is detected.
|
|
162
|
+
|
|
163
|
+
- **Params**: none.
|
|
164
|
+
|
|
165
|
+
- **Returns**: `void`.
|
|
166
|
+
|
|
167
|
+
- **Usage**: Triggered automatically by the `'socketClose'` listener.
|
|
168
|
+
|
|
169
|
+
5. `handleSocketClose(): Promise<void>` (private)
|
|
170
|
+
|
|
171
|
+
- **Purpose**: Performs one reconnect attempt; if browser is online, reinitializes WebSocket and marks socket as reconnected.
|
|
172
|
+
|
|
173
|
+
- **Params**: none.
|
|
174
|
+
|
|
175
|
+
- **Returns**: `Promise<void>` (rejects when browser is offline).
|
|
176
|
+
|
|
177
|
+
- **Usage**: Called repeatedly from `onSocketClose` interval loop.
|
|
178
|
+
|
|
179
|
+
6. `handleConnectionLost(): void` (private)
|
|
180
|
+
|
|
181
|
+
- **Purpose**: Marks the connection as lost and dispatches a connection status event.
|
|
182
|
+
|
|
183
|
+
- **Params**: none.
|
|
184
|
+
|
|
185
|
+
- **Returns**: `void`.
|
|
186
|
+
|
|
187
|
+
- **Usage**: Scheduled by `onPing` via `reconnectingTimer` after inactivity.
|
|
188
|
+
|
|
189
|
+
7. `handleRestoreFailed(): Promise<void>` (private)
|
|
190
|
+
|
|
191
|
+
- **Purpose**: Marks restore as failed, disables reconnect, emits failure state, and clears reconnect interval.
|
|
192
|
+
|
|
193
|
+
- **Params**: none.
|
|
194
|
+
|
|
195
|
+
- **Returns**: `Promise<void>`.
|
|
196
|
+
|
|
197
|
+
- **Usage**: Scheduled by `onPing` via `restoreTimer`.
|
|
198
|
+
|
|
199
|
+
8. `clearTimerOnRestoreFailed(): Promise<void>` (private)
|
|
200
|
+
|
|
201
|
+
- **Purpose**: Stops active reconnect interval to avoid duplicate retries.
|
|
202
|
+
|
|
203
|
+
- **Params**: none.
|
|
204
|
+
|
|
205
|
+
- **Returns**: `Promise<void>`.
|
|
206
|
+
|
|
207
|
+
- **Usage**: Called from reconnect/failure paths whenever interval cleanup is needed.
|
|
208
|
+
|
|
209
|
+
9. `updateConnectionData(): void` (private)
|
|
210
|
+
|
|
211
|
+
- **Purpose**: Resets transient connection flags (`isConnectionLost`, `isRestoreFailed`, `isSocketReconnected`) after recovery.
|
|
212
|
+
|
|
213
|
+
- **Params**: none.
|
|
214
|
+
|
|
215
|
+
- **Returns**: `void`.
|
|
216
|
+
|
|
217
|
+
- **Usage**: Called inside `onPing` before dispatching recovered state.
|
|
218
|
+
|
|
219
|
+
10. `dispatchConnectionEvent(socketReconnected = false): void` (private)
|
|
220
|
+
|
|
221
|
+
- **Purpose**: Builds `ConnectionLostDetails`, forwards it to `WebSocketManager.handleConnectionLost`, and emits `'connectionLost'`.
|
|
222
|
+
|
|
223
|
+
- **Params**: `socketReconnected` - optional override used when reconnect is explicitly detected.
|
|
224
|
+
|
|
225
|
+
- **Returns**: `void`.
|
|
226
|
+
|
|
227
|
+
- **Usage**: Used by lost/recovered/restore-failed paths to publish uniform connection state.
|
|
228
|
+
|
|
229
|
+
```typescript
|
|
230
|
+
type ConnectionLostDetails = {
|
|
231
|
+
isConnectionLost: boolean;
|
|
232
|
+
isRestoreFailed: boolean;
|
|
233
|
+
isSocketReconnected: boolean;
|
|
234
|
+
isKeepAlive: boolean;
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
connectionService.on('connectionLost', (details: ConnectionLostDetails) => {
|
|
238
|
+
if (details.isConnectionLost) {
|
|
239
|
+
// Connection lost — waiting for recovery
|
|
240
|
+
} else if (details.isRestoreFailed) {
|
|
241
|
+
// Recovery timeout (50 s) exceeded
|
|
242
|
+
} else if (details.isSocketReconnected) {
|
|
243
|
+
// Socket successfully reconnected
|
|
244
|
+
}
|
|
245
|
+
});
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
## Requires (dependencies)
|
|
249
|
+
- Webex SDK service catalog and authenticated request API
|
|
250
|
+
- Browser WebSocket, Worker, URL and network-status APIs
|
|
251
|
+
- LoggerProxy and log upload
|
|
252
|
+
|
|
253
|
+
## Requirements
|
|
254
|
+
| ID | WHAT | WHY | Source Evidence | Test / Example Evidence | Assumptions / Gaps | Confidence |
|
|
255
|
+
|---|---|---|---|---|---|---|
|
|
256
|
+
| CORE-R-001 | WebexRequest must delegate service/resource/method/body options to the authenticated host request API and return or reject with the host result unchanged. | All Contact Center REST clients share host-owned authentication and service routing without duplicating credential logic. | `src/services/core/WebexRequest.ts` | `test/unit/spec/services/core/WebexRequest.ts` | Authorization-header masking belongs to `AqmReqs` HTTP-failure handling, not `WebexRequest.request`. | PRESENT |
|
|
257
|
+
| CORE-R-002 | `initWebSocket` requires `{body: SubscribeRequest, resource: string}` and resolves only after WebSocket welcome or rejects on register/connect failure. | Subscription resource selection and readiness are required for a valid realtime session. | `src/services/core/websocket/WebSocketManager.ts` | `test/unit/spec/services/core/websocket/WebSocketManager.ts` | None; source and test evidence rechecked during the 2026-07-09 remediation; independent document revalidation pending. | PRESENT |
|
|
258
|
+
| CORE-R-003 | AqmReqs must be constructed with the primary WebSocket manager and settle generated request promises from `notifSuccess`/`notifFail` binds or `TIMEOUT_REQ`. | HTTP acknowledgement alone does not represent backend operation completion. | `src/services/core/aqm-reqs.ts` | `test/unit/spec/services/core/aqm-reqs.ts` | None; source and test evidence rechecked during the 2026-07-09 remediation; independent document revalidation pending. | PRESENT |
|
|
259
|
+
| CORE-R-004 | ConnectionService must emit transport-state details and retry `initWebSocket({body, resource})`; ContactCenter owns relogin policy. | Separating transport detection from agent recovery prevents Core from mutating package-level session state. | `src/services/core/websocket/connection-service.ts` | `test/unit/spec/services/core/websocket/connection-service.ts` | None; source and test evidence rechecked during the 2026-07-09 remediation; independent document revalidation pending. | PRESENT |
|
|
260
|
+
| CORE-R-005 | The keepalive worker must use the configured 4-second interval and 16-second close-socket timeout; AQM defaults to 20 seconds unless disabled/overridden. | Accurate timing is required for predictable recovery and request failure behavior. | `src/services/core/constants.ts` | `test/unit/spec/services/core/websocket/WebSocketManager.ts` | None; source and test evidence rechecked during the 2026-07-09 remediation; independent document revalidation pending. | PRESENT |
|
|
261
|
+
| CORE-R-006 | Treat Core timeout, keepalive, and recovery constants as fixed behavior controls, not rollout flags; Core owns no feature-gate evaluation. | Conflating operational constants with rollout policy could disable transport or correlation paths unexpectedly. | `src/services/core/constants.ts`, `src/services/index.ts` | `test/unit/spec/services/core/websocket/WebSocketManager.ts` | None; rollout applicability is explicitly N/A for Core. | PRESENT |
|
|
262
|
+
|
|
263
|
+
## Design Overview
|
|
264
|
+
Core separates four responsibilities:
|
|
265
|
+
|
|
266
|
+
1. `WebexRequest` wraps the authenticated host request API and service-catalog routing.
|
|
267
|
+
2. `WebSocketManager` registers a subscription using both `body` and `resource`, connects the socket, owns the keepalive worker, and emits raw messages/socket lifecycle events.
|
|
268
|
+
3. `AqmReqs` registers bind matchers on the primary WebSocket, sends HTTP through WebexRequest, and settles requests only from matching notifications, HTTP failure, or timeout.
|
|
269
|
+
4. `ConnectionService` observes message/socket liveness, emits connection-state details, and retries socket initialization. ContactCenter listens to those details and owns optional silent relogin.
|
|
270
|
+
|
|
271
|
+
```typescript
|
|
272
|
+
const aqmReqs = new AqmReqs(webSocketManager);
|
|
273
|
+
const setState = aqmReqs.req((p: {data: Agent.StateChange}) => ({
|
|
274
|
+
host: WCC_API_GATEWAY,
|
|
275
|
+
url: '/v1/agents/session/state',
|
|
276
|
+
data: p.data,
|
|
277
|
+
method: HTTP_METHODS.PUT,
|
|
278
|
+
notifSuccess: {
|
|
279
|
+
bind: {
|
|
280
|
+
type: CC_EVENTS.AGENT_STATE_CHANGE,
|
|
281
|
+
data: {type: CC_EVENTS.AGENT_STATE_CHANGE_SUCCESS},
|
|
282
|
+
},
|
|
283
|
+
msg: {} as Agent.StateChangeSuccess,
|
|
284
|
+
},
|
|
285
|
+
notifFail: {
|
|
286
|
+
bind: {
|
|
287
|
+
type: CC_EVENTS.AGENT_STATE_CHANGE,
|
|
288
|
+
data: {type: CC_EVENTS.AGENT_STATE_CHANGE_FAILED},
|
|
289
|
+
},
|
|
290
|
+
errId: 'Service.aqm.agent.stateChange',
|
|
291
|
+
},
|
|
292
|
+
}));
|
|
293
|
+
await setState({data: stateChangePayload});
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
`CLOSE_SOCKET_TIMEOUT` is 16000 ms. `CONNECTIVITY_CHECK_INTERVAL` drives reconnect attempts separately. `TIMEOUT_REQ` is the 20000 ms default AQM timeout; `WEBSOCKET_EVENT_TIMEOUT` is not the active AqmReqs default.
|
|
297
|
+
|
|
298
|
+
## Data Flow
|
|
299
|
+
```mermaid
|
|
300
|
+
flowchart LR
|
|
301
|
+
Service[Agent/contact/dialer request] --> AQM[AqmReqs generated function]
|
|
302
|
+
AQM --> Bind[Register success/failure bind matchers]
|
|
303
|
+
AQM --> WR[WebexRequest authenticated HTTP]
|
|
304
|
+
WR --> Backend[WCC backend]
|
|
305
|
+
Backend --> Ack[HTTP acknowledgement]
|
|
306
|
+
Backend --> WS[Primary WebSocket notification]
|
|
307
|
+
WS --> Match{Bind matches?}
|
|
308
|
+
Match -->|success| Resolve[Resolve typed response]
|
|
309
|
+
Match -->|failure| Reject[Reject structured error]
|
|
310
|
+
Bind -->|TIMEOUT_REQ| Reject
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
```mermaid
|
|
314
|
+
flowchart TD
|
|
315
|
+
Message[WebSocket message] --> WSM[WebSocketManager emits message]
|
|
316
|
+
WSM --> CS[ConnectionService resets liveness timers]
|
|
317
|
+
CS --> Lost{Lost/recovered state changed?}
|
|
318
|
+
Lost -->|yes| Emit[Emit connectionLost details]
|
|
319
|
+
Emit --> CC[ContactCenter recovery policy]
|
|
320
|
+
SocketClose[Socket close] --> Retry[Retry every CONNECTIVITY_CHECK_INTERVAL]
|
|
321
|
+
Retry --> Init[initWebSocket body + SUBSCRIBE_API resource]
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
## Sequence Diagram(s)
|
|
325
|
+
Sequence coverage:
|
|
326
|
+
|
|
327
|
+
| Operation group | Diagram | Failure / recovery coverage |
|
|
328
|
+
|---|---|---|
|
|
329
|
+
| Authenticated REST | WebexRequest | Host/service rejection is propagated unchanged; AQM applies its own error mapping separately. |
|
|
330
|
+
| WebSocket subscribe/connect | Subscribe and connect | Register/connect rejection and welcome resolution. |
|
|
331
|
+
| AQM operation | Correlated request | Duplicate pending request, HTTP failure, failure bind, and timeout reject. |
|
|
332
|
+
| Reconnect | Connection recovery | Offline attempts retry on the next interval; restored and restore-failed states are emitted to ContactCenter. |
|
|
333
|
+
|
|
334
|
+
### WebexRequest
|
|
335
|
+
|
|
336
|
+
```mermaid
|
|
337
|
+
sequenceDiagram
|
|
338
|
+
participant Caller
|
|
339
|
+
participant WR as WebexRequest
|
|
340
|
+
participant Host as webex.request
|
|
341
|
+
participant API as WCC backend
|
|
342
|
+
Caller->>WR: request(service, resource, method, body)
|
|
343
|
+
WR->>Host: authenticated request(options)
|
|
344
|
+
Host->>API: HTTP request
|
|
345
|
+
alt success
|
|
346
|
+
API-->>Host: response
|
|
347
|
+
Host-->>WR: status/body/headers
|
|
348
|
+
WR-->>Caller: response
|
|
349
|
+
else host/service rejection
|
|
350
|
+
API-->>Host: error
|
|
351
|
+
Host-->>WR: same rejection
|
|
352
|
+
WR-->>Caller: same rejection
|
|
353
|
+
end
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
### Subscribe and connect
|
|
357
|
+
|
|
358
|
+
```mermaid
|
|
359
|
+
sequenceDiagram
|
|
360
|
+
participant Caller
|
|
361
|
+
participant WSM as WebSocketManager
|
|
362
|
+
participant Host as webex.request
|
|
363
|
+
participant WS as Browser WebSocket
|
|
364
|
+
Caller->>WSM: initWebSocket({body, resource})
|
|
365
|
+
WSM->>Host: register(body, resource)
|
|
366
|
+
Host-->>WSM: WebSocket URL/subscription
|
|
367
|
+
WSM->>WS: connect()
|
|
368
|
+
alt welcome
|
|
369
|
+
WS-->>WSM: Welcome event
|
|
370
|
+
WSM-->>Caller: WelcomeResponse
|
|
371
|
+
else register/connect failure
|
|
372
|
+
WSM-->>Caller: throw error
|
|
373
|
+
end
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
### Correlated request
|
|
377
|
+
|
|
378
|
+
```mermaid
|
|
379
|
+
sequenceDiagram
|
|
380
|
+
participant Caller
|
|
381
|
+
participant AQM as AqmReqs
|
|
382
|
+
participant WR as WebexRequest
|
|
383
|
+
participant WS as Primary WebSocket
|
|
384
|
+
Caller->>AQM: generated request(payload)
|
|
385
|
+
AQM->>AQM: install notifSuccess/notifFail matchers
|
|
386
|
+
alt matching pending request already exists and timeout is enabled
|
|
387
|
+
AQM-->>Caller: Service.aqm.reqs.Pending
|
|
388
|
+
else request accepted
|
|
389
|
+
AQM->>WR: request(config)
|
|
390
|
+
WR-->>AQM: HTTP acknowledgement only
|
|
391
|
+
alt success bind
|
|
392
|
+
WS-->>AQM: matching success notification
|
|
393
|
+
AQM-->>Caller: typed result
|
|
394
|
+
else failure bind or HTTP rejection
|
|
395
|
+
AQM-->>Caller: structured error
|
|
396
|
+
else timeout
|
|
397
|
+
AQM-->>Caller: Service.aqm.reqs.Timeout
|
|
398
|
+
end
|
|
399
|
+
end
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
### Connection recovery
|
|
403
|
+
|
|
404
|
+
```mermaid
|
|
405
|
+
sequenceDiagram
|
|
406
|
+
participant WSM as WebSocketManager
|
|
407
|
+
participant CS as ConnectionService
|
|
408
|
+
participant CC as ContactCenter
|
|
409
|
+
WSM-->>CS: socketClose/message events
|
|
410
|
+
CS->>CS: update timers and reconnect state
|
|
411
|
+
alt browser online during retry interval
|
|
412
|
+
CS->>WSM: initWebSocket({body: subscribeRequest, resource: SUBSCRIBE_API})
|
|
413
|
+
WSM-->>CS: welcome/message
|
|
414
|
+
CS-->>CC: connectionLost({isSocketReconnected: true})
|
|
415
|
+
CC->>CC: decide whether to silentRelogin()
|
|
416
|
+
else browser offline or reconnect fails
|
|
417
|
+
CS->>CS: retain interval; retry on next CONNECTIVITY_CHECK_INTERVAL
|
|
418
|
+
opt recovery timeout expires
|
|
419
|
+
CS-->>CC: connectionLost({isRestoreFailed: true})
|
|
420
|
+
end
|
|
421
|
+
end
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
## Class / Component Relationships
|
|
425
|
+
```mermaid
|
|
426
|
+
classDiagram
|
|
427
|
+
class WebexRequest
|
|
428
|
+
class WebSocketManager
|
|
429
|
+
class AqmReqs
|
|
430
|
+
class ConnectionService
|
|
431
|
+
class ContactCenter
|
|
432
|
+
AqmReqs --> WebexRequest : authenticated HTTP
|
|
433
|
+
AqmReqs --> WebSocketManager : bind-matched completion
|
|
434
|
+
ConnectionService --> WebSocketManager : liveness + reconnect
|
|
435
|
+
ConnectionService --> ContactCenter : connectionLost details
|
|
436
|
+
ContactCenter --> WebSocketManager : registration lifecycle
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
## Use Cases
|
|
440
|
+
- **UC-1 Authenticated REST:** pass the service key and request options to the host and return its response or rejection unchanged. Evidence: `src/services/core/WebexRequest.ts`, `test/unit/spec/services/core/WebexRequest.ts`.
|
|
441
|
+
- **UC-2 Subscribe/connect:** register with `{body, resource}`, connect, and wait for welcome. Evidence: `src/services/core/websocket/WebSocketManager.ts`, `test/unit/spec/services/core/websocket/WebSocketManager.ts`.
|
|
442
|
+
- **UC-3 AQM correlation:** send HTTP but settle on matching notification/failure/timeout. Evidence: `src/services/core/aqm-reqs.ts`, `test/unit/spec/services/core/aqm-reqs.ts`.
|
|
443
|
+
- **UC-4 Reconnect:** retry socket initialization and emit state for ContactCenter-owned recovery. Evidence: `src/services/core/websocket/connection-service.ts`, `test/unit/spec/services/core/websocket/connection-service.ts`.
|
|
444
|
+
|
|
445
|
+
## State Model
|
|
446
|
+
WebSocketManager owns socket/welcome/worker state. AqmReqs owns pending success/failure/cancel bind maps until settlement or timeout. ConnectionService owns liveness/reconnect timers and flags, then emits state details. It does not own agent credentials, profile, or silent relogin.
|
|
447
|
+
|
|
448
|
+
## Business Rules & Invariants
|
|
449
|
+
- Core must preserve its typed public/event contracts and must not invent backend states or responses. Enforced in `src/services/core/WebexRequest.ts`.
|
|
450
|
+
- Rollout applicability is N/A for Core: keepalive, timeout, and reconnect constants control behavior, while Services constructs Core without a feature gate.
|
|
451
|
+
|
|
452
|
+
## Concurrency & Reactive Flow
|
|
453
|
+
The primary WebSocket fans messages to independent AqmReqs, ContactCenter, TaskManager, and ConnectionService listeners. AqmReqs clears all correlated bind entries on settlement. The keepalive worker posts status every 4000 ms and may request closure after 16000 ms offline; reconnect attempts use their separate interval. Listener registration/removal must preserve identity.
|
|
454
|
+
|
|
455
|
+
## State Machine
|
|
456
|
+
```mermaid
|
|
457
|
+
stateDiagram-v2
|
|
458
|
+
[*] --> SocketClosed
|
|
459
|
+
SocketClosed --> Connecting: initWebSocket(body, resource)
|
|
460
|
+
Connecting --> Connected: welcome
|
|
461
|
+
Connecting --> SocketClosed: register/connect error
|
|
462
|
+
Connected --> ConnectionSuspect: liveness timer expires
|
|
463
|
+
ConnectionSuspect --> Reconnecting: socket close / retry
|
|
464
|
+
Reconnecting --> Connected: reconnect + message
|
|
465
|
+
Reconnecting --> Reconnecting: offline or retry failure
|
|
466
|
+
Connected --> SocketClosed: manual close
|
|
467
|
+
```
|
|
468
|
+
|
|
469
|
+
## Protocol / Wire Format
|
|
470
|
+
`WebSocketManager.initWebSocket` accepts `{body: SubscribeRequest, resource: string}`. Subscription uses the host Webex request API; AqmReqs operational HTTP uses the WebexRequest wrapper. AQM request configs carry `host`, `url`, optional `method`/`data`, `notifSuccess.bind`, optional `notifFail.bind`, optional cancel bind, and optional timeout. HTTP acknowledgement never substitutes for the matching WebSocket operation result.
|
|
471
|
+
|
|
472
|
+
## Error Handling & Failure Modes
|
|
473
|
+
| Condition | Signal (error/code/result) | Caller recovery |
|
|
474
|
+
|---|---|---|
|
|
475
|
+
| Dependency rejection | Typed/rethrown error or failure event | Inspect structured details, preserve tracking id, and retry only when the operation is safe. |
|
|
476
|
+
| Timeout or missing async completion | Timeout/recovery state | Follow the module-specific recovery path; never synthesize success. |
|
|
477
|
+
|
|
478
|
+
Standard error handler for SDK operations:
|
|
479
|
+
|
|
480
|
+
```typescript
|
|
481
|
+
import {getErrorDetails} from './services/core/Utils';
|
|
482
|
+
|
|
483
|
+
try {
|
|
484
|
+
await operation();
|
|
485
|
+
} catch (error) {
|
|
486
|
+
const {error: detailedError, reason} = getErrorDetails(
|
|
487
|
+
error,
|
|
488
|
+
'methodName', // Method name for logging
|
|
489
|
+
'ModuleName' // Module name for logging
|
|
490
|
+
);
|
|
491
|
+
|
|
492
|
+
// getErrorDetails automatically:
|
|
493
|
+
// 1. Logs the error
|
|
494
|
+
// 2. Uploads logs (unless AGENT_NOT_FOUND in silentRelogin)
|
|
495
|
+
// 3. Extracts reason from error.details
|
|
496
|
+
// 4. Creates Error with reason as message
|
|
497
|
+
|
|
498
|
+
throw detailedError;
|
|
499
|
+
}
|
|
500
|
+
```
|
|
501
|
+
|
|
502
|
+
Error handler for task operations:
|
|
503
|
+
|
|
504
|
+
```typescript
|
|
505
|
+
import {generateTaskErrorObject} from './services/core/Utils';
|
|
506
|
+
|
|
507
|
+
try {
|
|
508
|
+
await taskOperation();
|
|
509
|
+
} catch (error) {
|
|
510
|
+
const taskError = generateTaskErrorObject(
|
|
511
|
+
error,
|
|
512
|
+
'transfer',
|
|
513
|
+
'TaskModule'
|
|
514
|
+
);
|
|
515
|
+
throw taskError;
|
|
516
|
+
}
|
|
517
|
+
```
|
|
518
|
+
|
|
519
|
+
A specific `Msg<T>` for failure responses. Access error details via `failure.data`:
|
|
520
|
+
|
|
521
|
+
```typescript
|
|
522
|
+
export type Failure = Msg<{
|
|
523
|
+
agentId: string;
|
|
524
|
+
trackingId: string;
|
|
525
|
+
reasonCode: number;
|
|
526
|
+
orgId: string;
|
|
527
|
+
reason: string;
|
|
528
|
+
}>;
|
|
529
|
+
|
|
530
|
+
// Usage in catch
|
|
531
|
+
const failure = error.details as Failure;
|
|
532
|
+
LoggerProxy.error(`Operation failed: ${failure.data?.reason}`, {
|
|
533
|
+
module: 'MyService',
|
|
534
|
+
method: 'myMethod',
|
|
535
|
+
trackingId: failure?.trackingId,
|
|
536
|
+
});
|
|
537
|
+
```
|
|
538
|
+
|
|
539
|
+
Error interface with a flexible data field for additional context:
|
|
540
|
+
|
|
541
|
+
```typescript
|
|
542
|
+
export interface AugmentedError extends Error {
|
|
543
|
+
data?: Record<string, any>;
|
|
544
|
+
}
|
|
545
|
+
```
|
|
546
|
+
|
|
547
|
+
```typescript
|
|
548
|
+
import * as Err from './Err';
|
|
549
|
+
|
|
550
|
+
const error = new Err.Details('Service.aqm.agent.login', {
|
|
551
|
+
status: 401,
|
|
552
|
+
type: 'UNAUTHORIZED',
|
|
553
|
+
trackingId: 'uuid',
|
|
554
|
+
});
|
|
555
|
+
```
|
|
556
|
+
|
|
557
|
+
```typescript
|
|
558
|
+
import {createErrDetailsObject} from './Utils';
|
|
559
|
+
|
|
560
|
+
const errDetails = createErrDetailsObject(webexRequestPayload);
|
|
561
|
+
// Returns Err.Details with trackingId and body
|
|
562
|
+
```
|
|
563
|
+
|
|
564
|
+
```typescript
|
|
565
|
+
// Correct
|
|
566
|
+
const {error: detailedError} = getErrorDetails(error, method, module);
|
|
567
|
+
throw detailedError;
|
|
568
|
+
|
|
569
|
+
// Wrong - loses context
|
|
570
|
+
throw error;
|
|
571
|
+
```
|
|
572
|
+
|
|
573
|
+
This section documents shared error helpers in `Utils.ts` that normalize errors, enrich them with context, and ensure consistent logging/upload behavior across services.
|
|
574
|
+
|
|
575
|
+
```typescript
|
|
576
|
+
// Msg - Generic message interface (GlobalTypes.ts)
|
|
577
|
+
export type Msg<T = any> = {
|
|
578
|
+
type: string;
|
|
579
|
+
orgId: string;
|
|
580
|
+
trackingId: string;
|
|
581
|
+
data: T;
|
|
582
|
+
};
|
|
583
|
+
|
|
584
|
+
// Failure - Backend error structure (GlobalTypes.ts)
|
|
585
|
+
// Built on Msg<T> with specific error data fields
|
|
586
|
+
export type Failure = Msg<{
|
|
587
|
+
agentId: string;
|
|
588
|
+
trackingId: string;
|
|
589
|
+
reasonCode: number;
|
|
590
|
+
orgId: string;
|
|
591
|
+
reason: string;
|
|
592
|
+
}>;
|
|
593
|
+
|
|
594
|
+
// AugmentedError - Extended Error with flexible data field (GlobalTypes.ts)
|
|
595
|
+
export interface AugmentedError extends Error {
|
|
596
|
+
data?: Record<string, any>;
|
|
597
|
+
}
|
|
598
|
+
```
|
|
599
|
+
|
|
600
|
+
```mermaid
|
|
601
|
+
flowchart TD
|
|
602
|
+
A[Error caught] --> B[Cast error.details to Failure]
|
|
603
|
+
B --> C[Extract reason from failure.data.reason]
|
|
604
|
+
C --> D{Is silentRelogin + AGENT_NOT_FOUND?}
|
|
605
|
+
D -->|Yes| E[Skip logging/upload]
|
|
606
|
+
D -->|No| F[Log error with LoggerProxy]
|
|
607
|
+
F --> G[Upload logs via WebexRequest]
|
|
608
|
+
G --> H[Check if stationLogin]
|
|
609
|
+
H -->|Yes| I[Get field-specific error data]
|
|
610
|
+
H -->|No| J[Use generic error]
|
|
611
|
+
I --> K[Create Error with data property]
|
|
612
|
+
J --> K
|
|
613
|
+
K --> L["Return {error, reason}"]
|
|
614
|
+
```
|
|
615
|
+
|
|
616
|
+
Use this helper for agent/config-style flows where backend failure payloads are transformed into a user-facing `Error` plus `reason`, with optional station-login field metadata.
|
|
617
|
+
|
|
618
|
+
```typescript
|
|
619
|
+
export const getErrorDetails = (error: any, methodName: string, moduleName: string) => {
|
|
620
|
+
let errData = {message: '', fieldName: ''};
|
|
621
|
+
|
|
622
|
+
const failure = error.details as Failure;
|
|
623
|
+
const reason = failure?.data?.reason ?? `Error while performing ${methodName}`;
|
|
624
|
+
|
|
625
|
+
// Log error (unless AGENT_NOT_FOUND in silentRelogin)
|
|
626
|
+
if (!(reason === 'AGENT_NOT_FOUND' && methodName === 'silentRelogin')) {
|
|
627
|
+
LoggerProxy.error(`${methodName} failed with reason: ${reason}`, {
|
|
628
|
+
module: moduleName,
|
|
629
|
+
method: methodName,
|
|
630
|
+
trackingId: failure?.trackingId,
|
|
631
|
+
});
|
|
632
|
+
|
|
633
|
+
// Upload logs
|
|
634
|
+
WebexRequest.getInstance().uploadLogs({
|
|
635
|
+
correlationId: failure?.trackingId,
|
|
636
|
+
});
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
// For stationLogin, extract field-specific error data (message + fieldName)
|
|
640
|
+
if (methodName === 'stationLogin') {
|
|
641
|
+
errData = getStationLoginErrorData(failure, error.loginOption);
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
const err = new Error(reason);
|
|
645
|
+
// @ts-ignore - custom property for backward compatibility
|
|
646
|
+
err.data = errData;
|
|
647
|
+
|
|
648
|
+
return {error: err, reason};
|
|
649
|
+
};
|
|
650
|
+
```
|
|
651
|
+
|
|
652
|
+
Use this helper for task/interaction flows where richer task error metadata (`errorType`, `errorData`, `reasonCode`, `trackingId`) is required on the returned `AugmentedError`.
|
|
653
|
+
|
|
654
|
+
```typescript
|
|
655
|
+
export const generateTaskErrorObject = (
|
|
656
|
+
error: any,
|
|
657
|
+
methodName: string,
|
|
658
|
+
moduleName: string
|
|
659
|
+
): AugmentedError => {
|
|
660
|
+
const trackingId = error?.details?.trackingId || error?.trackingId || '';
|
|
661
|
+
const errorMsg = error?.details?.msg;
|
|
662
|
+
|
|
663
|
+
const errorMessage = errorMsg?.errorMessage || error.message || 'Error';
|
|
664
|
+
const errorType = errorMsg?.errorType || error.name || 'Unknown Error';
|
|
665
|
+
const errorData = errorMsg?.errorData || '';
|
|
666
|
+
const reasonCode = errorMsg?.reasonCode || 0;
|
|
667
|
+
|
|
668
|
+
LoggerProxy.error(`${methodName} failed: ${errorMessage} (${errorType})`, {
|
|
669
|
+
module: moduleName,
|
|
670
|
+
method: methodName,
|
|
671
|
+
trackingId,
|
|
672
|
+
});
|
|
673
|
+
WebexRequest.getInstance().uploadLogs({correlationId: trackingId});
|
|
674
|
+
|
|
675
|
+
const reason = `${errorType}: ${errorMessage}${errorData ? ` (${errorData})` : ''}`;
|
|
676
|
+
const err: AugmentedError = new Error(reason);
|
|
677
|
+
err.data = {
|
|
678
|
+
message: errorMessage,
|
|
679
|
+
errorType,
|
|
680
|
+
errorData,
|
|
681
|
+
reasonCode,
|
|
682
|
+
trackingId,
|
|
683
|
+
};
|
|
684
|
+
|
|
685
|
+
return err;
|
|
686
|
+
};
|
|
687
|
+
```
|
|
688
|
+
|
|
689
|
+
```typescript
|
|
690
|
+
// Use getErrorDetails for:
|
|
691
|
+
// - Agent service operations
|
|
692
|
+
// - Station login/logout flows
|
|
693
|
+
//
|
|
694
|
+
// Use generateTaskErrorObject for:
|
|
695
|
+
// - Task service operations
|
|
696
|
+
// - Interaction-related errors
|
|
697
|
+
```
|
|
698
|
+
|
|
699
|
+
**Cause**: Subscribe API failed or invalid URL
|
|
700
|
+
|
|
701
|
+
**Solution**: Check subscribe response and WebSocket URL
|
|
702
|
+
|
|
703
|
+
**Cause**: Event listener not registered
|
|
704
|
+
|
|
705
|
+
**Solution**: Ensure `on('message', handler)` called before connect
|
|
706
|
+
|
|
707
|
+
**Cause**: Keepalive not enabled or network issues
|
|
708
|
+
|
|
709
|
+
**Solution**: Verify worker-driven keepalive is running after socket `onopen`, and check network/offline transitions
|
|
710
|
+
|
|
711
|
+
## Pitfalls
|
|
712
|
+
- `initWebSocket` requires both `body` and `resource`; omitting `resource` registers against no durable subscription endpoint.
|
|
713
|
+
- AqmReqs installs notification binds before HTTP and never resolves from acknowledgement; duplicate binds, timeout, and cleanup ordering are correctness-critical.
|
|
714
|
+
- Keepalive closure (16 seconds), lost-connection detection (8 seconds), reconnect interval (5 seconds), and recovery timeout (50 seconds) are separate controls and must not be conflated.
|
|
715
|
+
|
|
716
|
+
## Module Do's / Don'ts
|
|
717
|
+
- DO keep authentication and service resolution in the host Webex request layer; `WebexRequest.request` is a thin delegating wrapper.
|
|
718
|
+
- DO mask authorization headers in the `AqmReqs` HTTP-error/timeout paths before those details are logged or surfaced.
|
|
719
|
+
- DO clear success/failure/cancel bind entries together when an AQM request settles.
|
|
720
|
+
- DON'T move silent-relogin policy into ConnectionService; it emits transport state only.
|
|
721
|
+
- DON'T treat timeout constants as feature flags or reuse one timer for another lifecycle purpose.
|
|
722
|
+
|
|
723
|
+
```typescript
|
|
724
|
+
const response = await webexReq.request({...});
|
|
725
|
+
|
|
726
|
+
if (response.statusCode !== 200) {
|
|
727
|
+
throw new Error(`API call failed with ${response.statusCode}`);
|
|
728
|
+
}
|
|
729
|
+
```
|
|
730
|
+
|
|
731
|
+
```typescript
|
|
732
|
+
const trackingId = response.headers?.trackingid ||
|
|
733
|
+
response.headers?.TrackingID;
|
|
734
|
+
```
|
|
735
|
+
|
|
736
|
+
## Key Design Trade-off
|
|
737
|
+
- Request initiation stays HTTP while completion can arrive asynchronously over WebSocket; explicit timers and correlation preserve backend fidelity but increase lifecycle complexity.
|
|
738
|
+
|
|
739
|
+
## Test-Case Strategy (module)
|
|
740
|
+
Unit tests mirror module paths under `test/unit/spec/services/core`. Preserve positive and negative paths, event ordering, timeout/recovery behavior, and the package's 85% global branch/function/line/statement threshold.
|
|
741
|
+
|
|
742
|
+
| Behavior / Requirement | Existing test evidence | Gap |
|
|
743
|
+
|---|---|---|
|
|
744
|
+
| `CORE-R-001` | `test/unit/spec/services/core/WebexRequest.ts` | None. |
|
|
745
|
+
| `CORE-R-002` | `test/unit/spec/services/core/websocket/WebSocketManager.ts` | None. |
|
|
746
|
+
| `CORE-R-003` | `test/unit/spec/services/core/aqm-reqs.ts` | None. |
|
|
747
|
+
| `CORE-R-004` | `test/unit/spec/services/core/websocket/connection-service.ts` | None. |
|
|
748
|
+
| `CORE-R-005` | `test/unit/spec/services/core/websocket/WebSocketManager.ts` | Keep timer-value assertions synchronized with constants. |
|
|
749
|
+
| `CORE-R-006` | `test/unit/spec/services/core/websocket/WebSocketManager.ts` | Feature-gate absence is verified from construction/source rather than a dedicated negative test. |
|
|
750
|
+
|
|
751
|
+
## Traceability
|
|
752
|
+
- Repo architecture: `../../../../ai-docs/ARCHITECTURE.md` · Registry: `../../../../ai-docs/SPEC_INDEX.md`
|
|
753
|
+
- Coverage state and contracts baseline: `../../../../.sdd/manifest.json`
|
|
754
|
+
|
|
755
|
+
- [Root Orchestrator AGENTS.md](../../../../AGENTS.md) - Task routing, critical rules, cross-service patterns
|
|
756
|
+
|
|
757
|
+
- [WebSocketManager.ts](../websocket/WebSocketManager.ts)
|
|
758
|
+
|
|
759
|
+
- [WebexRequest.ts](../WebexRequest.ts)
|
|
760
|
+
|
|
761
|
+
- [Utils.ts](../Utils.ts)
|
|
762
|
+
|
|
763
|
+
- [GlobalTypes.ts](../GlobalTypes.ts)
|
|
764
|
+
|
|
765
|
+
- [Root Orchestrator AGENTS.md](../../../../AGENTS.md) — Task routing, critical rules, cross-service patterns
|
|
766
|
+
|
|
767
|
+
- [WebSocketManager.ts](../websocket/WebSocketManager.ts) — WebSocket lifecycle, keepalive worker integration
|
|
768
|
+
|
|
769
|
+
- [connection-service.ts](../websocket/connection-service.ts) — Reconnection logic, connection state events
|
|
770
|
+
|
|
771
|
+
- [keepalive.worker.js](../websocket/keepalive.worker.js) — Web Worker for periodic keepalive and offline detection
|
|
772
|
+
|
|
773
|
+
- [WebexRequest.ts](../WebexRequest.ts) — Singleton HTTP request handler
|
|
774
|
+
|
|
775
|
+
- [Utils.ts](../Utils.ts) — `getErrorDetails`, `generateTaskErrorObject`, consult utilities
|
|
776
|
+
|
|
777
|
+
- [Err.ts](../Err.ts) — `Err.Message` and `Err.Details` error classes
|
|
778
|
+
|
|
779
|
+
- [aqm-reqs.ts](../aqm-reqs.ts) — AQM request/response pattern, WebSocket notification binding
|
|
780
|
+
|
|
781
|
+
- [GlobalTypes.ts](../GlobalTypes.ts) — `Msg`, `Failure`, `AugmentedError` type definitions
|
|
782
|
+
|
|
783
|
+
- [types.ts](../types.ts) — `Pending`, `Req`, `Conf`, `Res` types for AqmReqs
|