@connect-xyz/withdraw-js 0.33.0 → 0.35.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.
Files changed (2) hide show
  1. package/dist/index.d.ts +240 -19
  2. package/package.json +1 -1
package/dist/index.d.ts CHANGED
@@ -32,11 +32,13 @@ declare interface BaseConfig<TEvent = AppEvent> extends CommonCallbacks<TEvent>
32
32
  * JWT token used for authentication
33
33
  */
34
34
  jwt: string;
35
+
35
36
  /**
36
37
  * Target environment
37
38
  * @default 'production'
38
39
  */
39
40
  env?: Environment;
41
+
40
42
  /**
41
43
  * Theme mode
42
44
  * @default 'auto'
@@ -49,54 +51,259 @@ declare interface BaseConfig<TEvent = AppEvent> extends CommonCallbacks<TEvent>
49
51
  theme?: Theme;
50
52
  }
51
53
 
52
- declare type BaseErrorMessageKeys = 'ALREADY_RENDERED' | 'NOT_RENDERED' | 'INVALID_CONTAINER' | 'SCRIPT_LOAD_FAILED' | 'WEB_COMPONENT_NOT_DEFINED';
54
+ declare type BaseErrorMessageKeys =
55
+ | 'ALREADY_RENDERED'
56
+ | 'NOT_RENDERED'
57
+ | 'INVALID_CONTAINER'
58
+ | 'SCRIPT_LOAD_FAILED'
59
+ | 'WEB_COMPONENT_NOT_DEFINED';
53
60
 
54
61
  declare abstract class BaseJsSdk<Config extends BaseConfig<never> = BaseConfig> {
55
- private config;
56
- private state;
57
- private scriptLoadingPromise?;
62
+ private config: Config;
63
+ private state: SdkState;
64
+ private scriptLoadingPromise?: Promise<void>;
65
+
58
66
  protected abstract errorMessages: Record<BaseErrorMessageKeys, string>;
59
67
  protected abstract scriptUrls: Record<string, string>;
60
68
  protected abstract webComponentTag: string;
61
- constructor(config: Config);
69
+
70
+ constructor(config: Config) {
71
+ if (!config.jwt || typeof config.jwt !== 'string') {
72
+ throw new Error(INVALID_JWT_ERROR_MESSAGE);
73
+ }
74
+
75
+ this.config = {
76
+ ...config,
77
+ env: config.env || DEFAULT_ENVIRONMENT,
78
+ theme: config.theme,
79
+ };
80
+
81
+ this.state = {
82
+ initialized: false,
83
+ scriptLoaded: false,
84
+ container: null,
85
+ element: null,
86
+ };
87
+ }
88
+
62
89
  /**
63
90
  * Render the widget to a container element
64
91
  * @param container - The container element to render the widget into
65
92
  * @returns Promise that resolves when the widget is rendered
66
93
  */
67
- render(container: HTMLElement): Promise<void>;
94
+ async render(container: HTMLElement): Promise<void> {
95
+ // Validate container
96
+ if (!container || !(container instanceof HTMLElement)) {
97
+ throw new Error(this.errorMessages.INVALID_CONTAINER);
98
+ }
99
+
100
+ // Check if already rendered
101
+ if (this.state.initialized) {
102
+ throw new Error(this.errorMessages.ALREADY_RENDERED);
103
+ }
104
+
105
+ try {
106
+ // Ensure script is loaded
107
+ await this.ensureScriptLoaded();
108
+
109
+ // Create and append the web component
110
+ const element = this.createWebComponent();
111
+
112
+ // Clear the container and append the element
113
+ container.innerHTML = '';
114
+ container.appendChild(element);
115
+
116
+ // Update state
117
+ this.state.container = container;
118
+ this.state.element = element;
119
+ this.state.initialized = true;
120
+ } catch (error) {
121
+ console.error('Failed to render widget:', error);
122
+ throw error;
123
+ }
124
+ }
125
+
68
126
  /**
69
127
  * Update the configuration of the widget
70
128
  * @param config - Partial configuration to update
71
129
  * @returns void
72
130
  */
73
- updateConfig(config: Partial<Config>): void;
131
+ updateConfig(config: Partial<Config>): void {
132
+ if (!this.state.initialized || !this.state.element) {
133
+ throw new Error(this.errorMessages.NOT_RENDERED);
134
+ }
135
+
136
+ const element = this.state.element as SdkElement<Config>;
137
+
138
+ Object.entries(config).forEach(([key, value]) => {
139
+ if (value) {
140
+ this.config[key as keyof Config] = value;
141
+ element[key as keyof Config] = value;
142
+ }
143
+ });
144
+ }
145
+
74
146
  /**
75
147
  * Destroy the widget and clean up resources
76
148
  */
77
- destroy(): void;
149
+ destroy(): void {
150
+ if (!this.state.initialized) {
151
+ return; // Nothing to destroy
152
+ }
153
+
154
+ // Remove the element from the container
155
+ if (this.state.element && this.state.element.parentNode) {
156
+ this.state.element.parentNode.removeChild(this.state.element);
157
+ }
158
+
159
+ // Clear the container
160
+ if (this.state.container) {
161
+ this.state.container.innerHTML = '';
162
+ }
163
+
164
+ // Reset state
165
+ this.state.container = null;
166
+ this.state.element = null;
167
+ this.state.initialized = false;
168
+ }
169
+
78
170
  /**
79
171
  * Check if the widget is currently rendered
80
172
  * @returns True if the widget is rendered, false otherwise
81
173
  */
82
- isRendered(): boolean;
174
+ isRendered(): boolean {
175
+ return this.state.initialized;
176
+ }
177
+
83
178
  /**
84
179
  * Get the current configuration
85
180
  * @returns The current configuration object
86
181
  */
87
- getConfig(): Readonly<Config>;
88
- private getEnvironment;
89
- private getScriptId;
90
- private isScriptLoaded;
91
- private getWebComponent;
92
- private getScriptUrl;
93
- private loadScript;
94
- private waitForWebComponent;
182
+ getConfig(): Readonly<Config> {
183
+ return { ...this.config };
184
+ }
185
+
186
+ private getEnvironment(): Environment {
187
+ return this.config.env || DEFAULT_ENVIRONMENT;
188
+ }
189
+
190
+ private getScriptId() {
191
+ return `${this.webComponentTag}-script-${this.getEnvironment()}`;
192
+ }
193
+
194
+ private isScriptLoaded() {
195
+ return !!document.getElementById(this.getScriptId());
196
+ }
197
+
198
+ private getWebComponent() {
199
+ return customElements.get(this.webComponentTag);
200
+ }
201
+
202
+ private getScriptUrl() {
203
+ // Support local development with Vite
204
+ if (typeof import.meta !== 'undefined' && import.meta.env?.['VITE_INTERNAL_BUILD'] === 'true') {
205
+ return import.meta.env['VITE_SCRIPT_URL'] || this.scriptUrls[this.getEnvironment()];
206
+ }
207
+
208
+ return this.scriptUrls[this.getEnvironment()];
209
+ }
210
+
211
+ private async loadScript() {
212
+ if (this.isScriptLoaded()) {
213
+ return;
214
+ }
215
+
216
+ if (this.scriptLoadingPromise) {
217
+ return this.scriptLoadingPromise;
218
+ }
219
+
220
+ this.scriptLoadingPromise = new Promise<void>((resolve, reject) => {
221
+ const script = document.createElement('script');
222
+ script.id = this.getScriptId();
223
+ script.src = this.getScriptUrl();
224
+ script.type = 'module';
225
+ script.async = true;
226
+
227
+ script.onload = () => {
228
+ setTimeout(() => {
229
+ if (this.getWebComponent()) {
230
+ resolve();
231
+ } else {
232
+ reject(new Error(this.errorMessages.WEB_COMPONENT_NOT_DEFINED));
233
+ }
234
+ }, 0);
235
+ };
236
+
237
+ script.onerror = () => {
238
+ this.scriptLoadingPromise = undefined;
239
+ reject(new Error(`${this.errorMessages.SCRIPT_LOAD_FAILED} (${this.getEnvironment()})`));
240
+ };
241
+
242
+ document.head.appendChild(script);
243
+ });
244
+
245
+ try {
246
+ await this.scriptLoadingPromise;
247
+ } catch (error) {
248
+ this.scriptLoadingPromise = undefined;
249
+ throw error;
250
+ }
251
+
252
+ return this.scriptLoadingPromise;
253
+ }
254
+
255
+ private async waitForWebComponent(timeout = 5000) {
256
+ if (this.getWebComponent()) {
257
+ return;
258
+ }
259
+
260
+ return new Promise<void>((resolve, reject) => {
261
+ const timeoutId = setTimeout(() => {
262
+ reject(new Error(`Timeout waiting for ${this.webComponentTag} to be defined`));
263
+ }, timeout);
264
+
265
+ customElements
266
+ .whenDefined(this.webComponentTag)
267
+ .then(() => {
268
+ clearTimeout(timeoutId);
269
+ resolve();
270
+ })
271
+ .catch((error) => {
272
+ clearTimeout(timeoutId);
273
+ reject(error);
274
+ });
275
+ });
276
+ }
277
+
95
278
  /**
96
279
  * Load the web component script if not already loaded
97
280
  */
98
- protected ensureScriptLoaded(): Promise<void>;
99
- private createWebComponent;
281
+ protected async ensureScriptLoaded(): Promise<void> {
282
+ if (this.state.scriptLoaded) {
283
+ return;
284
+ }
285
+
286
+ try {
287
+ await this.loadScript();
288
+ await this.waitForWebComponent();
289
+ this.state.scriptLoaded = true;
290
+ } catch (error) {
291
+ console.error('Failed to load Connect script:', error);
292
+ throw error;
293
+ }
294
+ }
295
+
296
+ private createWebComponent() {
297
+ const element = document.createElement(this.webComponentTag) as SdkElement<Config>;
298
+
299
+ Object.entries(this.config).forEach(([key, value]) => {
300
+ if (value) {
301
+ element[key as keyof Config] = value;
302
+ }
303
+ });
304
+
305
+ return element;
306
+ }
100
307
  }
101
308
 
102
309
  /**
@@ -154,6 +361,20 @@ declare type ErrorPayload = {
154
361
  */
155
362
  declare type SdkElement<Config extends BaseConfig<never>> = HTMLElement & Config;
156
363
 
364
+ /**
365
+ * Internal state of the SDK
366
+ */
367
+ declare interface SdkState {
368
+ /** Whether the SDK is initialized */
369
+ initialized: boolean;
370
+ /** Whether the web component script is loaded */
371
+ scriptLoaded: boolean;
372
+ /** The container element where the widget is rendered */
373
+ container: HTMLElement | null;
374
+ /** The web component element */
375
+ element: HTMLElement | null;
376
+ }
377
+
157
378
  /**
158
379
  * Theme configuration for the SDK
159
380
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@connect-xyz/withdraw-js",
3
- "version": "0.33.0",
3
+ "version": "0.35.0",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",