@wetransform/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 +93 -0
- package/dist/index.d.mts +64 -0
- package/dist/index.mjs +850 -0
- package/package.json +33 -0
package/README.md
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# @wetransform/core
|
|
2
|
+
|
|
3
|
+
Headless browser SDK to open WeTransform in your app (modal or inline), authenticate, and interact through a typed event API.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @wetransform/core
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
import { createWeTransform } from '@wetransform/core'
|
|
15
|
+
|
|
16
|
+
const sdk = createWeTransform({
|
|
17
|
+
organizationHandle: 'acme',
|
|
18
|
+
locale: 'en',
|
|
19
|
+
displayAsModal: true,
|
|
20
|
+
initialLocation: 'transformations',
|
|
21
|
+
authentication: {
|
|
22
|
+
customerId: 'ext-123',
|
|
23
|
+
signature: 'signed-payload',
|
|
24
|
+
templateHandle: 'demo-template',
|
|
25
|
+
sourceId: 'newsletter',
|
|
26
|
+
},
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
// Subscribing to the successSubmit event
|
|
30
|
+
sdk.on('successSubmit', (payload) => console.log('File successfully submitted', payload))
|
|
31
|
+
|
|
32
|
+
// Opening WeTransform
|
|
33
|
+
await sdk.open()
|
|
34
|
+
// Close WeTransform
|
|
35
|
+
await sdk.close()
|
|
36
|
+
// Destroy WeTransform session
|
|
37
|
+
await sdk.destroy()
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## API
|
|
41
|
+
|
|
42
|
+
### `createWeTransform(config)`
|
|
43
|
+
|
|
44
|
+
Returns a `WeTransformInstance` with:
|
|
45
|
+
|
|
46
|
+
- `open(): Promise<void>` - initializes auth and mounts WeTransform
|
|
47
|
+
- `close(): Promise<void>` - closes SDK UI and unmounts WeTransform
|
|
48
|
+
- `destroy(): Promise<void>` - closes and tears down SDK resources
|
|
49
|
+
- `on(event, handler): () => void` - subscribes to SDK events, returns unsubscribe function
|
|
50
|
+
|
|
51
|
+
### `WeTransformConfig`
|
|
52
|
+
|
|
53
|
+
- `organizationHandle: string`
|
|
54
|
+
- `locale?: 'en' | 'fr'`
|
|
55
|
+
- `authentication: WeTransformAuthenticationConfig`
|
|
56
|
+
- `initialLocation?: 'transformations' | 'uploader' | 'mapper' | 'finalize' | 'sourcePreview' | 'sourceUpdate' | 'scheduler'`
|
|
57
|
+
- `displayAsModal?: boolean` (default: `true`)
|
|
58
|
+
- `mountElement?: string | HTMLElement` (default id: `weTransform_iframeContainer`, used when `displayAsModal` is `false`)
|
|
59
|
+
|
|
60
|
+
Some `initialLocation` values require additional auth fields:
|
|
61
|
+
|
|
62
|
+
- `uploader`: requires `authentication.templateHandle`
|
|
63
|
+
- `mapper`, `finalize`, `sourcePreview`, `sourceUpdate`, `scheduler`: require both `authentication.templateHandle` and `authentication.sourceId`
|
|
64
|
+
|
|
65
|
+
### `WeTransformAuthenticationConfig`
|
|
66
|
+
|
|
67
|
+
- `customerId: string`
|
|
68
|
+
- `signature: string`
|
|
69
|
+
- `templateHandle?: string`
|
|
70
|
+
- `sourceId?: string`
|
|
71
|
+
|
|
72
|
+
## Events
|
|
73
|
+
|
|
74
|
+
Supported `sdk.on(...)` events:
|
|
75
|
+
|
|
76
|
+
- `open`
|
|
77
|
+
- `close`
|
|
78
|
+
- `destroy`
|
|
79
|
+
- `onReady`
|
|
80
|
+
- `successSubmit` with payload
|
|
81
|
+
|
|
82
|
+
```ts
|
|
83
|
+
{
|
|
84
|
+
customerId: string
|
|
85
|
+
templateHandle: string
|
|
86
|
+
fileUrl: string
|
|
87
|
+
signature: string
|
|
88
|
+
// Deprecated
|
|
89
|
+
redirectUrl: string
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
- `error` with payload `{ code: string; message: string }`
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
//#region src/types.d.ts
|
|
2
|
+
type WeTransformSdkConfigBase = {
|
|
3
|
+
organizationHandle: string;
|
|
4
|
+
locale?: SupportedLocales;
|
|
5
|
+
displayAsModal?: boolean;
|
|
6
|
+
mountElement?: string | HTMLElement;
|
|
7
|
+
};
|
|
8
|
+
type WeTransformSdkConfigWithoutInitialLocation = WeTransformSdkConfigBase & {
|
|
9
|
+
initialLocation?: undefined;
|
|
10
|
+
authentication: WeTransformAuthenticationConfig;
|
|
11
|
+
};
|
|
12
|
+
type WeTransformConfig = WeTransformSdkConfigWithoutInitialLocation | { [L in WeTransformInitialLocations]: {
|
|
13
|
+
organizationHandle: WeTransformSdkConfigBase['organizationHandle'];
|
|
14
|
+
initialLocation: L;
|
|
15
|
+
authentication: AuthenticationConfigForLocation<L>;
|
|
16
|
+
} & WeTransformSdkConfigBase }[WeTransformInitialLocations];
|
|
17
|
+
type WeTransformAuthenticationConfig = {
|
|
18
|
+
customerId: string;
|
|
19
|
+
signature: string;
|
|
20
|
+
templateHandle?: string;
|
|
21
|
+
sourceId?: string;
|
|
22
|
+
};
|
|
23
|
+
type AuthenticationConfigRequiringTemplateHandle = WeTransformAuthenticationConfig & {
|
|
24
|
+
templateHandle: string;
|
|
25
|
+
};
|
|
26
|
+
type AuthenticationConfigRequiringTemplateAndSource = AuthenticationConfigRequiringTemplateHandle & {
|
|
27
|
+
sourceId: string;
|
|
28
|
+
};
|
|
29
|
+
type WeTransformInitialLocations = 'transformations' | 'uploader' | 'mapper' | 'finalize' | 'sourcePreview' | 'sourceUpdate' | 'scheduler';
|
|
30
|
+
type LocationsRequiringTemplateHandle = 'uploader';
|
|
31
|
+
type LocationsRequiringTemplateAndSource = 'mapper' | 'finalize' | 'sourcePreview' | 'sourceUpdate' | 'scheduler';
|
|
32
|
+
type AuthenticationConfigForLocation<L extends WeTransformInitialLocations> = L extends LocationsRequiringTemplateAndSource ? AuthenticationConfigRequiringTemplateAndSource : L extends LocationsRequiringTemplateHandle ? AuthenticationConfigRequiringTemplateHandle : WeTransformAuthenticationConfig;
|
|
33
|
+
type WeTransformEventMap = {
|
|
34
|
+
open: () => void;
|
|
35
|
+
close: () => void;
|
|
36
|
+
destroy: () => void;
|
|
37
|
+
onReady: () => void;
|
|
38
|
+
successSubmit: (payload: {
|
|
39
|
+
customerId: string;
|
|
40
|
+
templateHandle: string;
|
|
41
|
+
fileUrl: string;
|
|
42
|
+
signature: string;
|
|
43
|
+
/**
|
|
44
|
+
* Deprecated. Prefer handling the resulting navigation flow in the host app.
|
|
45
|
+
*/
|
|
46
|
+
redirectUrl: string;
|
|
47
|
+
}) => void;
|
|
48
|
+
error: (payload: {
|
|
49
|
+
code: string;
|
|
50
|
+
message: string;
|
|
51
|
+
}) => void;
|
|
52
|
+
};
|
|
53
|
+
type WeTransformInstance = {
|
|
54
|
+
open: () => Promise<void>;
|
|
55
|
+
close: () => Promise<void>;
|
|
56
|
+
destroy: () => Promise<void>;
|
|
57
|
+
on: <K extends keyof WeTransformEventMap>(event: K, handler: WeTransformEventMap[K]) => () => void;
|
|
58
|
+
};
|
|
59
|
+
type SupportedLocales = 'en' | 'fr';
|
|
60
|
+
//#endregion
|
|
61
|
+
//#region src/index.d.ts
|
|
62
|
+
declare function createWeTransform(config: WeTransformConfig): WeTransformInstance;
|
|
63
|
+
//#endregion
|
|
64
|
+
export { type WeTransformAuthenticationConfig, type WeTransformConfig, type WeTransformEventMap, type WeTransformInstance, createWeTransform };
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,850 @@
|
|
|
1
|
+
import { WindowMessenger, connect } from "penpal";
|
|
2
|
+
import { createFocusTrap } from "focus-trap";
|
|
3
|
+
//#region src/events/EventEmitter.ts
|
|
4
|
+
var EventEmitter = class {
|
|
5
|
+
listeners = {};
|
|
6
|
+
on(event, handler) {
|
|
7
|
+
const existing = this.listeners[event] ?? /* @__PURE__ */ new Set();
|
|
8
|
+
existing.add(handler);
|
|
9
|
+
this.listeners[event] = existing;
|
|
10
|
+
return () => this.off(event, handler);
|
|
11
|
+
}
|
|
12
|
+
off(event, handler) {
|
|
13
|
+
const existing = this.listeners[event];
|
|
14
|
+
if (!existing) return;
|
|
15
|
+
existing.delete(handler);
|
|
16
|
+
if (existing.size === 0) delete this.listeners[event];
|
|
17
|
+
}
|
|
18
|
+
emit(event, ...args) {
|
|
19
|
+
const existing = this.listeners[event];
|
|
20
|
+
if (!existing) return;
|
|
21
|
+
for (const handler of existing) handler(...args);
|
|
22
|
+
}
|
|
23
|
+
clear() {
|
|
24
|
+
this.listeners = {};
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
//#endregion
|
|
28
|
+
//#region src/utils/abort.ts
|
|
29
|
+
function createAbortError() {
|
|
30
|
+
return new DOMException("The operation was aborted.", "AbortError");
|
|
31
|
+
}
|
|
32
|
+
function isAbortError(error) {
|
|
33
|
+
return error instanceof DOMException && error.name === "AbortError";
|
|
34
|
+
}
|
|
35
|
+
function throwIfAborted(signal) {
|
|
36
|
+
if (signal.aborted) throw createAbortError();
|
|
37
|
+
}
|
|
38
|
+
//#endregion
|
|
39
|
+
//#region src/auth/AuthBridge.ts
|
|
40
|
+
var AuthBridge = class {
|
|
41
|
+
accessToken = null;
|
|
42
|
+
refreshToken = null;
|
|
43
|
+
initPromise = null;
|
|
44
|
+
refreshPromise = null;
|
|
45
|
+
revokePromise = null;
|
|
46
|
+
constructor(apiUrl, authentication, emitError) {
|
|
47
|
+
this.apiUrl = apiUrl;
|
|
48
|
+
this.authentication = authentication;
|
|
49
|
+
this.emitError = emitError;
|
|
50
|
+
}
|
|
51
|
+
async getAccessToken(signal) {
|
|
52
|
+
if (this.accessToken) return this.accessToken;
|
|
53
|
+
return this.initAuth(signal);
|
|
54
|
+
}
|
|
55
|
+
async refreshAccessToken(signal) {
|
|
56
|
+
if (this.refreshPromise) return this.refreshPromise;
|
|
57
|
+
if (!this.refreshToken) {
|
|
58
|
+
this.emitError({
|
|
59
|
+
code: "auth_refresh_missing",
|
|
60
|
+
message: "No refresh token available for refresh."
|
|
61
|
+
});
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
this.refreshPromise = this.post("/connect/refresh", { refresh_token: this.refreshToken }, void 0, signal).then((response) => {
|
|
65
|
+
if (response.success) {
|
|
66
|
+
this.setTokens(response.payload);
|
|
67
|
+
return this.accessToken;
|
|
68
|
+
} else {
|
|
69
|
+
this.emitError({
|
|
70
|
+
code: "auth_refresh_failed",
|
|
71
|
+
message: "Auth refresh failed."
|
|
72
|
+
});
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
}).catch((error) => {
|
|
76
|
+
if (isAbortError(error)) return null;
|
|
77
|
+
this.emitError({
|
|
78
|
+
code: "auth_refresh_failed",
|
|
79
|
+
message: error instanceof Error ? error.message : "Refresh failed."
|
|
80
|
+
});
|
|
81
|
+
return null;
|
|
82
|
+
}).finally(() => {
|
|
83
|
+
this.refreshPromise = null;
|
|
84
|
+
});
|
|
85
|
+
return this.refreshPromise;
|
|
86
|
+
}
|
|
87
|
+
clear() {
|
|
88
|
+
this.accessToken = null;
|
|
89
|
+
this.refreshToken = null;
|
|
90
|
+
this.initPromise = null;
|
|
91
|
+
this.refreshPromise = null;
|
|
92
|
+
this.revokePromise = null;
|
|
93
|
+
}
|
|
94
|
+
revokeSession(options, signal) {
|
|
95
|
+
if (this.revokePromise) return this.revokePromise;
|
|
96
|
+
const refreshToken = this.refreshToken;
|
|
97
|
+
this.accessToken = null;
|
|
98
|
+
this.refreshToken = null;
|
|
99
|
+
if (!refreshToken) return Promise.resolve();
|
|
100
|
+
this.revokePromise = this.post("/connect/revoke", { refresh_token: refreshToken }, { keepalive: options?.bestEffort === true }, signal).then(() => void 0).catch((error) => {
|
|
101
|
+
if (isAbortError(error)) return;
|
|
102
|
+
this.emitError({
|
|
103
|
+
code: "auth_revoke_failed",
|
|
104
|
+
message: error instanceof Error ? error.message : "Session revoke failed."
|
|
105
|
+
});
|
|
106
|
+
}).finally(() => {
|
|
107
|
+
this.revokePromise = null;
|
|
108
|
+
});
|
|
109
|
+
return this.revokePromise;
|
|
110
|
+
}
|
|
111
|
+
async initAuth(signal) {
|
|
112
|
+
if (this.initPromise) return this.initPromise;
|
|
113
|
+
this.initPromise = this.post("/connect/embed", {
|
|
114
|
+
external_id: this.authentication.customerId,
|
|
115
|
+
signature: this.authentication.signature,
|
|
116
|
+
template_handle: this.authentication.templateHandle,
|
|
117
|
+
source_id: this.authentication.sourceId
|
|
118
|
+
}, void 0, signal).then((response) => {
|
|
119
|
+
if (response.success) {
|
|
120
|
+
this.setTokens(response.payload);
|
|
121
|
+
return this.accessToken;
|
|
122
|
+
} else {
|
|
123
|
+
this.emitError({
|
|
124
|
+
code: "auth_init_failed",
|
|
125
|
+
message: "Auth init failed."
|
|
126
|
+
});
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
}).catch((error) => {
|
|
130
|
+
if (isAbortError(error)) return null;
|
|
131
|
+
this.emitError({
|
|
132
|
+
code: "auth_init_failed",
|
|
133
|
+
message: error instanceof Error ? error.message : "Auth init failed."
|
|
134
|
+
});
|
|
135
|
+
return null;
|
|
136
|
+
}).finally(() => {
|
|
137
|
+
this.initPromise = null;
|
|
138
|
+
});
|
|
139
|
+
return this.initPromise;
|
|
140
|
+
}
|
|
141
|
+
setTokens(tokens) {
|
|
142
|
+
this.accessToken = tokens.access_token;
|
|
143
|
+
this.refreshToken = tokens.refresh_token;
|
|
144
|
+
}
|
|
145
|
+
async post(path, body, options, signal) {
|
|
146
|
+
const url = this.apiUrl + path;
|
|
147
|
+
const response = await fetch(url, {
|
|
148
|
+
method: "POST",
|
|
149
|
+
headers: { "Content-Type": "application/json" },
|
|
150
|
+
body: JSON.stringify(body),
|
|
151
|
+
keepalive: !!options?.keepalive,
|
|
152
|
+
signal
|
|
153
|
+
});
|
|
154
|
+
if (!response.ok) throw new Error(`Request failed with status ${response.status}.`);
|
|
155
|
+
return await response.json();
|
|
156
|
+
}
|
|
157
|
+
};
|
|
158
|
+
//#endregion
|
|
159
|
+
//#region src/iframe/urlHelper.ts
|
|
160
|
+
const getInitialLocationPath = (location, templateHandle, sourceId) => {
|
|
161
|
+
if (location) switch (location) {
|
|
162
|
+
case "transformations": return "/app/workflows";
|
|
163
|
+
case "uploader": return templateHandle ? `/${templateHandle}` : "/app/workflows";
|
|
164
|
+
case "mapper": return templateHandle && sourceId ? `/${templateHandle}/${sourceId}/map/columns/match` : "/app/workflows";
|
|
165
|
+
case "finalize": return templateHandle && sourceId ? `/${templateHandle}/${sourceId}/review` : "/app/workflows";
|
|
166
|
+
case "sourcePreview": return templateHandle && sourceId ? `/${templateHandle}/source/preview/${sourceId}` : "/app/workflows";
|
|
167
|
+
case "sourceUpdate": return templateHandle && sourceId ? `/${templateHandle}/source/update-config/${sourceId}` : "/app/workflows";
|
|
168
|
+
case "scheduler": return templateHandle && sourceId ? `/${templateHandle}/${sourceId}/embedded-schedules/edit` : "/app/workflows";
|
|
169
|
+
default: return "/app/workflows";
|
|
170
|
+
}
|
|
171
|
+
if (templateHandle && sourceId) return `/${templateHandle}/${sourceId}/map/columns/match`;
|
|
172
|
+
if (templateHandle) return `/${templateHandle}`;
|
|
173
|
+
return "/app/workflows";
|
|
174
|
+
};
|
|
175
|
+
//#endregion
|
|
176
|
+
//#region src/iframe/IframeManager.ts
|
|
177
|
+
const EMBED_ROUTE_STORAGE_KEY = "wetransform-sdk-embed-route";
|
|
178
|
+
const EMBED_ROUTE_PENDING_STORAGE_KEY = "wetransform-sdk-embed-route-pending";
|
|
179
|
+
var IframeManager = class {
|
|
180
|
+
iframe = null;
|
|
181
|
+
connection = null;
|
|
182
|
+
childApi = null;
|
|
183
|
+
activeSignal = null;
|
|
184
|
+
origin;
|
|
185
|
+
onPopState = (e) => {
|
|
186
|
+
this.syncChildToHostUrl(e);
|
|
187
|
+
};
|
|
188
|
+
constructor(baseUrl, events, authBridge, locationInfo) {
|
|
189
|
+
this.baseUrl = baseUrl;
|
|
190
|
+
this.events = events;
|
|
191
|
+
this.authBridge = authBridge;
|
|
192
|
+
this.locationInfo = locationInfo;
|
|
193
|
+
this.origin = new URL(baseUrl).origin;
|
|
194
|
+
}
|
|
195
|
+
mount(container, bootstrapToken, signal) {
|
|
196
|
+
this.unmount();
|
|
197
|
+
this.activeSignal = signal ?? null;
|
|
198
|
+
const initialPath = this.resolveInitialPath();
|
|
199
|
+
const iframe = document.createElement("iframe");
|
|
200
|
+
iframe.src = this.buildIframeUrl(initialPath, bootstrapToken);
|
|
201
|
+
iframe.title = "WeTransform";
|
|
202
|
+
iframe.setAttribute("allow", "clipboard-read; clipboard-write");
|
|
203
|
+
iframe.setAttribute("tabindex", "0");
|
|
204
|
+
iframe.style.width = "100%";
|
|
205
|
+
iframe.style.height = "100%";
|
|
206
|
+
iframe.style.border = "0";
|
|
207
|
+
iframe.style.display = "block";
|
|
208
|
+
container.appendChild(iframe);
|
|
209
|
+
this.iframe = iframe;
|
|
210
|
+
window.addEventListener("popstate", this.onPopState);
|
|
211
|
+
this.connect(signal);
|
|
212
|
+
}
|
|
213
|
+
unmount() {
|
|
214
|
+
window.removeEventListener("popstate", this.onPopState);
|
|
215
|
+
this.activeSignal = null;
|
|
216
|
+
if (this.connection) {
|
|
217
|
+
this.connection.destroy();
|
|
218
|
+
this.connection = null;
|
|
219
|
+
this.childApi = null;
|
|
220
|
+
}
|
|
221
|
+
if (this.iframe?.parentElement) this.iframe.parentElement.removeChild(this.iframe);
|
|
222
|
+
this.iframe = null;
|
|
223
|
+
}
|
|
224
|
+
async requestLogout(signal) {
|
|
225
|
+
await this.notifyLogout(signal ?? this.activeSignal ?? void 0);
|
|
226
|
+
}
|
|
227
|
+
clearPersistedRoute() {
|
|
228
|
+
sessionStorage.removeItem(EMBED_ROUTE_STORAGE_KEY);
|
|
229
|
+
sessionStorage.removeItem(EMBED_ROUTE_PENDING_STORAGE_KEY);
|
|
230
|
+
}
|
|
231
|
+
persistRouteForPageLeave(event) {
|
|
232
|
+
if (event.persisted) return;
|
|
233
|
+
const currentRoute = this.getPersistedRoute();
|
|
234
|
+
if (!currentRoute) return;
|
|
235
|
+
sessionStorage.setItem(EMBED_ROUTE_PENDING_STORAGE_KEY, currentRoute);
|
|
236
|
+
sessionStorage.removeItem(EMBED_ROUTE_STORAGE_KEY);
|
|
237
|
+
}
|
|
238
|
+
connect(signal) {
|
|
239
|
+
if (!this.iframe) return;
|
|
240
|
+
if (!this.iframe.contentWindow) {
|
|
241
|
+
this.events.emit("error", {
|
|
242
|
+
code: "iframe_unavailable",
|
|
243
|
+
message: "Iframe contentWindow is not available."
|
|
244
|
+
});
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
this.connection = connect({
|
|
248
|
+
messenger: new WindowMessenger({
|
|
249
|
+
remoteWindow: this.iframe.contentWindow,
|
|
250
|
+
allowedOrigins: [this.origin]
|
|
251
|
+
}),
|
|
252
|
+
methods: this.createParentMethods()
|
|
253
|
+
});
|
|
254
|
+
this.childApi = this.withAbort(this.connection.promise, signal).catch((error) => {
|
|
255
|
+
if (isAbortError(error)) return null;
|
|
256
|
+
throw error;
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
createParentMethods() {
|
|
260
|
+
return {
|
|
261
|
+
onReady: async () => {
|
|
262
|
+
this.events.emit("onReady");
|
|
263
|
+
try {
|
|
264
|
+
await this.sendAuthToChild(void 0, this.activeSignal ?? void 0);
|
|
265
|
+
} catch (error) {
|
|
266
|
+
if (!isAbortError(error)) throw error;
|
|
267
|
+
}
|
|
268
|
+
},
|
|
269
|
+
needRefresh: async () => {
|
|
270
|
+
const accessToken = await this.authBridge.refreshAccessToken(this.activeSignal ?? void 0);
|
|
271
|
+
if (!accessToken) return false;
|
|
272
|
+
await this.sendAuthToChild(accessToken, this.activeSignal ?? void 0);
|
|
273
|
+
return true;
|
|
274
|
+
},
|
|
275
|
+
currentUrl: (payload) => {
|
|
276
|
+
window.history.pushState({ path: payload.url }, "", "");
|
|
277
|
+
this.setPersistedRoute(payload.url);
|
|
278
|
+
},
|
|
279
|
+
successSubmit: (payload) => {
|
|
280
|
+
this.clearPersistedRoute();
|
|
281
|
+
this.events.emit("successSubmit", payload);
|
|
282
|
+
},
|
|
283
|
+
error: (payload) => {
|
|
284
|
+
this.events.emit("error", payload);
|
|
285
|
+
}
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
async sendAuthToChild(overrideToken, signal) {
|
|
289
|
+
const child = this.childApi ? await this.withAbort(this.childApi, signal) : null;
|
|
290
|
+
if (!child) return;
|
|
291
|
+
const accessToken = overrideToken ?? await this.authBridge.getAccessToken(signal ?? this.activeSignal ?? void 0);
|
|
292
|
+
if (!accessToken) return;
|
|
293
|
+
await this.withAbort(child.setAuth({ access_token: accessToken }), signal);
|
|
294
|
+
}
|
|
295
|
+
async notifyLogout(signal) {
|
|
296
|
+
const logout = (this.childApi ? await this.withAbort(this.childApi, signal) : null)?.logout;
|
|
297
|
+
if (!logout) return;
|
|
298
|
+
try {
|
|
299
|
+
await this.withAbort(logout(), signal);
|
|
300
|
+
} catch {}
|
|
301
|
+
}
|
|
302
|
+
async syncChildToHostUrl(e) {
|
|
303
|
+
const signal = this.activeSignal ?? void 0;
|
|
304
|
+
let child = null;
|
|
305
|
+
try {
|
|
306
|
+
child = this.childApi ? await this.withAbort(this.childApi, signal) : null;
|
|
307
|
+
} catch (error) {
|
|
308
|
+
if (isAbortError(error)) return;
|
|
309
|
+
throw error;
|
|
310
|
+
}
|
|
311
|
+
const navigate = child?.navigate;
|
|
312
|
+
if (!navigate) return;
|
|
313
|
+
const url = e.state?.path;
|
|
314
|
+
if (typeof url !== "string") return;
|
|
315
|
+
try {
|
|
316
|
+
await this.withAbort(navigate({ route: url }), signal);
|
|
317
|
+
} catch {}
|
|
318
|
+
}
|
|
319
|
+
withAbort(promise, signal) {
|
|
320
|
+
if (!signal) return promise;
|
|
321
|
+
if (signal.aborted) return Promise.reject(createAbortError());
|
|
322
|
+
return new Promise((resolve, reject) => {
|
|
323
|
+
const onAbort = () => {
|
|
324
|
+
signal.removeEventListener("abort", onAbort);
|
|
325
|
+
reject(createAbortError());
|
|
326
|
+
};
|
|
327
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
328
|
+
promise.then((value) => {
|
|
329
|
+
signal.removeEventListener("abort", onAbort);
|
|
330
|
+
resolve(value);
|
|
331
|
+
}).catch((error) => {
|
|
332
|
+
signal.removeEventListener("abort", onAbort);
|
|
333
|
+
reject(error);
|
|
334
|
+
});
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
buildIframeUrl(path, bootstrapToken) {
|
|
338
|
+
const url = new URL(path, this.baseUrl);
|
|
339
|
+
url.searchParams.set("sdk", "true");
|
|
340
|
+
if (bootstrapToken) url.searchParams.set("token", bootstrapToken);
|
|
341
|
+
return url.toString();
|
|
342
|
+
}
|
|
343
|
+
resolveInitialPath() {
|
|
344
|
+
const persistedRoute = this.getPersistedRoute();
|
|
345
|
+
if (persistedRoute) return persistedRoute;
|
|
346
|
+
const pendingRoute = sessionStorage.getItem(EMBED_ROUTE_PENDING_STORAGE_KEY);
|
|
347
|
+
if (!pendingRoute) return getInitialLocationPath(this.locationInfo.initialLocation, this.locationInfo.templateHandle, this.locationInfo.sourceId);
|
|
348
|
+
if (this.isReloadNavigation()) {
|
|
349
|
+
this.setPersistedRoute(pendingRoute);
|
|
350
|
+
sessionStorage.removeItem(EMBED_ROUTE_PENDING_STORAGE_KEY);
|
|
351
|
+
return pendingRoute;
|
|
352
|
+
}
|
|
353
|
+
sessionStorage.removeItem(EMBED_ROUTE_PENDING_STORAGE_KEY);
|
|
354
|
+
return getInitialLocationPath(this.locationInfo.initialLocation, this.locationInfo.templateHandle, this.locationInfo.sourceId);
|
|
355
|
+
}
|
|
356
|
+
getPersistedRoute() {
|
|
357
|
+
return sessionStorage.getItem(EMBED_ROUTE_STORAGE_KEY);
|
|
358
|
+
}
|
|
359
|
+
setPersistedRoute(route) {
|
|
360
|
+
sessionStorage.setItem(EMBED_ROUTE_STORAGE_KEY, route);
|
|
361
|
+
sessionStorage.removeItem(EMBED_ROUTE_PENDING_STORAGE_KEY);
|
|
362
|
+
}
|
|
363
|
+
isReloadNavigation() {
|
|
364
|
+
const [entry] = performance.getEntriesByType("navigation");
|
|
365
|
+
return entry instanceof PerformanceNavigationTiming ? entry.type === "reload" : false;
|
|
366
|
+
}
|
|
367
|
+
};
|
|
368
|
+
//#endregion
|
|
369
|
+
//#region src/ui/ModalHost.ts
|
|
370
|
+
var ModalHost = class {
|
|
371
|
+
host = null;
|
|
372
|
+
shadowRoot = null;
|
|
373
|
+
iframeContainer = null;
|
|
374
|
+
focusTrap = null;
|
|
375
|
+
inertSnapshot = [];
|
|
376
|
+
previousOverflow = null;
|
|
377
|
+
constructor(onCloseRequest) {
|
|
378
|
+
this.onCloseRequest = onCloseRequest;
|
|
379
|
+
}
|
|
380
|
+
open() {
|
|
381
|
+
if (this.host && this.iframeContainer) return this.iframeContainer;
|
|
382
|
+
this.host = document.createElement("div");
|
|
383
|
+
this.host.setAttribute("data-WeTransform-sdk", "");
|
|
384
|
+
this.shadowRoot = this.host.attachShadow({ mode: "open" });
|
|
385
|
+
const style = document.createElement("style");
|
|
386
|
+
style.textContent = this.getStyles();
|
|
387
|
+
const backdrop = document.createElement("div");
|
|
388
|
+
backdrop.className = "sdk-backdrop";
|
|
389
|
+
const shell = document.createElement("div");
|
|
390
|
+
shell.className = "sdk-shell";
|
|
391
|
+
const dialog = document.createElement("div");
|
|
392
|
+
dialog.className = "sdk-dialog";
|
|
393
|
+
dialog.setAttribute("role", "dialog");
|
|
394
|
+
dialog.setAttribute("aria-modal", "true");
|
|
395
|
+
dialog.setAttribute("aria-label", "WeTransform");
|
|
396
|
+
dialog.tabIndex = -1;
|
|
397
|
+
const closeBtn = document.createElement("button");
|
|
398
|
+
closeBtn.className = "sdk-close-btn";
|
|
399
|
+
closeBtn.setAttribute("aria-label", "Close");
|
|
400
|
+
closeBtn.type = "button";
|
|
401
|
+
closeBtn.innerHTML = "×";
|
|
402
|
+
closeBtn.addEventListener("click", (e) => {
|
|
403
|
+
e.stopPropagation();
|
|
404
|
+
this.onCloseRequest();
|
|
405
|
+
});
|
|
406
|
+
const frameContainer = document.createElement("div");
|
|
407
|
+
frameContainer.className = "sdk-frame-container";
|
|
408
|
+
dialog.appendChild(frameContainer);
|
|
409
|
+
shell.appendChild(dialog);
|
|
410
|
+
shell.appendChild(closeBtn);
|
|
411
|
+
backdrop.appendChild(shell);
|
|
412
|
+
this.shadowRoot.appendChild(style);
|
|
413
|
+
this.shadowRoot.appendChild(backdrop);
|
|
414
|
+
document.body.appendChild(this.host);
|
|
415
|
+
this.iframeContainer = frameContainer;
|
|
416
|
+
this.lockPage();
|
|
417
|
+
this.focusTrap = createFocusTrap(shell, {
|
|
418
|
+
escapeDeactivates: false,
|
|
419
|
+
allowOutsideClick: true,
|
|
420
|
+
fallbackFocus: dialog
|
|
421
|
+
});
|
|
422
|
+
this.focusTrap.activate();
|
|
423
|
+
dialog.focus();
|
|
424
|
+
return frameContainer;
|
|
425
|
+
}
|
|
426
|
+
close() {
|
|
427
|
+
this.focusTrap?.deactivate();
|
|
428
|
+
this.focusTrap = null;
|
|
429
|
+
this.unlockPage();
|
|
430
|
+
if (this.host?.parentElement) this.host.parentElement.removeChild(this.host);
|
|
431
|
+
this.host = null;
|
|
432
|
+
this.shadowRoot = null;
|
|
433
|
+
this.iframeContainer = null;
|
|
434
|
+
}
|
|
435
|
+
lockPage() {
|
|
436
|
+
const bodyChildren = Array.from(document.body.children);
|
|
437
|
+
this.inertSnapshot = [];
|
|
438
|
+
for (const child of bodyChildren) {
|
|
439
|
+
if (child === this.host) continue;
|
|
440
|
+
const element = child;
|
|
441
|
+
const inertElement = this.getInertCapableElement(element);
|
|
442
|
+
this.inertSnapshot.push({
|
|
443
|
+
element,
|
|
444
|
+
ariaHidden: element.getAttribute("aria-hidden"),
|
|
445
|
+
inert: inertElement ? Boolean(inertElement.inert) : false
|
|
446
|
+
});
|
|
447
|
+
element.setAttribute("aria-hidden", "true");
|
|
448
|
+
if (inertElement) inertElement.inert = true;
|
|
449
|
+
}
|
|
450
|
+
this.previousOverflow = document.documentElement.style.overflow || null;
|
|
451
|
+
document.documentElement.style.overflow = "hidden";
|
|
452
|
+
}
|
|
453
|
+
unlockPage() {
|
|
454
|
+
for (const snapshot of this.inertSnapshot) {
|
|
455
|
+
if (snapshot.ariaHidden === null) snapshot.element.removeAttribute("aria-hidden");
|
|
456
|
+
else snapshot.element.setAttribute("aria-hidden", snapshot.ariaHidden);
|
|
457
|
+
const inertElement = this.getInertCapableElement(snapshot.element);
|
|
458
|
+
if (inertElement) inertElement.inert = snapshot.inert;
|
|
459
|
+
}
|
|
460
|
+
this.inertSnapshot = [];
|
|
461
|
+
if (this.previousOverflow === null) document.documentElement.style.removeProperty("overflow");
|
|
462
|
+
else document.documentElement.style.overflow = this.previousOverflow;
|
|
463
|
+
this.previousOverflow = null;
|
|
464
|
+
}
|
|
465
|
+
getInertCapableElement(element) {
|
|
466
|
+
return "inert" in element ? element : null;
|
|
467
|
+
}
|
|
468
|
+
getStyles() {
|
|
469
|
+
return `
|
|
470
|
+
:host {
|
|
471
|
+
all: initial;
|
|
472
|
+
}
|
|
473
|
+
.sdk-backdrop {
|
|
474
|
+
position: fixed;
|
|
475
|
+
inset: 0;
|
|
476
|
+
background: rgba(0, 0, 0, 0.5);
|
|
477
|
+
display: flex;
|
|
478
|
+
align-items: center;
|
|
479
|
+
justify-content: center;
|
|
480
|
+
z-index: 2147483647;
|
|
481
|
+
}
|
|
482
|
+
.sdk-dialog {
|
|
483
|
+
width: 98vw;
|
|
484
|
+
height: 98vh;
|
|
485
|
+
overflow: hidden;
|
|
486
|
+
background: #ffffff;
|
|
487
|
+
border-radius: 12px;
|
|
488
|
+
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.2);
|
|
489
|
+
display: flex;
|
|
490
|
+
flex-direction: column;
|
|
491
|
+
}
|
|
492
|
+
.sdk-shell {
|
|
493
|
+
position: relative;
|
|
494
|
+
}
|
|
495
|
+
.sdk-close-btn {
|
|
496
|
+
position: absolute;
|
|
497
|
+
top: -8px;
|
|
498
|
+
right: -8px;
|
|
499
|
+
width: 20px;
|
|
500
|
+
height: 20px;
|
|
501
|
+
padding: 0;
|
|
502
|
+
border: none;
|
|
503
|
+
background: rgb(0,0,0);
|
|
504
|
+
border-radius: 100%;
|
|
505
|
+
display: flex;
|
|
506
|
+
align-items: center;
|
|
507
|
+
justify-content: center;
|
|
508
|
+
font-size: 20px;
|
|
509
|
+
line-height: 1;
|
|
510
|
+
cursor: pointer;
|
|
511
|
+
color: #fff;
|
|
512
|
+
box-shadow: 0px 0px 10px 0px rgba(0, 0, 0, 0.5);
|
|
513
|
+
}
|
|
514
|
+
.sdk-close-btn:hover {
|
|
515
|
+
background: rgba(0,0,0,0.6);
|
|
516
|
+
}
|
|
517
|
+
.sdk-frame-container {
|
|
518
|
+
flex: 1;
|
|
519
|
+
min-height: 0;
|
|
520
|
+
}
|
|
521
|
+
`;
|
|
522
|
+
}
|
|
523
|
+
};
|
|
524
|
+
//#endregion
|
|
525
|
+
//#region src/ui/UiHost.ts
|
|
526
|
+
const DEFAULT_IFRAME_CONTAINER_STYLE_ID = "wetransform-sdk-inline-container-defaults";
|
|
527
|
+
const OPENING_LOADER_ATTRIBUTE = "data-sender-sdk-loader";
|
|
528
|
+
const OPENING_LOADER_FADE_OUT_DURATION_MS = 400;
|
|
529
|
+
const OPENING_LOADER_SVG = "<svg class=\"wetransform-iframeLoader\" width=\"46px\" height=\"46px\" viewBox=\"25 25 50 50\" style=\"animation: wetransform-spin-animation 2s linear infinite; transform-origin: center center;\" ><circle cx=\"50\" cy=\"50\" r=\"20\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"5\" stroke-miterlimit=\"10\" style=\"stroke-dasharray: 1,200; stroke-dashoffset: 0; animation: wetransform-spin-dash 1.5s ease-in-out infinite;\" ></circle></svg>";
|
|
530
|
+
function ensureDefaultInlineContainerStyles() {
|
|
531
|
+
if (document.getElementById(DEFAULT_IFRAME_CONTAINER_STYLE_ID)) return;
|
|
532
|
+
const style = document.createElement("style");
|
|
533
|
+
style.id = DEFAULT_IFRAME_CONTAINER_STYLE_ID;
|
|
534
|
+
style.textContent = `
|
|
535
|
+
:where(#weTransform_iframeContainer) {
|
|
536
|
+
width: 1080px;
|
|
537
|
+
height: 720px;
|
|
538
|
+
border: 0;
|
|
539
|
+
position: relative;
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
:where(.wetransform-iframeLoader) {
|
|
543
|
+
color: var(--wetransform-primary-color, #008df7);
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
@keyframes wetransform-spin-animation {
|
|
547
|
+
0% {
|
|
548
|
+
transform:rotate3d(0,0,1,0);
|
|
549
|
+
}
|
|
550
|
+
25% {
|
|
551
|
+
transform:rotate3d(0,0,1,90deg);
|
|
552
|
+
}
|
|
553
|
+
50% {
|
|
554
|
+
transform:rotate3d(0,0,1,180deg);
|
|
555
|
+
}
|
|
556
|
+
75% {
|
|
557
|
+
transform:rotate3d(0,0,1,270deg);
|
|
558
|
+
}
|
|
559
|
+
to {
|
|
560
|
+
transform:rotate3d(0,0,1,359deg);
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
@keyframes wetransform-spin-dash {
|
|
565
|
+
0% {
|
|
566
|
+
stroke-dasharray:1,200;
|
|
567
|
+
stroke-dashoffset:0
|
|
568
|
+
}
|
|
569
|
+
50% {
|
|
570
|
+
stroke-dasharray:89,200;
|
|
571
|
+
stroke-dashoffset:-35px
|
|
572
|
+
}
|
|
573
|
+
to {
|
|
574
|
+
stroke-dasharray:89,200;
|
|
575
|
+
stroke-dashoffset:-124px
|
|
576
|
+
}
|
|
577
|
+
}`;
|
|
578
|
+
document.head.prepend(style);
|
|
579
|
+
}
|
|
580
|
+
var UiHost = class {
|
|
581
|
+
modalHost = null;
|
|
582
|
+
inlineContainer = null;
|
|
583
|
+
ownsInlineContainer = false;
|
|
584
|
+
openingContainer = null;
|
|
585
|
+
clearOpeningLoader = null;
|
|
586
|
+
constructor(config) {
|
|
587
|
+
this.config = config;
|
|
588
|
+
}
|
|
589
|
+
open(onCloseRequest) {
|
|
590
|
+
if (this.config.displayAsModal) {
|
|
591
|
+
this.modalHost ??= new ModalHost(onCloseRequest);
|
|
592
|
+
return this.modalHost.open();
|
|
593
|
+
}
|
|
594
|
+
return this.ensureInlineContainer();
|
|
595
|
+
}
|
|
596
|
+
startOpening(onCloseRequest) {
|
|
597
|
+
const container = this.open(onCloseRequest);
|
|
598
|
+
this.openingContainer = container;
|
|
599
|
+
this.clearOpeningLoader = this.showOpeningLoader(container);
|
|
600
|
+
return container;
|
|
601
|
+
}
|
|
602
|
+
finishOpening() {
|
|
603
|
+
this.clearOpeningLoader?.();
|
|
604
|
+
this.clearOpeningLoader = null;
|
|
605
|
+
this.openingContainer = null;
|
|
606
|
+
}
|
|
607
|
+
hasOpeningUi() {
|
|
608
|
+
return this.openingContainer !== null;
|
|
609
|
+
}
|
|
610
|
+
cancelOpening() {
|
|
611
|
+
if (!this.openingContainer) return;
|
|
612
|
+
this.finishOpening();
|
|
613
|
+
this.close();
|
|
614
|
+
}
|
|
615
|
+
close() {
|
|
616
|
+
this.finishOpening();
|
|
617
|
+
if (this.config.displayAsModal) {
|
|
618
|
+
this.modalHost?.close();
|
|
619
|
+
return;
|
|
620
|
+
}
|
|
621
|
+
this.clearInlineContainer();
|
|
622
|
+
}
|
|
623
|
+
dispose() {
|
|
624
|
+
this.finishOpening();
|
|
625
|
+
this.modalHost = null;
|
|
626
|
+
this.inlineContainer = null;
|
|
627
|
+
this.ownsInlineContainer = false;
|
|
628
|
+
this.openingContainer = null;
|
|
629
|
+
this.clearOpeningLoader = null;
|
|
630
|
+
}
|
|
631
|
+
showOpeningLoader(container) {
|
|
632
|
+
const existingLoader = container.querySelector(`[${OPENING_LOADER_ATTRIBUTE}]`);
|
|
633
|
+
if (existingLoader?.parentElement) existingLoader.parentElement.removeChild(existingLoader);
|
|
634
|
+
const loader = document.createElement("div");
|
|
635
|
+
loader.setAttribute(OPENING_LOADER_ATTRIBUTE, "");
|
|
636
|
+
loader.setAttribute("role", "status");
|
|
637
|
+
loader.setAttribute("aria-live", "polite");
|
|
638
|
+
loader.setAttribute("aria-label", "Loading");
|
|
639
|
+
loader.innerHTML = OPENING_LOADER_SVG;
|
|
640
|
+
loader.style.display = "flex";
|
|
641
|
+
loader.style.alignItems = "center";
|
|
642
|
+
loader.style.justifyContent = "center";
|
|
643
|
+
loader.style.width = "100%";
|
|
644
|
+
loader.style.height = "100%";
|
|
645
|
+
loader.style.minHeight = "120px";
|
|
646
|
+
loader.style.color = "#1f2937";
|
|
647
|
+
loader.style.fontFamily = "system-ui, -apple-system, Segoe UI, sans-serif";
|
|
648
|
+
loader.style.fontSize = "14px";
|
|
649
|
+
loader.style.backgroundColor = "#fff";
|
|
650
|
+
loader.style.position = "absolute";
|
|
651
|
+
loader.style.top = "0";
|
|
652
|
+
loader.style.left = "0";
|
|
653
|
+
loader.style.opacity = "1";
|
|
654
|
+
loader.style.transition = `opacity ${OPENING_LOADER_FADE_OUT_DURATION_MS}ms`;
|
|
655
|
+
container.appendChild(loader);
|
|
656
|
+
let isCleared = false;
|
|
657
|
+
let fallbackTimer = null;
|
|
658
|
+
return () => {
|
|
659
|
+
if (isCleared) return;
|
|
660
|
+
isCleared = true;
|
|
661
|
+
const removeLoader = () => {
|
|
662
|
+
loader.removeEventListener("transitionend", removeLoader);
|
|
663
|
+
if (fallbackTimer !== null) {
|
|
664
|
+
window.clearTimeout(fallbackTimer);
|
|
665
|
+
fallbackTimer = null;
|
|
666
|
+
}
|
|
667
|
+
if (loader.parentElement) loader.parentElement.removeChild(loader);
|
|
668
|
+
};
|
|
669
|
+
loader.addEventListener("transitionend", removeLoader);
|
|
670
|
+
fallbackTimer = window.setTimeout(removeLoader, OPENING_LOADER_FADE_OUT_DURATION_MS + 50);
|
|
671
|
+
loader.style.opacity = "0";
|
|
672
|
+
};
|
|
673
|
+
}
|
|
674
|
+
ensureInlineContainer() {
|
|
675
|
+
if (this.inlineContainer) return this.inlineContainer;
|
|
676
|
+
if (this.config.mountElement instanceof HTMLElement) {
|
|
677
|
+
this.inlineContainer = this.config.mountElement;
|
|
678
|
+
this.ownsInlineContainer = false;
|
|
679
|
+
return this.config.mountElement;
|
|
680
|
+
}
|
|
681
|
+
const existingMountElement = document.getElementById(this.config.mountElement);
|
|
682
|
+
if (existingMountElement) {
|
|
683
|
+
this.inlineContainer = existingMountElement;
|
|
684
|
+
this.ownsInlineContainer = false;
|
|
685
|
+
return existingMountElement;
|
|
686
|
+
}
|
|
687
|
+
const container = document.createElement("div");
|
|
688
|
+
container.id = this.config.mountElement;
|
|
689
|
+
document.body.appendChild(container);
|
|
690
|
+
this.inlineContainer = container;
|
|
691
|
+
this.ownsInlineContainer = true;
|
|
692
|
+
return container;
|
|
693
|
+
}
|
|
694
|
+
clearInlineContainer() {
|
|
695
|
+
if (this.ownsInlineContainer && this.inlineContainer?.parentElement) this.inlineContainer.parentElement.removeChild(this.inlineContainer);
|
|
696
|
+
this.inlineContainer = null;
|
|
697
|
+
this.ownsInlineContainer = false;
|
|
698
|
+
}
|
|
699
|
+
};
|
|
700
|
+
//#endregion
|
|
701
|
+
//#region src/index.ts
|
|
702
|
+
function resolveBaseUrl(organizationHandle) {
|
|
703
|
+
const envBaseUrl = import.meta.env.VITE_SENDER_BASE_URL;
|
|
704
|
+
if (envBaseUrl) return envBaseUrl.replace("{organizationHandle}", organizationHandle);
|
|
705
|
+
return window.location.origin;
|
|
706
|
+
}
|
|
707
|
+
function resolveApiUrl(organizationHandle, locale) {
|
|
708
|
+
const envApiUrl = import.meta.env.VITE_SENDER_API_URL;
|
|
709
|
+
if (!envApiUrl) throw new Error("SENDER_API_URL environment variable is not defined.");
|
|
710
|
+
return `${envApiUrl}/${locale ?? "en"}/send/${organizationHandle}`;
|
|
711
|
+
}
|
|
712
|
+
function createWeTransform(config) {
|
|
713
|
+
if (typeof window === "undefined") throw new Error("WeTransform SDK must run in a browser context.");
|
|
714
|
+
ensureDefaultInlineContainerStyles();
|
|
715
|
+
const baseUrl = resolveBaseUrl(config.organizationHandle);
|
|
716
|
+
const apiUrl = resolveApiUrl(config.organizationHandle, config.locale);
|
|
717
|
+
const events = new EventEmitter();
|
|
718
|
+
const uiHost = new UiHost({
|
|
719
|
+
displayAsModal: config.displayAsModal ?? true,
|
|
720
|
+
mountElement: config.mountElement ?? "weTransform_iframeContainer"
|
|
721
|
+
});
|
|
722
|
+
const authBridge = new AuthBridge(apiUrl, config.authentication, (payload) => {
|
|
723
|
+
events.emit("error", payload);
|
|
724
|
+
});
|
|
725
|
+
const iframeManager = new IframeManager(baseUrl, events, authBridge, {
|
|
726
|
+
initialLocation: config.initialLocation,
|
|
727
|
+
templateHandle: config.authentication.templateHandle,
|
|
728
|
+
sourceId: config.authentication.sourceId
|
|
729
|
+
});
|
|
730
|
+
const revokeOnPageUnload = () => {
|
|
731
|
+
authBridge.revokeSession({ bestEffort: true });
|
|
732
|
+
};
|
|
733
|
+
const onPageHide = (event) => {
|
|
734
|
+
iframeManager.persistRouteForPageLeave(event);
|
|
735
|
+
revokeOnPageUnload();
|
|
736
|
+
};
|
|
737
|
+
window.addEventListener("pagehide", onPageHide);
|
|
738
|
+
window.addEventListener("beforeunload", revokeOnPageUnload);
|
|
739
|
+
let status = "idle";
|
|
740
|
+
let openingPromise = null;
|
|
741
|
+
let currentOpenAbortController = null;
|
|
742
|
+
let stopWaitingForReady = null;
|
|
743
|
+
const open = async () => {
|
|
744
|
+
if (status !== "idle") return;
|
|
745
|
+
status = "opening";
|
|
746
|
+
const container = uiHost.startOpening(close);
|
|
747
|
+
events.emit("open");
|
|
748
|
+
const openAbortController = new AbortController();
|
|
749
|
+
currentOpenAbortController = openAbortController;
|
|
750
|
+
const onReadyPromise = new Promise((resolve) => {
|
|
751
|
+
const unsubscribeOnReady = events.on("onReady", () => {
|
|
752
|
+
unsubscribeOnReady();
|
|
753
|
+
stopWaitingForReady = null;
|
|
754
|
+
resolve();
|
|
755
|
+
});
|
|
756
|
+
const onAbort = () => {
|
|
757
|
+
unsubscribeOnReady();
|
|
758
|
+
stopWaitingForReady = null;
|
|
759
|
+
resolve();
|
|
760
|
+
};
|
|
761
|
+
stopWaitingForReady = () => {
|
|
762
|
+
openAbortController.signal.removeEventListener("abort", onAbort);
|
|
763
|
+
onAbort();
|
|
764
|
+
};
|
|
765
|
+
openAbortController.signal.addEventListener("abort", onAbort, { once: true });
|
|
766
|
+
});
|
|
767
|
+
openingPromise = (async () => {
|
|
768
|
+
try {
|
|
769
|
+
const accessToken = await authBridge.getAccessToken(openAbortController.signal);
|
|
770
|
+
if (!accessToken) return;
|
|
771
|
+
throwIfAborted(openAbortController.signal);
|
|
772
|
+
iframeManager.mount(container, accessToken, openAbortController.signal);
|
|
773
|
+
await onReadyPromise;
|
|
774
|
+
throwIfAborted(openAbortController.signal);
|
|
775
|
+
if (status !== "opening") return;
|
|
776
|
+
uiHost.finishOpening();
|
|
777
|
+
status = "open";
|
|
778
|
+
} catch (error) {
|
|
779
|
+
stopWaitingForReady?.();
|
|
780
|
+
if (isAbortError(error)) return;
|
|
781
|
+
events.emit("error", {
|
|
782
|
+
code: "open_failed",
|
|
783
|
+
message: error instanceof Error ? error.message : "Failed to open WeTransform SDK."
|
|
784
|
+
});
|
|
785
|
+
}
|
|
786
|
+
})();
|
|
787
|
+
try {
|
|
788
|
+
await openingPromise;
|
|
789
|
+
} finally {
|
|
790
|
+
const shouldAutoClose = status === "opening" && uiHost.hasOpeningUi();
|
|
791
|
+
if (currentOpenAbortController === openAbortController) currentOpenAbortController = null;
|
|
792
|
+
if (shouldAutoClose) {
|
|
793
|
+
status = "idle";
|
|
794
|
+
uiHost.cancelOpening();
|
|
795
|
+
events.emit("close");
|
|
796
|
+
}
|
|
797
|
+
openingPromise = null;
|
|
798
|
+
}
|
|
799
|
+
};
|
|
800
|
+
const close = async () => {
|
|
801
|
+
if (status === "destroyed" || status === "idle" || status === "closing") return;
|
|
802
|
+
const wasOpen = status === "open";
|
|
803
|
+
const wasOpening = status === "opening";
|
|
804
|
+
status = "closing";
|
|
805
|
+
currentOpenAbortController?.abort();
|
|
806
|
+
if (wasOpening) {
|
|
807
|
+
stopWaitingForReady?.();
|
|
808
|
+
await iframeManager.requestLogout();
|
|
809
|
+
iframeManager.unmount();
|
|
810
|
+
iframeManager.clearPersistedRoute();
|
|
811
|
+
uiHost.cancelOpening();
|
|
812
|
+
status = "idle";
|
|
813
|
+
events.emit("close");
|
|
814
|
+
return;
|
|
815
|
+
}
|
|
816
|
+
if (wasOpen) {
|
|
817
|
+
await iframeManager.requestLogout();
|
|
818
|
+
iframeManager.unmount();
|
|
819
|
+
uiHost.close();
|
|
820
|
+
iframeManager.clearPersistedRoute();
|
|
821
|
+
status = "idle";
|
|
822
|
+
events.emit("close");
|
|
823
|
+
}
|
|
824
|
+
};
|
|
825
|
+
const destroy = async () => {
|
|
826
|
+
if (status === "destroyed") return;
|
|
827
|
+
currentOpenAbortController?.abort();
|
|
828
|
+
await close();
|
|
829
|
+
uiHost.dispose();
|
|
830
|
+
authBridge.revokeSession().finally(() => {
|
|
831
|
+
authBridge.clear();
|
|
832
|
+
});
|
|
833
|
+
window.removeEventListener("pagehide", onPageHide);
|
|
834
|
+
window.removeEventListener("beforeunload", revokeOnPageUnload);
|
|
835
|
+
events.emit("destroy");
|
|
836
|
+
events.clear();
|
|
837
|
+
status = "destroyed";
|
|
838
|
+
};
|
|
839
|
+
const on = (event, handler) => {
|
|
840
|
+
return events.on(event, handler);
|
|
841
|
+
};
|
|
842
|
+
return {
|
|
843
|
+
open,
|
|
844
|
+
close,
|
|
845
|
+
destroy,
|
|
846
|
+
on
|
|
847
|
+
};
|
|
848
|
+
}
|
|
849
|
+
//#endregion
|
|
850
|
+
export { createWeTransform };
|
package/package.json
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@wetransform/core",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"license": "ISC",
|
|
5
|
+
"files": [
|
|
6
|
+
"dist/**"
|
|
7
|
+
],
|
|
8
|
+
"type": "module",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": "./dist/index.mjs"
|
|
11
|
+
},
|
|
12
|
+
"publishConfig": {
|
|
13
|
+
"access": "public"
|
|
14
|
+
},
|
|
15
|
+
"scripts": {
|
|
16
|
+
"build": "vp pack",
|
|
17
|
+
"dev": "vp pack --watch",
|
|
18
|
+
"lint": "vp lint --type-aware --type-check .",
|
|
19
|
+
"test": "vp test run"
|
|
20
|
+
},
|
|
21
|
+
"dependencies": {
|
|
22
|
+
"focus-trap": "^8.0.1",
|
|
23
|
+
"penpal": "^7.0.6"
|
|
24
|
+
},
|
|
25
|
+
"devDependencies": {
|
|
26
|
+
"jsdom": "^29.0.1",
|
|
27
|
+
"typescript": "catalog:",
|
|
28
|
+
"vite": "catalog:",
|
|
29
|
+
"vite-plugin-css-injected-by-js": "^4.0.1",
|
|
30
|
+
"vite-plus": "catalog:",
|
|
31
|
+
"vitest": "catalog:"
|
|
32
|
+
}
|
|
33
|
+
}
|