@komo-tech/embed-core 0.1.0
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/README.md +54 -0
- package/dist/FetchEmbedSessionClient.d.ts +39 -0
- package/dist/FetchEmbedSessionClient.d.ts.map +1 -0
- package/dist/FetchEmbedSessionClient.js +209 -0
- package/dist/KomoEmbedSessionManager.d.ts +103 -0
- package/dist/KomoEmbedSessionManager.d.ts.map +1 -0
- package/dist/KomoEmbedSessionManager.js +377 -0
- package/dist/RefreshScheduler.d.ts +10 -0
- package/dist/RefreshScheduler.d.ts.map +1 -0
- package/dist/RefreshScheduler.js +10 -0
- package/dist/errors.d.ts +15 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +12 -0
- package/dist/events.d.ts +28 -0
- package/dist/events.d.ts.map +1 -0
- package/dist/events.js +26 -0
- package/dist/identity.d.ts +13 -0
- package/dist/identity.d.ts.map +1 -0
- package/dist/identity.js +46 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +5 -0
- package/dist/types.d.ts +64 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +1 -0
- package/package.json +58 -0
package/README.md
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# @komo-tech/embed-core
|
|
2
|
+
|
|
3
|
+
Platform-neutral session core for Komo embeds. Owns identity exchange, memory-only session state, refresh scheduling, and typed events without binding to web or React Native runtime APIs.
|
|
4
|
+
|
|
5
|
+
Copyright Komo Technologies. All rights reserved.
|
|
6
|
+
|
|
7
|
+
## Usage
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
import {
|
|
11
|
+
FetchEmbedSessionClient,
|
|
12
|
+
KomoEmbedSessionManager
|
|
13
|
+
} from '@komo-tech/embed-core';
|
|
14
|
+
|
|
15
|
+
const session = new KomoEmbedSessionManager({
|
|
16
|
+
workspaceId: 'workspace-guid',
|
|
17
|
+
siteId: 'site-guid',
|
|
18
|
+
getIdentityToken: async () => ({
|
|
19
|
+
type: 'jwt',
|
|
20
|
+
token: await host.getJwt()
|
|
21
|
+
}),
|
|
22
|
+
client: new FetchEmbedSessionClient({
|
|
23
|
+
baseUrl: 'https://api.komo.tech',
|
|
24
|
+
fetch: hostFetch
|
|
25
|
+
})
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
const bearer = await session.getBearerToken();
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Use email identity without `siteId`:
|
|
32
|
+
|
|
33
|
+
```ts
|
|
34
|
+
const session = new KomoEmbedSessionManager({
|
|
35
|
+
workspaceId: 'workspace-guid',
|
|
36
|
+
getIdentityToken: () => ({
|
|
37
|
+
type: 'email',
|
|
38
|
+
attributes: { email: 'person@example.com' }
|
|
39
|
+
}),
|
|
40
|
+
client
|
|
41
|
+
});
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Subscribe to events:
|
|
45
|
+
|
|
46
|
+
```ts
|
|
47
|
+
const unsubscribe = session.onAuthenticated(({ contactId, trustLevel }) => {
|
|
48
|
+
logger.info('Komo session authenticated', { contactId, trustLevel });
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
unsubscribe();
|
|
52
|
+
session.dispose();
|
|
53
|
+
```
|
|
54
|
+
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { EmbedSessionClient, EmbedSessionExchangeRequest, EmbedSessionExchangeResponse, EmbedSessionRefreshRequest, EmbedSessionRefreshResponse, PortableAbortSignal } from './types.js';
|
|
2
|
+
export type PortableHeadersInit = Record<string, string> | Array<[string, string]> | {
|
|
3
|
+
forEach(callback: (value: string, key: string) => void): void;
|
|
4
|
+
};
|
|
5
|
+
export interface PortableFetchRequestInit {
|
|
6
|
+
method?: string;
|
|
7
|
+
signal?: PortableAbortSignal;
|
|
8
|
+
body?: string;
|
|
9
|
+
headers?: Record<string, string>;
|
|
10
|
+
}
|
|
11
|
+
export interface PortableFetchResponse {
|
|
12
|
+
readonly ok: boolean;
|
|
13
|
+
readonly status: number;
|
|
14
|
+
text(): Promise<string>;
|
|
15
|
+
}
|
|
16
|
+
export type PortableFetch = (input: string, init?: PortableFetchRequestInit) => Promise<PortableFetchResponse>;
|
|
17
|
+
export type RuntimeFetch = (input: string) => Promise<PortableFetchResponse>;
|
|
18
|
+
export interface FetchEmbedSessionClientOptions {
|
|
19
|
+
baseUrl: string;
|
|
20
|
+
fetch?: PortableFetch | RuntimeFetch;
|
|
21
|
+
maxRetries?: number;
|
|
22
|
+
retryBaseDelayMs?: number;
|
|
23
|
+
headers?: PortableHeadersInit | (() => PortableHeadersInit);
|
|
24
|
+
}
|
|
25
|
+
export declare class FetchEmbedSessionClient implements EmbedSessionClient {
|
|
26
|
+
private readonly baseUrl;
|
|
27
|
+
private readonly fetchImpl;
|
|
28
|
+
private readonly maxRetries;
|
|
29
|
+
private readonly retryBaseDelayMs;
|
|
30
|
+
private readonly headers?;
|
|
31
|
+
constructor(options: FetchEmbedSessionClientOptions);
|
|
32
|
+
exchange(request: EmbedSessionExchangeRequest, signal?: PortableAbortSignal): Promise<EmbedSessionExchangeResponse>;
|
|
33
|
+
refresh(request: EmbedSessionRefreshRequest, signal?: PortableAbortSignal): Promise<EmbedSessionRefreshResponse>;
|
|
34
|
+
private exchangeOnce;
|
|
35
|
+
private refreshOnce;
|
|
36
|
+
private postSessionRequest;
|
|
37
|
+
private resolveHeaders;
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=FetchEmbedSessionClient.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FetchEmbedSessionClient.d.ts","sourceRoot":"","sources":["../src/FetchEmbedSessionClient.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,kBAAkB,EAClB,2BAA2B,EAC3B,4BAA4B,EAC5B,0BAA0B,EAC1B,2BAA2B,EAG3B,mBAAmB,EACpB,MAAM,YAAY,CAAC;AAEpB,MAAM,MAAM,mBAAmB,GAC3B,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GACtB,KAAK,CAAC,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,GACvB;IACE,OAAO,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,KAAK,IAAI,GAAG,IAAI,CAAC;CAC/D,CAAC;AAEN,MAAM,WAAW,wBAAwB;IACvC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,mBAAmB,CAAC;IAC7B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAClC;AAED,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,EAAE,EAAE,OAAO,CAAC;IACrB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,IAAI,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC;CACzB;AAED,MAAM,MAAM,aAAa,GAAG,CAC1B,KAAK,EAAE,MAAM,EACb,IAAI,CAAC,EAAE,wBAAwB,KAC5B,OAAO,CAAC,qBAAqB,CAAC,CAAC;AAEpC,MAAM,MAAM,YAAY,GAAG,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,qBAAqB,CAAC,CAAC;AAE7E,MAAM,WAAW,8BAA8B;IAC7C,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,aAAa,GAAG,YAAY,CAAC;IACrC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,OAAO,CAAC,EAAE,mBAAmB,GAAG,CAAC,MAAM,mBAAmB,CAAC,CAAC;CAC7D;AAmJD,qBAAa,uBAAwB,YAAW,kBAAkB;IAChE,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAgB;IAC1C,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAS;IAC1C,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAoD;gBAEjE,OAAO,EAAE,8BAA8B;IAY7C,QAAQ,CACZ,OAAO,EAAE,2BAA2B,EACpC,MAAM,CAAC,EAAE,mBAAmB,GAC3B,OAAO,CAAC,4BAA4B,CAAC;IA6BlC,OAAO,CACX,OAAO,EAAE,0BAA0B,EACnC,MAAM,CAAC,EAAE,mBAAmB,GAC3B,OAAO,CAAC,2BAA2B,CAAC;YA6BzB,YAAY;YAYZ,WAAW;YAYX,kBAAkB;IA6DhC,OAAO,CAAC,cAAc;CAsBvB"}
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import { EmbedSessionExchangeError } from './errors.js';
|
|
2
|
+
const exchangePath = '/api/v1/live/auth/sdk/session';
|
|
3
|
+
const refreshPath = '/api/v1/live/auth/sdk/session/refresh';
|
|
4
|
+
const retryableStatus = (status) => status >= 500 && status <= 599;
|
|
5
|
+
const delay = (ms, signal) => new Promise((resolve, reject) => {
|
|
6
|
+
if (signal?.aborted) {
|
|
7
|
+
reject(new Error('Exchange request aborted'));
|
|
8
|
+
return;
|
|
9
|
+
}
|
|
10
|
+
const timeout = setTimeout(() => {
|
|
11
|
+
signal?.removeEventListener?.('abort', onAbort);
|
|
12
|
+
resolve();
|
|
13
|
+
}, ms);
|
|
14
|
+
const onAbort = () => {
|
|
15
|
+
clearTimeout(timeout);
|
|
16
|
+
reject(new Error('Exchange request aborted'));
|
|
17
|
+
};
|
|
18
|
+
signal?.addEventListener?.('abort', onAbort, { once: true });
|
|
19
|
+
});
|
|
20
|
+
const toUrl = (baseUrl, path) => {
|
|
21
|
+
const normalizedBase = baseUrl.endsWith('/') ? baseUrl.slice(0, -1) : baseUrl;
|
|
22
|
+
return `${normalizedBase}${path}`;
|
|
23
|
+
};
|
|
24
|
+
const toWireIdentity = (identity) => {
|
|
25
|
+
switch (identity.type) {
|
|
26
|
+
case 'jwt':
|
|
27
|
+
return { type: 'jwt', token: identity.token };
|
|
28
|
+
case 'email':
|
|
29
|
+
return { type: 'email', attributes: identity.attributes };
|
|
30
|
+
case 'oauth2':
|
|
31
|
+
return { type: 'oauth2', token: identity.token };
|
|
32
|
+
case 'custom':
|
|
33
|
+
return { ...identity };
|
|
34
|
+
case 'anonymous':
|
|
35
|
+
return { type: 'anonymous' };
|
|
36
|
+
default: {
|
|
37
|
+
const exhaustive = identity;
|
|
38
|
+
return exhaustive;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
const toBody = (request) => ({
|
|
43
|
+
workspaceId: request.workspaceId,
|
|
44
|
+
...(request.siteId ? { siteId: request.siteId } : {}),
|
|
45
|
+
identity: toWireIdentity(request.identity)
|
|
46
|
+
});
|
|
47
|
+
const toRefreshBody = (request) => ({
|
|
48
|
+
sessionToken: request.sessionToken
|
|
49
|
+
});
|
|
50
|
+
const isRecord = (input) => typeof input === 'object' && input !== null;
|
|
51
|
+
const isExchangeResponse = (input) => {
|
|
52
|
+
if (!isRecord(input)) {
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
return (typeof input.sessionToken === 'string' &&
|
|
56
|
+
typeof input.expiresAt === 'string' &&
|
|
57
|
+
typeof input.contactId === 'string' &&
|
|
58
|
+
typeof input.trustLevel === 'string');
|
|
59
|
+
};
|
|
60
|
+
const parseJson = async (response, invalidJsonRetryable) => {
|
|
61
|
+
const text = await response.text();
|
|
62
|
+
if (text.length === 0) {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
try {
|
|
66
|
+
return JSON.parse(text);
|
|
67
|
+
}
|
|
68
|
+
catch (error) {
|
|
69
|
+
throw new EmbedSessionExchangeError('Exchange response was not valid JSON', {
|
|
70
|
+
status: response.status,
|
|
71
|
+
retryable: invalidJsonRetryable,
|
|
72
|
+
cause: error
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
const isStructuredFailure = (envelope) => envelope.success === false ||
|
|
77
|
+
envelope.isError === true ||
|
|
78
|
+
envelope.result === 'Failed' ||
|
|
79
|
+
envelope.result === 128;
|
|
80
|
+
const toExchangeError = (message, status, retryable, body) => {
|
|
81
|
+
const envelope = isRecord(body)
|
|
82
|
+
? body
|
|
83
|
+
: undefined;
|
|
84
|
+
const failureReason = envelope?.failureReason;
|
|
85
|
+
const safeMessage = envelope?.errorMessage ?? message;
|
|
86
|
+
return new EmbedSessionExchangeError(safeMessage, {
|
|
87
|
+
status,
|
|
88
|
+
failureReason,
|
|
89
|
+
retryable
|
|
90
|
+
});
|
|
91
|
+
};
|
|
92
|
+
export class FetchEmbedSessionClient {
|
|
93
|
+
baseUrl;
|
|
94
|
+
fetchImpl;
|
|
95
|
+
maxRetries;
|
|
96
|
+
retryBaseDelayMs;
|
|
97
|
+
headers;
|
|
98
|
+
constructor(options) {
|
|
99
|
+
if (!options.fetch) {
|
|
100
|
+
throw new Error('FetchEmbedSessionClient requires injected fetch');
|
|
101
|
+
}
|
|
102
|
+
this.baseUrl = options.baseUrl;
|
|
103
|
+
this.fetchImpl = options.fetch;
|
|
104
|
+
this.maxRetries = options.maxRetries ?? 2;
|
|
105
|
+
this.retryBaseDelayMs = options.retryBaseDelayMs ?? 250;
|
|
106
|
+
this.headers = options.headers;
|
|
107
|
+
}
|
|
108
|
+
async exchange(request, signal) {
|
|
109
|
+
let attempt = 0;
|
|
110
|
+
while (true) {
|
|
111
|
+
try {
|
|
112
|
+
return await this.exchangeOnce(request, signal);
|
|
113
|
+
}
|
|
114
|
+
catch (error) {
|
|
115
|
+
const exchangeError = error instanceof EmbedSessionExchangeError
|
|
116
|
+
? error
|
|
117
|
+
: new EmbedSessionExchangeError('Exchange request failed', {
|
|
118
|
+
retryable: !signal?.aborted,
|
|
119
|
+
cause: error
|
|
120
|
+
});
|
|
121
|
+
if (!exchangeError.retryable ||
|
|
122
|
+
attempt >= this.maxRetries ||
|
|
123
|
+
signal?.aborted) {
|
|
124
|
+
throw exchangeError;
|
|
125
|
+
}
|
|
126
|
+
attempt += 1;
|
|
127
|
+
await delay(this.retryBaseDelayMs * 2 ** (attempt - 1), signal);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
async refresh(request, signal) {
|
|
132
|
+
let attempt = 0;
|
|
133
|
+
while (true) {
|
|
134
|
+
try {
|
|
135
|
+
return await this.refreshOnce(request, signal);
|
|
136
|
+
}
|
|
137
|
+
catch (error) {
|
|
138
|
+
const exchangeError = error instanceof EmbedSessionExchangeError
|
|
139
|
+
? error
|
|
140
|
+
: new EmbedSessionExchangeError('Refresh request failed', {
|
|
141
|
+
retryable: !signal?.aborted,
|
|
142
|
+
cause: error
|
|
143
|
+
});
|
|
144
|
+
if (!exchangeError.retryable ||
|
|
145
|
+
attempt >= this.maxRetries ||
|
|
146
|
+
signal?.aborted) {
|
|
147
|
+
throw exchangeError;
|
|
148
|
+
}
|
|
149
|
+
attempt += 1;
|
|
150
|
+
await delay(this.retryBaseDelayMs * 2 ** (attempt - 1), signal);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
async exchangeOnce(request, signal) {
|
|
155
|
+
return this.postSessionRequest(exchangePath, toBody(request), 'Exchange', signal);
|
|
156
|
+
}
|
|
157
|
+
async refreshOnce(request, signal) {
|
|
158
|
+
return this.postSessionRequest(refreshPath, toRefreshBody(request), 'Refresh', signal);
|
|
159
|
+
}
|
|
160
|
+
async postSessionRequest(path, requestBody, operation, signal) {
|
|
161
|
+
const response = await this.fetchImpl(toUrl(this.baseUrl, path), {
|
|
162
|
+
method: 'POST',
|
|
163
|
+
signal,
|
|
164
|
+
body: JSON.stringify(requestBody),
|
|
165
|
+
headers: {
|
|
166
|
+
'content-type': 'application/json',
|
|
167
|
+
accept: 'application/json',
|
|
168
|
+
...this.resolveHeaders()
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
|
+
const body = await parseJson(response, retryableStatus(response.status));
|
|
172
|
+
if (!response.ok) {
|
|
173
|
+
throw toExchangeError(`${operation} request failed`, response.status, retryableStatus(response.status), body);
|
|
174
|
+
}
|
|
175
|
+
if (isRecord(body)) {
|
|
176
|
+
const envelope = body;
|
|
177
|
+
if (isStructuredFailure(envelope)) {
|
|
178
|
+
throw toExchangeError(`${operation} failed`, response.status, false, body);
|
|
179
|
+
}
|
|
180
|
+
if (isExchangeResponse(envelope.data)) {
|
|
181
|
+
return envelope.data;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
if (isExchangeResponse(body)) {
|
|
185
|
+
return body;
|
|
186
|
+
}
|
|
187
|
+
throw new EmbedSessionExchangeError(`${operation} response shape was invalid`, {
|
|
188
|
+
status: response.status,
|
|
189
|
+
retryable: false
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
resolveHeaders() {
|
|
193
|
+
const headers = typeof this.headers === 'function' ? this.headers() : this.headers;
|
|
194
|
+
if (!headers) {
|
|
195
|
+
return {};
|
|
196
|
+
}
|
|
197
|
+
if (Array.isArray(headers)) {
|
|
198
|
+
return Object.fromEntries(headers);
|
|
199
|
+
}
|
|
200
|
+
if ('forEach' in headers) {
|
|
201
|
+
const normalized = {};
|
|
202
|
+
headers.forEach((value, key) => {
|
|
203
|
+
normalized[key] = value;
|
|
204
|
+
});
|
|
205
|
+
return normalized;
|
|
206
|
+
}
|
|
207
|
+
return headers;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { type EmbedCoreEvents } from './events.js';
|
|
2
|
+
import type { TimerScheduler } from './RefreshScheduler.js';
|
|
3
|
+
import type { EmbedCoreLogger, EmbedSessionClient, EmbedSessionState, EmbedTrustLevel, IdentityResult, PlatformLifecycle } from './types.js';
|
|
4
|
+
export interface KomoEmbedSessionManagerOptions {
|
|
5
|
+
workspaceId?: string;
|
|
6
|
+
siteId?: string;
|
|
7
|
+
getIdentityToken?: () => Promise<IdentityResult | null> | IdentityResult | null;
|
|
8
|
+
client: EmbedSessionClient;
|
|
9
|
+
lifecycle?: PlatformLifecycle;
|
|
10
|
+
clock?: Pick<typeof Date, 'now'>;
|
|
11
|
+
refreshWindowMs?: number;
|
|
12
|
+
refreshJitterMs?: number;
|
|
13
|
+
timerScheduler?: TimerScheduler;
|
|
14
|
+
logger?: Pick<EmbedCoreLogger, 'debug' | 'info' | 'warn' | 'error'>;
|
|
15
|
+
}
|
|
16
|
+
export interface CurrentSession {
|
|
17
|
+
state: EmbedSessionState;
|
|
18
|
+
sessionToken?: string;
|
|
19
|
+
expiresAt?: Date;
|
|
20
|
+
contactId?: string;
|
|
21
|
+
trustLevel?: EmbedTrustLevel;
|
|
22
|
+
}
|
|
23
|
+
export declare class KomoEmbedSessionManager {
|
|
24
|
+
private readonly workspaceId?;
|
|
25
|
+
private readonly siteId?;
|
|
26
|
+
private readonly getIdentityToken?;
|
|
27
|
+
private readonly client;
|
|
28
|
+
private readonly clock;
|
|
29
|
+
private readonly refreshWindowMs;
|
|
30
|
+
private readonly refreshJitterMs;
|
|
31
|
+
private readonly timerScheduler;
|
|
32
|
+
private readonly logger?;
|
|
33
|
+
private readonly eventEmitter;
|
|
34
|
+
private readonly lifecycleUnsubscribers;
|
|
35
|
+
private currentSession;
|
|
36
|
+
private pendingExchange?;
|
|
37
|
+
private pendingRefresh?;
|
|
38
|
+
private refreshTimer?;
|
|
39
|
+
private isLoggedOut;
|
|
40
|
+
private isDisposed;
|
|
41
|
+
private exchangeVersion;
|
|
42
|
+
constructor(options: KomoEmbedSessionManagerOptions);
|
|
43
|
+
/**
|
|
44
|
+
* Returns an immutable snapshot of the current embed session state.
|
|
45
|
+
*/
|
|
46
|
+
getCurrentSession(): CurrentSession;
|
|
47
|
+
/**
|
|
48
|
+
* Returns a usable bearer token, refreshing or exchanging identity when needed.
|
|
49
|
+
* Returns null when auth is not configured, logged out, or disposed.
|
|
50
|
+
*/
|
|
51
|
+
getBearerToken(): Promise<string | null>;
|
|
52
|
+
/**
|
|
53
|
+
* Exchanges current host identity for an embed session and updates manager state.
|
|
54
|
+
*/
|
|
55
|
+
identifyContact(): Promise<CurrentSession>;
|
|
56
|
+
/**
|
|
57
|
+
* Clears the current session and prevents automatic re-authentication until
|
|
58
|
+
* identifyContact is called again.
|
|
59
|
+
*/
|
|
60
|
+
logout(): void;
|
|
61
|
+
/**
|
|
62
|
+
* Stops timers, removes lifecycle listeners, clears event subscribers, and
|
|
63
|
+
* ignores pending async exchange or refresh results.
|
|
64
|
+
*/
|
|
65
|
+
dispose(): void;
|
|
66
|
+
/**
|
|
67
|
+
* Subscribes to successful authenticated-session changes.
|
|
68
|
+
*/
|
|
69
|
+
onAuthenticated(callback: (payload: EmbedCoreEvents['authenticated']) => void): () => void;
|
|
70
|
+
/**
|
|
71
|
+
* Subscribes to session expiry events caused by logout or refresh failure.
|
|
72
|
+
*/
|
|
73
|
+
onSessionExpired(callback: (payload: EmbedCoreEvents['sessionExpired']) => void): () => void;
|
|
74
|
+
/**
|
|
75
|
+
* Subscribes to contact identity changes after exchange or merge.
|
|
76
|
+
*/
|
|
77
|
+
onIdentityChanged(callback: (payload: EmbedCoreEvents['identityChanged']) => void): () => void;
|
|
78
|
+
/**
|
|
79
|
+
* Subscribes to coarse session state transitions.
|
|
80
|
+
*/
|
|
81
|
+
onStateChanged(callback: (payload: EmbedCoreEvents['stateChanged']) => void): () => void;
|
|
82
|
+
/**
|
|
83
|
+
* True when the current session token can be reused without a refresh.
|
|
84
|
+
* Tokens inside the refresh window are treated as unusable so callers get a
|
|
85
|
+
* freshly exchanged token before expiry.
|
|
86
|
+
*/
|
|
87
|
+
private hasUsableToken;
|
|
88
|
+
/**
|
|
89
|
+
* True when the current session token has expired.
|
|
90
|
+
*/
|
|
91
|
+
private hasExpiredToken;
|
|
92
|
+
private exchangeIdentity;
|
|
93
|
+
private doExchangeIdentity;
|
|
94
|
+
private refreshSession;
|
|
95
|
+
private doRefreshSession;
|
|
96
|
+
private applySessionResponse;
|
|
97
|
+
private shouldFallbackToExchange;
|
|
98
|
+
private setSession;
|
|
99
|
+
private scheduleRefresh;
|
|
100
|
+
private cancelRefreshTimer;
|
|
101
|
+
private refreshFromLifecycle;
|
|
102
|
+
}
|
|
103
|
+
//# sourceMappingURL=KomoEmbedSessionManager.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"KomoEmbedSessionManager.d.ts","sourceRoot":"","sources":["../src/KomoEmbedSessionManager.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,377 @@
|
|
|
1
|
+
import { EmbedSessionExchangeError } from './errors.js';
|
|
2
|
+
import { TypedEventEmitter } from './events.js';
|
|
3
|
+
import { validateIdentityResult } from './identity.js';
|
|
4
|
+
import { DefaultTimerScheduler } from './RefreshScheduler.js';
|
|
5
|
+
const defaultRefreshWindowMs = 60_000;
|
|
6
|
+
const cloneSession = (session) => ({
|
|
7
|
+
...session,
|
|
8
|
+
...(session.expiresAt ? { expiresAt: new Date(session.expiresAt) } : {})
|
|
9
|
+
});
|
|
10
|
+
const parseExpiresAt = (expiresAt) => {
|
|
11
|
+
const date = new Date(expiresAt);
|
|
12
|
+
if (Number.isNaN(date.getTime())) {
|
|
13
|
+
throw new EmbedSessionExchangeError('Session response expiresAt is invalid', {
|
|
14
|
+
retryable: false
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
return date;
|
|
18
|
+
};
|
|
19
|
+
export class KomoEmbedSessionManager {
|
|
20
|
+
workspaceId;
|
|
21
|
+
siteId;
|
|
22
|
+
getIdentityToken;
|
|
23
|
+
client;
|
|
24
|
+
clock;
|
|
25
|
+
refreshWindowMs;
|
|
26
|
+
refreshJitterMs;
|
|
27
|
+
timerScheduler;
|
|
28
|
+
logger;
|
|
29
|
+
eventEmitter = new TypedEventEmitter();
|
|
30
|
+
lifecycleUnsubscribers = [];
|
|
31
|
+
currentSession = { state: 'UNAUTHENTICATED' };
|
|
32
|
+
pendingExchange;
|
|
33
|
+
pendingRefresh;
|
|
34
|
+
refreshTimer;
|
|
35
|
+
isLoggedOut = false;
|
|
36
|
+
isDisposed = false;
|
|
37
|
+
exchangeVersion = 0;
|
|
38
|
+
constructor(options) {
|
|
39
|
+
this.workspaceId = options.workspaceId;
|
|
40
|
+
this.siteId = options.siteId;
|
|
41
|
+
this.getIdentityToken = options.getIdentityToken;
|
|
42
|
+
this.client = options.client;
|
|
43
|
+
this.clock = options.clock ?? Date;
|
|
44
|
+
this.refreshWindowMs = options.refreshWindowMs ?? defaultRefreshWindowMs;
|
|
45
|
+
this.refreshJitterMs = options.refreshJitterMs ?? 0;
|
|
46
|
+
this.timerScheduler = options.timerScheduler ?? new DefaultTimerScheduler();
|
|
47
|
+
this.logger = options.logger;
|
|
48
|
+
if (options.lifecycle) {
|
|
49
|
+
this.lifecycleUnsubscribers.push(options.lifecycle.onForeground(() => {
|
|
50
|
+
this.refreshFromLifecycle();
|
|
51
|
+
}));
|
|
52
|
+
if (options.lifecycle.onBFCacheRestore) {
|
|
53
|
+
this.lifecycleUnsubscribers.push(options.lifecycle.onBFCacheRestore(() => {
|
|
54
|
+
this.refreshFromLifecycle();
|
|
55
|
+
}));
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Returns an immutable snapshot of the current embed session state.
|
|
61
|
+
*/
|
|
62
|
+
getCurrentSession() {
|
|
63
|
+
return cloneSession(this.currentSession);
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Returns a usable bearer token, refreshing or exchanging identity when needed.
|
|
67
|
+
* Returns null when auth is not configured, logged out, or disposed.
|
|
68
|
+
*/
|
|
69
|
+
async getBearerToken() {
|
|
70
|
+
if (this.isDisposed || this.isLoggedOut || !this.getIdentityToken) {
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
if (this.hasUsableToken()) {
|
|
74
|
+
return this.currentSession.sessionToken ?? null;
|
|
75
|
+
}
|
|
76
|
+
if (this.pendingExchange) {
|
|
77
|
+
const session = await this.pendingExchange;
|
|
78
|
+
return session.sessionToken ?? null;
|
|
79
|
+
}
|
|
80
|
+
if (!this.hasExpiredToken()) {
|
|
81
|
+
const session = await this.refreshSession();
|
|
82
|
+
return session.sessionToken ?? null;
|
|
83
|
+
}
|
|
84
|
+
const session = await this.exchangeIdentity();
|
|
85
|
+
return session.sessionToken ?? null;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Exchanges current host identity for an embed session and updates manager state.
|
|
89
|
+
*/
|
|
90
|
+
async identifyContact() {
|
|
91
|
+
if (this.isDisposed) {
|
|
92
|
+
return this.getCurrentSession();
|
|
93
|
+
}
|
|
94
|
+
this.isLoggedOut = false;
|
|
95
|
+
return this.exchangeIdentity();
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Clears the current session and prevents automatic re-authentication until
|
|
99
|
+
* identifyContact is called again.
|
|
100
|
+
*/
|
|
101
|
+
logout() {
|
|
102
|
+
const hadToken = Boolean(this.currentSession.sessionToken);
|
|
103
|
+
this.exchangeVersion += 1;
|
|
104
|
+
this.pendingExchange = undefined;
|
|
105
|
+
this.pendingRefresh = undefined;
|
|
106
|
+
this.isLoggedOut = true;
|
|
107
|
+
this.cancelRefreshTimer();
|
|
108
|
+
this.setSession({ state: 'LOGGED_OUT' });
|
|
109
|
+
if (hadToken) {
|
|
110
|
+
this.eventEmitter.emit('sessionExpired', { reason: 'logged_out' });
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Stops timers, removes lifecycle listeners, clears event subscribers, and
|
|
115
|
+
* ignores pending async exchange or refresh results.
|
|
116
|
+
*/
|
|
117
|
+
dispose() {
|
|
118
|
+
if (this.isDisposed) {
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
this.isDisposed = true;
|
|
122
|
+
this.exchangeVersion += 1;
|
|
123
|
+
this.pendingExchange = undefined;
|
|
124
|
+
this.pendingRefresh = undefined;
|
|
125
|
+
this.cancelRefreshTimer();
|
|
126
|
+
for (const unsubscribe of this.lifecycleUnsubscribers.splice(0)) {
|
|
127
|
+
unsubscribe();
|
|
128
|
+
}
|
|
129
|
+
this.eventEmitter.clear();
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Subscribes to successful authenticated-session changes.
|
|
133
|
+
*/
|
|
134
|
+
onAuthenticated(callback) {
|
|
135
|
+
return this.eventEmitter.on('authenticated', callback);
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Subscribes to session expiry events caused by logout or refresh failure.
|
|
139
|
+
*/
|
|
140
|
+
onSessionExpired(callback) {
|
|
141
|
+
return this.eventEmitter.on('sessionExpired', callback);
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Subscribes to contact identity changes after exchange or merge.
|
|
145
|
+
*/
|
|
146
|
+
onIdentityChanged(callback) {
|
|
147
|
+
return this.eventEmitter.on('identityChanged', callback);
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Subscribes to coarse session state transitions.
|
|
151
|
+
*/
|
|
152
|
+
onStateChanged(callback) {
|
|
153
|
+
return this.eventEmitter.on('stateChanged', callback);
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* True when the current session token can be reused without a refresh.
|
|
157
|
+
* Tokens inside the refresh window are treated as unusable so callers get a
|
|
158
|
+
* freshly exchanged token before expiry.
|
|
159
|
+
*/
|
|
160
|
+
hasUsableToken() {
|
|
161
|
+
if (!this.currentSession.sessionToken || !this.currentSession.expiresAt) {
|
|
162
|
+
return false;
|
|
163
|
+
}
|
|
164
|
+
return (this.currentSession.expiresAt.getTime() - this.clock.now() >
|
|
165
|
+
this.refreshWindowMs);
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* True when the current session token has expired.
|
|
169
|
+
*/
|
|
170
|
+
hasExpiredToken() {
|
|
171
|
+
if (!this.currentSession.sessionToken || !this.currentSession.expiresAt) {
|
|
172
|
+
return false;
|
|
173
|
+
}
|
|
174
|
+
return this.currentSession.expiresAt.getTime() <= this.clock.now();
|
|
175
|
+
}
|
|
176
|
+
exchangeIdentity() {
|
|
177
|
+
if (this.isDisposed) {
|
|
178
|
+
return Promise.resolve(this.getCurrentSession());
|
|
179
|
+
}
|
|
180
|
+
if (this.pendingExchange) {
|
|
181
|
+
return this.pendingExchange;
|
|
182
|
+
}
|
|
183
|
+
this.pendingRefresh = undefined;
|
|
184
|
+
const version = ++this.exchangeVersion;
|
|
185
|
+
const pendingExchange = this.doExchangeIdentity(version).finally(() => {
|
|
186
|
+
if (this.exchangeVersion === version) {
|
|
187
|
+
this.pendingExchange = undefined;
|
|
188
|
+
}
|
|
189
|
+
});
|
|
190
|
+
this.pendingExchange = pendingExchange;
|
|
191
|
+
return pendingExchange;
|
|
192
|
+
}
|
|
193
|
+
async doExchangeIdentity(version) {
|
|
194
|
+
if (!this.getIdentityToken) {
|
|
195
|
+
return this.getCurrentSession();
|
|
196
|
+
}
|
|
197
|
+
const previousSession = this.getCurrentSession();
|
|
198
|
+
this.setSession({ ...this.currentSession, state: 'AUTHENTICATING' });
|
|
199
|
+
try {
|
|
200
|
+
const identity = await this.getIdentityToken();
|
|
201
|
+
if (!identity) {
|
|
202
|
+
this.cancelRefreshTimer();
|
|
203
|
+
this.setSession({ state: 'UNAUTHENTICATED' });
|
|
204
|
+
return this.getCurrentSession();
|
|
205
|
+
}
|
|
206
|
+
if (this.exchangeVersion !== version ||
|
|
207
|
+
this.isLoggedOut ||
|
|
208
|
+
this.isDisposed) {
|
|
209
|
+
return this.getCurrentSession();
|
|
210
|
+
}
|
|
211
|
+
const validationErrors = validateIdentityResult({
|
|
212
|
+
workspaceId: this.workspaceId,
|
|
213
|
+
siteId: this.siteId,
|
|
214
|
+
identity
|
|
215
|
+
});
|
|
216
|
+
if (validationErrors.length > 0) {
|
|
217
|
+
throw new EmbedSessionExchangeError(validationErrors.map((error) => error.message).join('; '), {
|
|
218
|
+
retryable: false
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
const response = await this.client.exchange({
|
|
222
|
+
workspaceId: this.workspaceId,
|
|
223
|
+
...(this.siteId ? { siteId: this.siteId } : {}),
|
|
224
|
+
identity
|
|
225
|
+
});
|
|
226
|
+
if (this.exchangeVersion !== version ||
|
|
227
|
+
this.isLoggedOut ||
|
|
228
|
+
this.isDisposed) {
|
|
229
|
+
return this.getCurrentSession();
|
|
230
|
+
}
|
|
231
|
+
this.applySessionResponse(response, previousSession);
|
|
232
|
+
return this.getCurrentSession();
|
|
233
|
+
}
|
|
234
|
+
catch (error) {
|
|
235
|
+
if (this.exchangeVersion !== version ||
|
|
236
|
+
this.isLoggedOut ||
|
|
237
|
+
this.isDisposed) {
|
|
238
|
+
return this.getCurrentSession();
|
|
239
|
+
}
|
|
240
|
+
this.cancelRefreshTimer();
|
|
241
|
+
this.setSession({ state: 'FAILED' });
|
|
242
|
+
if (previousSession.sessionToken) {
|
|
243
|
+
this.eventEmitter.emit('sessionExpired', { reason: 'refresh_failed' });
|
|
244
|
+
}
|
|
245
|
+
this.logger?.warn('Komo embed session exchange failed');
|
|
246
|
+
throw error;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
refreshSession() {
|
|
250
|
+
if (this.isDisposed) {
|
|
251
|
+
return Promise.resolve(this.getCurrentSession());
|
|
252
|
+
}
|
|
253
|
+
if (this.pendingRefresh) {
|
|
254
|
+
return this.pendingRefresh;
|
|
255
|
+
}
|
|
256
|
+
const version = this.exchangeVersion;
|
|
257
|
+
const pendingRefresh = this.doRefreshSession(version).finally(() => {
|
|
258
|
+
if (this.exchangeVersion === version) {
|
|
259
|
+
this.pendingRefresh = undefined;
|
|
260
|
+
}
|
|
261
|
+
});
|
|
262
|
+
this.pendingRefresh = pendingRefresh;
|
|
263
|
+
return pendingRefresh;
|
|
264
|
+
}
|
|
265
|
+
async doRefreshSession(version) {
|
|
266
|
+
const previousSession = this.getCurrentSession();
|
|
267
|
+
const sessionToken = previousSession.sessionToken;
|
|
268
|
+
if (!sessionToken) {
|
|
269
|
+
return this.exchangeIdentity();
|
|
270
|
+
}
|
|
271
|
+
try {
|
|
272
|
+
const response = await this.client.refresh({ sessionToken });
|
|
273
|
+
if (this.exchangeVersion !== version ||
|
|
274
|
+
this.isLoggedOut ||
|
|
275
|
+
this.isDisposed) {
|
|
276
|
+
return this.getCurrentSession();
|
|
277
|
+
}
|
|
278
|
+
this.applySessionResponse(response, previousSession);
|
|
279
|
+
return this.getCurrentSession();
|
|
280
|
+
}
|
|
281
|
+
catch (error) {
|
|
282
|
+
if (this.exchangeVersion !== version ||
|
|
283
|
+
this.isLoggedOut ||
|
|
284
|
+
this.isDisposed) {
|
|
285
|
+
return this.getCurrentSession();
|
|
286
|
+
}
|
|
287
|
+
if (this.shouldFallbackToExchange(error)) {
|
|
288
|
+
return this.exchangeIdentity();
|
|
289
|
+
}
|
|
290
|
+
this.cancelRefreshTimer();
|
|
291
|
+
this.setSession({
|
|
292
|
+
state: 'FAILED',
|
|
293
|
+
contactId: previousSession.contactId,
|
|
294
|
+
trustLevel: previousSession.trustLevel
|
|
295
|
+
});
|
|
296
|
+
this.eventEmitter.emit('sessionExpired', { reason: 'refresh_failed' });
|
|
297
|
+
this.logger?.warn('Komo embed session refresh failed');
|
|
298
|
+
throw error;
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
applySessionResponse(response, previousSession) {
|
|
302
|
+
const nextState = response.trustLevel === 'Anonymous' ? 'ANONYMOUS' : 'AUTHENTICATED';
|
|
303
|
+
const nextSession = {
|
|
304
|
+
state: nextState,
|
|
305
|
+
sessionToken: response.sessionToken,
|
|
306
|
+
expiresAt: parseExpiresAt(response.expiresAt),
|
|
307
|
+
contactId: response.contactId,
|
|
308
|
+
trustLevel: response.trustLevel
|
|
309
|
+
};
|
|
310
|
+
const previousContactId = previousSession.contactId;
|
|
311
|
+
this.setSession(nextSession);
|
|
312
|
+
this.scheduleRefresh();
|
|
313
|
+
if (nextState === 'AUTHENTICATED' &&
|
|
314
|
+
(previousSession.state !== 'AUTHENTICATED' ||
|
|
315
|
+
previousContactId !== response.contactId ||
|
|
316
|
+
previousSession.trustLevel !== response.trustLevel)) {
|
|
317
|
+
this.eventEmitter.emit('authenticated', {
|
|
318
|
+
contactId: response.contactId,
|
|
319
|
+
trustLevel: response.trustLevel
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
if (previousContactId !== response.contactId) {
|
|
323
|
+
this.eventEmitter.emit('identityChanged', {
|
|
324
|
+
previousContactId,
|
|
325
|
+
contactId: response.contactId,
|
|
326
|
+
reason: response.mergedFromContactId ? 'merge' : 'exchange'
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
shouldFallbackToExchange(error) {
|
|
331
|
+
return (error instanceof EmbedSessionExchangeError &&
|
|
332
|
+
(error.failureReason === 'TokenExpired' ||
|
|
333
|
+
error.failureReason === 'TokenInvalid'));
|
|
334
|
+
}
|
|
335
|
+
setSession(nextSession) {
|
|
336
|
+
const previousState = this.currentSession.state;
|
|
337
|
+
this.currentSession = cloneSession(nextSession);
|
|
338
|
+
if (previousState !== nextSession.state) {
|
|
339
|
+
this.eventEmitter.emit('stateChanged', {
|
|
340
|
+
previousState,
|
|
341
|
+
state: nextSession.state
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
scheduleRefresh() {
|
|
346
|
+
this.cancelRefreshTimer();
|
|
347
|
+
if (this.isDisposed ||
|
|
348
|
+
this.isLoggedOut ||
|
|
349
|
+
this.currentSession.state === 'FAILED' ||
|
|
350
|
+
!this.currentSession.sessionToken ||
|
|
351
|
+
!this.currentSession.expiresAt ||
|
|
352
|
+
!this.getIdentityToken) {
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
const delayMs = Math.max(0, this.currentSession.expiresAt.getTime() -
|
|
356
|
+
this.clock.now() -
|
|
357
|
+
this.refreshWindowMs -
|
|
358
|
+
this.refreshJitterMs);
|
|
359
|
+
this.refreshTimer = this.timerScheduler.set(delayMs, () => {
|
|
360
|
+
this.getBearerToken().catch(() => {
|
|
361
|
+
this.logger?.warn('Komo embed session refresh failed');
|
|
362
|
+
});
|
|
363
|
+
});
|
|
364
|
+
}
|
|
365
|
+
cancelRefreshTimer() {
|
|
366
|
+
this.refreshTimer?.cancel();
|
|
367
|
+
this.refreshTimer = undefined;
|
|
368
|
+
}
|
|
369
|
+
refreshFromLifecycle() {
|
|
370
|
+
if (this.isLoggedOut || !this.currentSession.sessionToken) {
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
this.getBearerToken().catch(() => {
|
|
374
|
+
this.logger?.warn('Komo embed session lifecycle refresh failed');
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export interface TimerHandle {
|
|
2
|
+
cancel(): void;
|
|
3
|
+
}
|
|
4
|
+
export interface TimerScheduler {
|
|
5
|
+
set(delayMs: number, callback: () => void): TimerHandle;
|
|
6
|
+
}
|
|
7
|
+
export declare class DefaultTimerScheduler implements TimerScheduler {
|
|
8
|
+
set(delayMs: number, callback: () => void): TimerHandle;
|
|
9
|
+
}
|
|
10
|
+
//# sourceMappingURL=RefreshScheduler.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"RefreshScheduler.d.ts","sourceRoot":"","sources":["../src/RefreshScheduler.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,WAAW;IAC1B,MAAM,IAAI,IAAI,CAAC;CAChB;AAED,MAAM,WAAW,cAAc;IAC7B,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,IAAI,GAAG,WAAW,CAAC;CACzD;AAED,qBAAa,qBAAsB,YAAW,cAAc;IAC1D,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,IAAI,GAAG,WAAW;CASxD"}
|
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { ExchangeFailureReason } from './types.js';
|
|
2
|
+
interface EmbedSessionExchangeErrorOptions {
|
|
3
|
+
status?: number;
|
|
4
|
+
failureReason?: ExchangeFailureReason;
|
|
5
|
+
retryable: boolean;
|
|
6
|
+
cause?: unknown;
|
|
7
|
+
}
|
|
8
|
+
export declare class EmbedSessionExchangeError extends Error {
|
|
9
|
+
readonly status?: number;
|
|
10
|
+
readonly failureReason?: ExchangeFailureReason;
|
|
11
|
+
readonly retryable: boolean;
|
|
12
|
+
constructor(message: string, options: EmbedSessionExchangeErrorOptions);
|
|
13
|
+
}
|
|
14
|
+
export {};
|
|
15
|
+
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAExD,UAAU,gCAAgC;IACxC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,aAAa,CAAC,EAAE,qBAAqB,CAAC;IACtC,SAAS,EAAE,OAAO,CAAC;IACnB,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED,qBAAa,yBAA0B,SAAQ,KAAK;IAClD,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,aAAa,CAAC,EAAE,qBAAqB,CAAC;IAC/C,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;gBAEhB,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,gCAAgC;CAOvE"}
|
package/dist/errors.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export class EmbedSessionExchangeError extends Error {
|
|
2
|
+
status;
|
|
3
|
+
failureReason;
|
|
4
|
+
retryable;
|
|
5
|
+
constructor(message, options) {
|
|
6
|
+
super(message, { cause: options.cause });
|
|
7
|
+
this.name = 'EmbedSessionExchangeError';
|
|
8
|
+
this.status = options.status;
|
|
9
|
+
this.failureReason = options.failureReason;
|
|
10
|
+
this.retryable = options.retryable;
|
|
11
|
+
}
|
|
12
|
+
}
|
package/dist/events.d.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { EmbedSessionState, EmbedTrustLevel } from './types.js';
|
|
2
|
+
export type EmbedCoreEvents = {
|
|
3
|
+
authenticated: {
|
|
4
|
+
contactId: string;
|
|
5
|
+
trustLevel: EmbedTrustLevel;
|
|
6
|
+
};
|
|
7
|
+
sessionExpired: {
|
|
8
|
+
reason: 'expired' | 'refresh_failed' | 'logged_out';
|
|
9
|
+
};
|
|
10
|
+
identityChanged: {
|
|
11
|
+
previousContactId?: string;
|
|
12
|
+
contactId: string;
|
|
13
|
+
reason: 'exchange' | 'merge';
|
|
14
|
+
};
|
|
15
|
+
stateChanged: {
|
|
16
|
+
previousState: EmbedSessionState;
|
|
17
|
+
state: EmbedSessionState;
|
|
18
|
+
};
|
|
19
|
+
};
|
|
20
|
+
type EventCallback<Payload> = (payload: Payload) => void;
|
|
21
|
+
export declare class TypedEventEmitter<Events extends Record<string, unknown>> {
|
|
22
|
+
private readonly listeners;
|
|
23
|
+
on<EventName extends keyof Events>(eventName: EventName, callback: EventCallback<Events[EventName]>): () => void;
|
|
24
|
+
emit<EventName extends keyof Events>(eventName: EventName, payload: Events[EventName]): void;
|
|
25
|
+
clear(): void;
|
|
26
|
+
}
|
|
27
|
+
export {};
|
|
28
|
+
//# sourceMappingURL=events.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"events.d.ts","sourceRoot":"","sources":["../src/events.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAErE,MAAM,MAAM,eAAe,GAAG;IAC5B,aAAa,EAAE;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,eAAe,CAAA;KAAE,CAAC;IAClE,cAAc,EAAE;QAAE,MAAM,EAAE,SAAS,GAAG,gBAAgB,GAAG,YAAY,CAAA;KAAE,CAAC;IACxE,eAAe,EAAE;QACf,iBAAiB,CAAC,EAAE,MAAM,CAAC;QAC3B,SAAS,EAAE,MAAM,CAAC;QAClB,MAAM,EAAE,UAAU,GAAG,OAAO,CAAC;KAC9B,CAAC;IACF,YAAY,EAAE;QAAE,aAAa,EAAE,iBAAiB,CAAC;QAAC,KAAK,EAAE,iBAAiB,CAAA;KAAE,CAAC;CAC9E,CAAC;AAEF,KAAK,aAAa,CAAC,OAAO,IAAI,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,CAAC;AAMzD,qBAAa,iBAAiB,CAAC,MAAM,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IACnE,OAAO,CAAC,QAAQ,CAAC,SAAS,CAGtB;IAEJ,EAAE,CAAC,SAAS,SAAS,MAAM,MAAM,EAC/B,SAAS,EAAE,SAAS,EACpB,QAAQ,EAAE,aAAa,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,GACzC,MAAM,IAAI;IAcb,IAAI,CAAC,SAAS,SAAS,MAAM,MAAM,EACjC,SAAS,EAAE,SAAS,EACpB,OAAO,EAAE,MAAM,CAAC,SAAS,CAAC,GACzB,IAAI;IAWP,KAAK,IAAI,IAAI;CAGd"}
|
package/dist/events.js
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export class TypedEventEmitter {
|
|
2
|
+
listeners = new Map();
|
|
3
|
+
on(eventName, callback) {
|
|
4
|
+
const listeners = this.listeners.get(eventName) ?? new Set();
|
|
5
|
+
listeners.add(callback);
|
|
6
|
+
this.listeners.set(eventName, listeners);
|
|
7
|
+
return () => {
|
|
8
|
+
listeners.delete(callback);
|
|
9
|
+
if (listeners.size === 0) {
|
|
10
|
+
this.listeners.delete(eventName);
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
emit(eventName, payload) {
|
|
15
|
+
const listeners = this.listeners.get(eventName);
|
|
16
|
+
if (!listeners) {
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
for (const listener of listeners) {
|
|
20
|
+
listener(payload);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
clear() {
|
|
24
|
+
this.listeners.clear();
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { IdentityResult } from './types.js';
|
|
2
|
+
export interface IdentityValidationError {
|
|
3
|
+
code: 'missing_workspace_id' | 'missing_site_id' | 'missing_required_field';
|
|
4
|
+
message: string;
|
|
5
|
+
}
|
|
6
|
+
interface ValidateIdentityResultInput {
|
|
7
|
+
workspaceId?: string;
|
|
8
|
+
siteId?: string;
|
|
9
|
+
identity: IdentityResult;
|
|
10
|
+
}
|
|
11
|
+
export declare const validateIdentityResult: ({ workspaceId, siteId, identity }: ValidateIdentityResultInput) => IdentityValidationError[];
|
|
12
|
+
export {};
|
|
13
|
+
//# sourceMappingURL=identity.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"identity.d.ts","sourceRoot":"","sources":["../src/identity.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAEjD,MAAM,WAAW,uBAAuB;IACtC,IAAI,EAAE,sBAAsB,GAAG,iBAAiB,GAAG,wBAAwB,CAAC;IAC5E,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,UAAU,2BAA2B;IACnC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,cAAc,CAAC;CAC1B;AAaD,eAAO,MAAM,sBAAsB,GAAI,mCAIpC,2BAA2B,KAAG,uBAAuB,EA0CvD,CAAC"}
|
package/dist/identity.js
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
const hasText = (value) => typeof value === 'string' && value.trim().length > 0;
|
|
2
|
+
const hasAttributes = (attributes) => Object.keys(attributes).length > 0;
|
|
3
|
+
const missingField = (field) => ({
|
|
4
|
+
code: 'missing_required_field',
|
|
5
|
+
message: `Missing required identity field: ${field}`
|
|
6
|
+
});
|
|
7
|
+
export const validateIdentityResult = ({ workspaceId, siteId, identity }) => {
|
|
8
|
+
const errors = [];
|
|
9
|
+
if (!hasText(workspaceId)) {
|
|
10
|
+
errors.push({
|
|
11
|
+
code: 'missing_workspace_id',
|
|
12
|
+
message: 'workspaceId is required'
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
switch (identity.type) {
|
|
16
|
+
case 'jwt':
|
|
17
|
+
if (!hasText(siteId)) {
|
|
18
|
+
errors.push({
|
|
19
|
+
code: 'missing_site_id',
|
|
20
|
+
message: 'siteId is required for jwt identity exchange'
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
if (!hasText(identity.token)) {
|
|
24
|
+
errors.push(missingField('token'));
|
|
25
|
+
}
|
|
26
|
+
break;
|
|
27
|
+
case 'email':
|
|
28
|
+
if (!hasAttributes(identity.attributes)) {
|
|
29
|
+
errors.push(missingField('attributes'));
|
|
30
|
+
}
|
|
31
|
+
break;
|
|
32
|
+
case 'oauth2':
|
|
33
|
+
if (!hasText(identity.token)) {
|
|
34
|
+
errors.push(missingField('token'));
|
|
35
|
+
}
|
|
36
|
+
break;
|
|
37
|
+
case 'custom':
|
|
38
|
+
if (!hasText(identity.provider)) {
|
|
39
|
+
errors.push(missingField('provider'));
|
|
40
|
+
}
|
|
41
|
+
break;
|
|
42
|
+
case 'anonymous':
|
|
43
|
+
break;
|
|
44
|
+
}
|
|
45
|
+
return errors;
|
|
46
|
+
};
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export type { EmbedAuthType, EmbedCustomProvider, EmbedSessionClient, EmbedSessionExchangeRequest, EmbedSessionExchangeResponse, EmbedSessionRefreshRequest, EmbedSessionRefreshResponse, EmbedSessionState, EmbedTrustLevel, ExchangeFailureReason, EmbedCoreLogger, IdentityResult, PlatformLifecycle, PortableAbortSignal } from './types.js';
|
|
2
|
+
export type { IdentityValidationError } from './identity.js';
|
|
3
|
+
export { validateIdentityResult } from './identity.js';
|
|
4
|
+
export { EmbedSessionExchangeError } from './errors.js';
|
|
5
|
+
export type { EmbedCoreEvents } from './events.js';
|
|
6
|
+
export type { FetchEmbedSessionClientOptions, PortableFetch, PortableFetchRequestInit, PortableFetchResponse, PortableHeadersInit, RuntimeFetch } from './FetchEmbedSessionClient.js';
|
|
7
|
+
export { FetchEmbedSessionClient } from './FetchEmbedSessionClient.js';
|
|
8
|
+
export type { CurrentSession, KomoEmbedSessionManagerOptions } from './KomoEmbedSessionManager.js';
|
|
9
|
+
export { KomoEmbedSessionManager } from './KomoEmbedSessionManager.js';
|
|
10
|
+
export type { TimerHandle, TimerScheduler } from './RefreshScheduler.js';
|
|
11
|
+
export { DefaultTimerScheduler } from './RefreshScheduler.js';
|
|
12
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,aAAa,EACb,mBAAmB,EACnB,kBAAkB,EAClB,2BAA2B,EAC3B,4BAA4B,EAC5B,0BAA0B,EAC1B,2BAA2B,EAC3B,iBAAiB,EACjB,eAAe,EACf,qBAAqB,EACrB,eAAe,EACf,cAAc,EACd,iBAAiB,EACjB,mBAAmB,EACpB,MAAM,YAAY,CAAC;AACpB,YAAY,EAAE,uBAAuB,EAAE,MAAM,eAAe,CAAC;AAC7D,OAAO,EAAE,sBAAsB,EAAE,MAAM,eAAe,CAAC;AACvD,OAAO,EAAE,yBAAyB,EAAE,MAAM,aAAa,CAAC;AACxD,YAAY,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AACnD,YAAY,EACV,8BAA8B,EAC9B,aAAa,EACb,wBAAwB,EACxB,qBAAqB,EACrB,mBAAmB,EACnB,YAAY,EACb,MAAM,8BAA8B,CAAC;AACtC,OAAO,EAAE,uBAAuB,EAAE,MAAM,8BAA8B,CAAC;AACvE,YAAY,EACV,cAAc,EACd,8BAA8B,EAC/B,MAAM,8BAA8B,CAAC;AACtC,OAAO,EAAE,uBAAuB,EAAE,MAAM,8BAA8B,CAAC;AACvE,YAAY,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACzE,OAAO,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { validateIdentityResult } from './identity.js';
|
|
2
|
+
export { EmbedSessionExchangeError } from './errors.js';
|
|
3
|
+
export { FetchEmbedSessionClient } from './FetchEmbedSessionClient.js';
|
|
4
|
+
export { KomoEmbedSessionManager } from './KomoEmbedSessionManager.js';
|
|
5
|
+
export { DefaultTimerScheduler } from './RefreshScheduler.js';
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
export type EmbedAuthType = 'jwt' | 'email' | 'oauth2' | 'custom' | 'anonymous';
|
|
2
|
+
export type EmbedCustomProvider = string;
|
|
3
|
+
export type EmbedTrustLevel = 'Anonymous' | 'Identified' | 'Verified';
|
|
4
|
+
export interface PortableAbortSignal {
|
|
5
|
+
readonly aborted: boolean;
|
|
6
|
+
addEventListener?(type: 'abort', listener: () => void, options?: {
|
|
7
|
+
once?: boolean;
|
|
8
|
+
}): void;
|
|
9
|
+
removeEventListener?(type: 'abort', listener: () => void): void;
|
|
10
|
+
}
|
|
11
|
+
export interface EmbedCoreLogger {
|
|
12
|
+
debug(message: string, ...data: unknown[]): void;
|
|
13
|
+
info(message: string, ...data: unknown[]): void;
|
|
14
|
+
warn(message: string, ...data: unknown[]): void;
|
|
15
|
+
error(message: string, ...data: unknown[]): void;
|
|
16
|
+
}
|
|
17
|
+
export type EmbedSessionState = 'UNAUTHENTICATED' | 'AUTHENTICATING' | 'AUTHENTICATED' | 'ANONYMOUS' | 'FAILED' | 'LOGGED_OUT';
|
|
18
|
+
export type IdentityResult = {
|
|
19
|
+
type: 'jwt';
|
|
20
|
+
token: string;
|
|
21
|
+
} | {
|
|
22
|
+
type: 'email';
|
|
23
|
+
attributes: Record<string, string>;
|
|
24
|
+
} | {
|
|
25
|
+
type: 'oauth2';
|
|
26
|
+
token: string;
|
|
27
|
+
} | ({
|
|
28
|
+
type: 'custom';
|
|
29
|
+
provider: EmbedCustomProvider;
|
|
30
|
+
} & Record<string, unknown>) | {
|
|
31
|
+
type: 'anonymous';
|
|
32
|
+
};
|
|
33
|
+
export interface EmbedSessionExchangeRequest {
|
|
34
|
+
workspaceId: string;
|
|
35
|
+
siteId?: string;
|
|
36
|
+
identity: IdentityResult;
|
|
37
|
+
}
|
|
38
|
+
export interface EmbedSessionExchangeResponse {
|
|
39
|
+
sessionToken: string;
|
|
40
|
+
expiresAt: string;
|
|
41
|
+
contactId: string;
|
|
42
|
+
trustLevel: EmbedTrustLevel;
|
|
43
|
+
mergedFromContactId?: string;
|
|
44
|
+
}
|
|
45
|
+
export interface EmbedSessionRefreshRequest {
|
|
46
|
+
sessionToken: string;
|
|
47
|
+
}
|
|
48
|
+
export interface EmbedSessionRefreshResponse {
|
|
49
|
+
sessionToken: string;
|
|
50
|
+
expiresAt: string;
|
|
51
|
+
contactId: string;
|
|
52
|
+
trustLevel: EmbedTrustLevel;
|
|
53
|
+
mergedFromContactId?: string;
|
|
54
|
+
}
|
|
55
|
+
export type ExchangeFailureReason = 'WorkspaceNotFound' | 'SiteNotFound' | 'TypeNotPermitted' | 'TokenInvalid' | 'AttendeeNotFound' | 'UpstreamUnavailable' | 'OAuth2NotSupported' | 'ContactResolutionFailed' | 'TokenExpired' | 'InternalError';
|
|
56
|
+
export interface EmbedSessionClient {
|
|
57
|
+
exchange(request: EmbedSessionExchangeRequest, signal?: PortableAbortSignal): Promise<EmbedSessionExchangeResponse>;
|
|
58
|
+
refresh(request: EmbedSessionRefreshRequest, signal?: PortableAbortSignal): Promise<EmbedSessionRefreshResponse>;
|
|
59
|
+
}
|
|
60
|
+
export interface PlatformLifecycle {
|
|
61
|
+
onForeground(callback: () => void): () => void;
|
|
62
|
+
onBFCacheRestore?(callback: () => void): () => void;
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@komo-tech/embed-core",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": false,
|
|
5
|
+
"description": "Platform-neutral session core for Komo embeds.",
|
|
6
|
+
"license": "UNLICENSED",
|
|
7
|
+
"homepage": "https://developers.komo.tech/embedding",
|
|
8
|
+
"bugs": {
|
|
9
|
+
"email": "support@komo.tech"
|
|
10
|
+
},
|
|
11
|
+
"author": "Komo <support@komo.tech> (https://www.komo.tech/)",
|
|
12
|
+
"repository": "https://www.komo.tech/",
|
|
13
|
+
"files": [
|
|
14
|
+
"dist",
|
|
15
|
+
"README.md"
|
|
16
|
+
],
|
|
17
|
+
"type": "module",
|
|
18
|
+
"sideEffects": false,
|
|
19
|
+
"main": "./dist/index.js",
|
|
20
|
+
"module": "./dist/index.js",
|
|
21
|
+
"types": "./dist/index.d.ts",
|
|
22
|
+
"exports": {
|
|
23
|
+
".": {
|
|
24
|
+
"types": "./dist/index.d.ts",
|
|
25
|
+
"import": "./dist/index.js",
|
|
26
|
+
"default": "./dist/index.js"
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
"publishConfig": {
|
|
30
|
+
"access": "public",
|
|
31
|
+
"registry": "https://registry.npmjs.org/"
|
|
32
|
+
},
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"@types/node": "^25.6.0",
|
|
35
|
+
"@vitest/coverage-v8": "4.1.0",
|
|
36
|
+
"oxfmt": "^0.46.0",
|
|
37
|
+
"oxlint": "^1.61.0",
|
|
38
|
+
"oxlint-tsgolint": "^0.21.1",
|
|
39
|
+
"typescript": "5.9.3",
|
|
40
|
+
"vitest": "4.1.0",
|
|
41
|
+
"@komo-tech/oxlint-config": "0.0.0",
|
|
42
|
+
"@komo-tech/typescript-config": "0.0.0"
|
|
43
|
+
},
|
|
44
|
+
"scripts": {
|
|
45
|
+
"build": "pnpm run clean:dist && tsc -p tsconfig.build.json",
|
|
46
|
+
"clean": "rm -rf dist node_modules",
|
|
47
|
+
"clean:dist": "rm -rf dist",
|
|
48
|
+
"dev": "tsc -p tsconfig.build.json --watch --preserveWatchOutput",
|
|
49
|
+
"format": "oxfmt src || true",
|
|
50
|
+
"format:check": "oxfmt --check src || true",
|
|
51
|
+
"lint": "oxlint --type-aware --type-check",
|
|
52
|
+
"lint:ci": "oxlint --type-aware --type-check --quiet --format=github",
|
|
53
|
+
"lint:errors": "oxlint --type-aware --type-check --quiet",
|
|
54
|
+
"lint:fix": "oxlint --type-aware --fix",
|
|
55
|
+
"test": "vitest run",
|
|
56
|
+
"test:ci": "vitest run --coverage --reporter=github-actions --reporter=junit --reporter=default --outputFile.junit=./test-reports/test-embed-core.xml"
|
|
57
|
+
}
|
|
58
|
+
}
|