@memberjunction/ng-react 5.23.0 → 5.24.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.
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,145 @@
1
+ /**
2
+ * @vitest-environment jsdom
3
+ */
4
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
5
+ // Minimal mock for AngularAdapterService
6
+ function createMockAdapter(options) {
7
+ const { initializeShouldFail = false, createRootAvailable = true } = options ?? {};
8
+ return {
9
+ initialize: initializeShouldFail
10
+ ? vi.fn().mockRejectedValue(new Error('Failed to load React from all CDN sources'))
11
+ : vi.fn().mockResolvedValue(undefined),
12
+ getRuntimeContext: vi.fn().mockReturnValue({
13
+ ReactDOM: createRootAvailable
14
+ ? { createRoot: vi.fn().mockReturnValue({ unmount: vi.fn() }) }
15
+ : undefined,
16
+ }),
17
+ isInitialized: vi.fn().mockReturnValue(!initializeShouldFail && createRootAvailable),
18
+ destroy: vi.fn(),
19
+ };
20
+ }
21
+ // Mock external dependencies
22
+ vi.mock('@memberjunction/react-runtime', () => ({
23
+ reactRootManager: {
24
+ RegisterHook: vi.fn(),
25
+ cleanup: vi.fn(),
26
+ },
27
+ }));
28
+ vi.mock('../lib/config/react-debug.config', () => ({
29
+ ReactDebugConfig: {
30
+ getDebugMode: () => false,
31
+ },
32
+ }));
33
+ vi.mock('../lib/hooks/antd-dropdown-position-hook', () => ({
34
+ createAntdDropdownPositionHook: vi.fn().mockReturnValue({}),
35
+ }));
36
+ import { ReactBridgeService } from '../lib/services/react-bridge.service';
37
+ /** Construct a ReactBridgeService with a mocked adapter. */
38
+ function createService(adapter) {
39
+ vi.spyOn(console, 'log').mockImplementation(() => { });
40
+ vi.spyOn(console, 'warn').mockImplementation(() => { });
41
+ vi.spyOn(console, 'error').mockImplementation(() => { });
42
+ const service = new ReactBridgeService(adapter);
43
+ return service;
44
+ }
45
+ /** Shorten timeouts so tests run fast. */
46
+ function setFastTimeouts(service, overrides) {
47
+ const s = service;
48
+ s.maxWaitTime = overrides?.maxWaitTime ?? 150;
49
+ s.checkInterval = overrides?.checkInterval ?? 30;
50
+ s.maxBootstrapRetries = overrides?.maxBootstrapRetries ?? 2;
51
+ s.retryBaseDelay = overrides?.retryBaseDelay ?? 50;
52
+ }
53
+ describe('ReactBridgeService', () => {
54
+ beforeEach(() => {
55
+ vi.restoreAllMocks();
56
+ });
57
+ describe('bootstrapWithRetries (via getReactContext)', () => {
58
+ it('should resolve on first attempt when CDN is reachable', async () => {
59
+ const adapter = createMockAdapter();
60
+ const service = createService(adapter);
61
+ setFastTimeouts(service);
62
+ const context = await service.getReactContext();
63
+ expect(context.ReactDOM).toBeDefined();
64
+ expect(adapter.initialize).toHaveBeenCalled();
65
+ });
66
+ it('should retry and succeed when CDN recovers after initial failure', async () => {
67
+ const adapter = createMockAdapter({ initializeShouldFail: true });
68
+ const service = createService(adapter);
69
+ // Use 0 retries so the constructor's bootstrap fails fast
70
+ setFastTimeouts(service, { maxBootstrapRetries: 0 });
71
+ // Wait for constructor's bootstrap to fail (sets bootstrapPromise to null)
72
+ try {
73
+ await service.bootstrapPromise;
74
+ }
75
+ catch {
76
+ // Expected — CDN was "down"
77
+ }
78
+ // CDN recovers
79
+ adapter.initialize.mockResolvedValue(undefined);
80
+ adapter.getRuntimeContext.mockReturnValue({
81
+ ReactDOM: { createRoot: vi.fn().mockReturnValue({ unmount: vi.fn() }) },
82
+ });
83
+ // Restore retries and call getReactContext — should create a fresh bootstrap and succeed
84
+ setFastTimeouts(service, { maxBootstrapRetries: 2 });
85
+ const context = await service.getReactContext();
86
+ expect(context.ReactDOM).toBeDefined();
87
+ });
88
+ it('should throw after all bootstrap retries are exhausted', async () => {
89
+ const adapter = createMockAdapter({ initializeShouldFail: true });
90
+ const service = createService(adapter);
91
+ setFastTimeouts(service, { maxBootstrapRetries: 1 });
92
+ await expect(service.getReactContext()).rejects.toThrow(/CDN/);
93
+ });
94
+ });
95
+ describe('waitForReactReady', () => {
96
+ it('should resolve immediately when React is already available', async () => {
97
+ const adapter = createMockAdapter();
98
+ const service = createService(adapter);
99
+ setFastTimeouts(service);
100
+ // Await bootstrap first (same as component flow)
101
+ await service.getReactContext();
102
+ await expect(service.waitForReactReady()).resolves.toBeUndefined();
103
+ expect(service.isReady()).toBe(true);
104
+ });
105
+ it('should resolve after re-bootstrap when createRoot is initially broken (stale cache)', async () => {
106
+ // Bootstrap succeeds (scripts loaded) but createRoot not available (corrupt cache)
107
+ const adapter = createMockAdapter({ createRootAvailable: false });
108
+ const service = createService(adapter);
109
+ setFastTimeouts(service);
110
+ // Wait for initial bootstrap to complete
111
+ await service.getReactContext().catch(() => { });
112
+ // On re-bootstrap (triggered by createRoot poll failure), fix createRoot
113
+ let destroyCount = 0;
114
+ adapter.destroy.mockImplementation(() => {
115
+ destroyCount++;
116
+ // After destroy + re-init, make createRoot available
117
+ adapter.getRuntimeContext.mockReturnValue({
118
+ ReactDOM: { createRoot: vi.fn().mockReturnValue({ unmount: vi.fn() }) },
119
+ });
120
+ adapter.initialize.mockResolvedValue(undefined);
121
+ });
122
+ await expect(service.waitForReactReady()).resolves.toBeUndefined();
123
+ expect(service.isReady()).toBe(true);
124
+ expect(destroyCount).toBeGreaterThanOrEqual(1);
125
+ });
126
+ it('should throw with hard-refresh suggestion after all attempts fail', async () => {
127
+ const adapter = createMockAdapter({ createRootAvailable: false });
128
+ const service = createService(adapter);
129
+ setFastTimeouts(service, { maxBootstrapRetries: 0 });
130
+ await service.getReactContext().catch(() => { });
131
+ await expect(service.waitForReactReady()).rejects.toThrow(/hard refresh/);
132
+ });
133
+ it('should allow subsequent components to resolve via reactReady$ after first succeeds', async () => {
134
+ const adapter = createMockAdapter();
135
+ const service = createService(adapter);
136
+ setFastTimeouts(service);
137
+ await service.getReactContext();
138
+ await service.waitForReactReady();
139
+ // Second call resolves immediately via BehaviorSubject
140
+ await expect(service.waitForReactReady()).resolves.toBeUndefined();
141
+ expect(service.isReady()).toBe(true);
142
+ });
143
+ });
144
+ });
145
+ //# sourceMappingURL=react-bridge.service.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"react-bridge.service.test.js","sourceRoot":"","sources":["../../src/__tests__/react-bridge.service.test.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * @vitest-environment jsdom\n */\nimport { describe, it, expect, vi, beforeEach } from 'vitest';\n\n// Minimal mock for AngularAdapterService\nfunction createMockAdapter(options?: {\n initializeShouldFail?: boolean;\n createRootAvailable?: boolean;\n}) {\n const { initializeShouldFail = false, createRootAvailable = true } = options ?? {};\n\n return {\n initialize: initializeShouldFail\n ? vi.fn().mockRejectedValue(new Error('Failed to load React from all CDN sources'))\n : vi.fn().mockResolvedValue(undefined),\n getRuntimeContext: vi.fn().mockReturnValue({\n ReactDOM: createRootAvailable\n ? { createRoot: vi.fn().mockReturnValue({ unmount: vi.fn() }) }\n : undefined,\n }),\n isInitialized: vi.fn().mockReturnValue(!initializeShouldFail && createRootAvailable),\n destroy: vi.fn(),\n };\n}\n\n// Mock external dependencies\nvi.mock('@memberjunction/react-runtime', () => ({\n reactRootManager: {\n RegisterHook: vi.fn(),\n cleanup: vi.fn(),\n },\n}));\n\nvi.mock('../lib/config/react-debug.config', () => ({\n ReactDebugConfig: {\n getDebugMode: () => false,\n },\n}));\n\nvi.mock('../lib/hooks/antd-dropdown-position-hook', () => ({\n createAntdDropdownPositionHook: vi.fn().mockReturnValue({}),\n}));\n\nimport { ReactBridgeService } from '../lib/services/react-bridge.service';\n\n/** Construct a ReactBridgeService with a mocked adapter. */\nfunction createService(adapter: ReturnType<typeof createMockAdapter>): ReactBridgeService {\n vi.spyOn(console, 'log').mockImplementation(() => {});\n vi.spyOn(console, 'warn').mockImplementation(() => {});\n vi.spyOn(console, 'error').mockImplementation(() => {});\n\n const service = new (ReactBridgeService as unknown as new (a: unknown) => ReactBridgeService)(adapter);\n\n return service;\n}\n\n/** Shorten timeouts so tests run fast. */\nfunction setFastTimeouts(\n service: ReactBridgeService,\n overrides?: { maxWaitTime?: number; checkInterval?: number; maxBootstrapRetries?: number; retryBaseDelay?: number }\n) {\n const s = service as unknown as Record<string, number>;\n s.maxWaitTime = overrides?.maxWaitTime ?? 150;\n s.checkInterval = overrides?.checkInterval ?? 30;\n s.maxBootstrapRetries = overrides?.maxBootstrapRetries ?? 2;\n s.retryBaseDelay = overrides?.retryBaseDelay ?? 50;\n}\n\ndescribe('ReactBridgeService', () => {\n beforeEach(() => {\n vi.restoreAllMocks();\n });\n\n describe('bootstrapWithRetries (via getReactContext)', () => {\n it('should resolve on first attempt when CDN is reachable', async () => {\n const adapter = createMockAdapter();\n const service = createService(adapter);\n setFastTimeouts(service);\n\n const context = await service.getReactContext();\n expect(context.ReactDOM).toBeDefined();\n expect(adapter.initialize).toHaveBeenCalled();\n });\n\n it('should retry and succeed when CDN recovers after initial failure', async () => {\n const adapter = createMockAdapter({ initializeShouldFail: true });\n const service = createService(adapter);\n // Use 0 retries so the constructor's bootstrap fails fast\n setFastTimeouts(service, { maxBootstrapRetries: 0 });\n\n // Wait for constructor's bootstrap to fail (sets bootstrapPromise to null)\n try {\n await (service as unknown as { bootstrapPromise: Promise<void> | null }).bootstrapPromise;\n } catch {\n // Expected — CDN was \"down\"\n }\n\n // CDN recovers\n adapter.initialize.mockResolvedValue(undefined);\n adapter.getRuntimeContext.mockReturnValue({\n ReactDOM: { createRoot: vi.fn().mockReturnValue({ unmount: vi.fn() }) },\n });\n\n // Restore retries and call getReactContext — should create a fresh bootstrap and succeed\n setFastTimeouts(service, { maxBootstrapRetries: 2 });\n const context = await service.getReactContext();\n expect(context.ReactDOM).toBeDefined();\n });\n\n it('should throw after all bootstrap retries are exhausted', async () => {\n const adapter = createMockAdapter({ initializeShouldFail: true });\n const service = createService(adapter);\n setFastTimeouts(service, { maxBootstrapRetries: 1 });\n\n await expect(service.getReactContext()).rejects.toThrow(/CDN/);\n });\n });\n\n describe('waitForReactReady', () => {\n it('should resolve immediately when React is already available', async () => {\n const adapter = createMockAdapter();\n const service = createService(adapter);\n setFastTimeouts(service);\n\n // Await bootstrap first (same as component flow)\n await service.getReactContext();\n await expect(service.waitForReactReady()).resolves.toBeUndefined();\n expect(service.isReady()).toBe(true);\n });\n\n it('should resolve after re-bootstrap when createRoot is initially broken (stale cache)', async () => {\n // Bootstrap succeeds (scripts loaded) but createRoot not available (corrupt cache)\n const adapter = createMockAdapter({ createRootAvailable: false });\n const service = createService(adapter);\n setFastTimeouts(service);\n\n // Wait for initial bootstrap to complete\n await service.getReactContext().catch(() => {});\n\n // On re-bootstrap (triggered by createRoot poll failure), fix createRoot\n let destroyCount = 0;\n adapter.destroy.mockImplementation(() => {\n destroyCount++;\n // After destroy + re-init, make createRoot available\n adapter.getRuntimeContext.mockReturnValue({\n ReactDOM: { createRoot: vi.fn().mockReturnValue({ unmount: vi.fn() }) },\n });\n adapter.initialize.mockResolvedValue(undefined);\n });\n\n await expect(service.waitForReactReady()).resolves.toBeUndefined();\n expect(service.isReady()).toBe(true);\n expect(destroyCount).toBeGreaterThanOrEqual(1);\n });\n\n it('should throw with hard-refresh suggestion after all attempts fail', async () => {\n const adapter = createMockAdapter({ createRootAvailable: false });\n const service = createService(adapter);\n setFastTimeouts(service, { maxBootstrapRetries: 0 });\n\n await service.getReactContext().catch(() => {});\n\n await expect(service.waitForReactReady()).rejects.toThrow(/hard refresh/);\n });\n\n it('should allow subsequent components to resolve via reactReady$ after first succeeds', async () => {\n const adapter = createMockAdapter();\n const service = createService(adapter);\n setFastTimeouts(service);\n\n await service.getReactContext();\n await service.waitForReactReady();\n\n // Second call resolves immediately via BehaviorSubject\n await expect(service.waitForReactReady()).resolves.toBeUndefined();\n expect(service.isReady()).toBe(true);\n });\n });\n});\n"]}
@@ -19,19 +19,43 @@ export declare class ReactBridgeService implements OnDestroy {
19
19
  private firstComponentAttempted;
20
20
  private maxWaitTime;
21
21
  private checkInterval;
22
+ private maxBootstrapRetries;
23
+ private retryBaseDelay;
22
24
  debug: boolean;
25
+ private bootstrapPromise;
23
26
  constructor(adapter: AngularAdapterService);
24
27
  ngOnDestroy(): void;
25
28
  /**
26
- * Bootstrap React early during service initialization
29
+ * Bootstrap React from CDN with automatic retries.
30
+ * Deduplicates: concurrent callers share the same in-flight promise.
31
+ * On failure, clears the promise so the next call can retry.
27
32
  */
28
- private bootstrapReact;
33
+ private bootstrapWithRetries;
29
34
  /**
30
- * Wait for React to be ready, with special handling for first component
35
+ * Attempt to load the React ecosystem from CDN, retrying with exponential
36
+ * backoff if the CDN is unreachable or returns an error.
37
+ */
38
+ private doBootstrapWithRetries;
39
+ /**
40
+ * Wait for React to be ready, with special handling for first component.
41
+ * If bootstrap succeeded but createRoot isn't usable (stale browser cache),
42
+ * forces a re-bootstrap and retries.
31
43
  */
32
44
  waitForReactReady(): Promise<void>;
33
45
  /**
34
- * Get the current React context if loaded
46
+ * Poll for ReactDOM.createRoot availability. If the poll window expires
47
+ * (bootstrap succeeded but createRoot is broken, e.g. stale cache),
48
+ * force a fresh re-bootstrap and retry.
49
+ */
50
+ private pollForReactReadyWithRetries;
51
+ /**
52
+ * Poll for ReactDOM.createRoot for up to maxWaitTime ms.
53
+ * @returns true if createRoot became available, false if the poll window expired.
54
+ */
55
+ private pollForCreateRoot;
56
+ /**
57
+ * Get the current React context if loaded.
58
+ * Awaits the bootstrap (with retries) rather than calling adapter.initialize() independently.
35
59
  * @returns React context with React, ReactDOM, Babel, and libraries
36
60
  */
37
61
  getReactContext(): Promise<RuntimeContext>;
@@ -25,40 +25,69 @@ export class ReactBridgeService {
25
25
  this.reactReady$ = this.reactReadySubject.asObservable();
26
26
  // Track if this is the first component trying to use React
27
27
  this.firstComponentAttempted = false;
28
- this.maxWaitTime = 5000; // Maximum 5 seconds wait time
28
+ this.maxWaitTime = 5000; // Maximum 5 seconds wait time for createRoot poll
29
29
  this.checkInterval = 200; // Check every 200ms
30
+ this.maxBootstrapRetries = 2; // Retry bootstrap up to 2 additional times
31
+ this.retryBaseDelay = 2000; // Base delay between bootstrap retries (ms)
30
32
  // Debug flag from project configuration
31
33
  this.debug = ReactDebugConfig.getDebugMode();
34
+ // The current bootstrap attempt — shared between constructor and getReactContext()
35
+ this.bootstrapPromise = null;
32
36
  // Bootstrap React immediately on service initialization
33
- this.bootstrapReact();
37
+ this.bootstrapWithRetries();
34
38
  }
35
39
  ngOnDestroy() {
36
40
  this.cleanup();
37
41
  }
38
42
  /**
39
- * Bootstrap React early during service initialization
43
+ * Bootstrap React from CDN with automatic retries.
44
+ * Deduplicates: concurrent callers share the same in-flight promise.
45
+ * On failure, clears the promise so the next call can retry.
40
46
  */
41
- async bootstrapReact() {
42
- try {
43
- // Log the debug mode being used
44
- console.log(`ReactBridgeService: Initializing React with debug mode = ${this.debug} (from ReactDebugConfig)`);
45
- // Pass debug flag to get development builds when debug is enabled
46
- await this.adapter.initialize(undefined, undefined, { debug: this.debug });
47
- // Register Angular-specific runtime hooks for library compatibility
48
- reactRootManager.RegisterHook(createAntdDropdownPositionHook());
49
- if (this.debug) {
50
- console.log('React ecosystem pre-loaded successfully with DEVELOPMENT builds (detailed error messages)');
47
+ bootstrapWithRetries() {
48
+ if (!this.bootstrapPromise) {
49
+ this.bootstrapPromise = this.doBootstrapWithRetries();
50
+ }
51
+ return this.bootstrapPromise;
52
+ }
53
+ /**
54
+ * Attempt to load the React ecosystem from CDN, retrying with exponential
55
+ * backoff if the CDN is unreachable or returns an error.
56
+ */
57
+ async doBootstrapWithRetries() {
58
+ console.log(`ReactBridgeService: Initializing React with debug mode = ${this.debug} (from ReactDebugConfig)`);
59
+ let lastError;
60
+ for (let attempt = 0; attempt <= this.maxBootstrapRetries; attempt++) {
61
+ try {
62
+ if (attempt > 0) {
63
+ const delay = this.retryBaseDelay * Math.pow(2, attempt - 1);
64
+ console.warn(`React CDN bootstrap attempt ${attempt + 1} of ${this.maxBootstrapRetries + 1} — retrying in ${delay}ms...`);
65
+ await new Promise(resolve => setTimeout(resolve, delay));
66
+ }
67
+ await this.adapter.initialize(undefined, undefined, { debug: this.debug });
68
+ // Register Angular-specific runtime hooks for library compatibility
69
+ reactRootManager.RegisterHook(createAntdDropdownPositionHook());
70
+ if (this.debug) {
71
+ console.log('React ecosystem pre-loaded successfully with DEVELOPMENT builds (detailed error messages)');
72
+ }
73
+ else {
74
+ console.log('React ecosystem pre-loaded successfully with PRODUCTION builds (minified)');
75
+ }
76
+ return; // Success
51
77
  }
52
- else {
53
- console.log('React ecosystem pre-loaded successfully with PRODUCTION builds (minified)');
78
+ catch (error) {
79
+ lastError = error;
80
+ console.error(`React bootstrap attempt ${attempt + 1} failed:`, error);
54
81
  }
55
82
  }
56
- catch (error) {
57
- console.error('Failed to pre-load React ecosystem:', error);
58
- }
83
+ // All attempts exhausted — clear promise so future calls can retry
84
+ this.bootstrapPromise = null;
85
+ throw lastError;
59
86
  }
60
87
  /**
61
- * Wait for React to be ready, with special handling for first component
88
+ * Wait for React to be ready, with special handling for first component.
89
+ * If bootstrap succeeded but createRoot isn't usable (stale browser cache),
90
+ * forces a re-bootstrap and retries.
62
91
  */
63
92
  async waitForReactReady() {
64
93
  // If already ready, return immediately
@@ -69,51 +98,82 @@ export class ReactBridgeService {
69
98
  const isFirstComponent = !this.firstComponentAttempted;
70
99
  this.firstComponentAttempted = true;
71
100
  if (isFirstComponent) {
72
- // First component - check periodically until React is ready
73
- if (this.debug) {
74
- console.log('First React component loading - checking for React initialization');
75
- }
76
- const startTime = Date.now();
77
- while (Date.now() - startTime < this.maxWaitTime) {
101
+ await this.pollForReactReadyWithRetries();
102
+ }
103
+ else {
104
+ // Subsequent components wait for the ready signal
105
+ await firstValueFrom(this.reactReady$.pipe(filter(ready => ready)));
106
+ }
107
+ }
108
+ /**
109
+ * Poll for ReactDOM.createRoot availability. If the poll window expires
110
+ * (bootstrap succeeded but createRoot is broken, e.g. stale cache),
111
+ * force a fresh re-bootstrap and retry.
112
+ */
113
+ async pollForReactReadyWithRetries() {
114
+ const overallStart = Date.now();
115
+ for (let attempt = 0; attempt <= this.maxBootstrapRetries; attempt++) {
116
+ if (attempt > 0) {
117
+ console.warn(`React createRoot check failed (attempt ${attempt}) — forcing fresh CDN re-bootstrap...`);
118
+ // Clear the cached bootstrap promise so doBootstrapWithRetries runs fresh
119
+ this.bootstrapPromise = null;
120
+ this.adapter.destroy();
78
121
  try {
79
- const testDiv = document.createElement('div');
80
- const context = this.adapter.getRuntimeContext();
81
- if (context.ReactDOM?.createRoot) {
82
- // Try to create a test root
83
- const testRoot = context.ReactDOM.createRoot(testDiv);
84
- if (testRoot) {
85
- testRoot.unmount();
86
- // React is ready!
87
- this.reactReadySubject.next(true);
88
- if (this.debug) {
89
- console.log(`React is fully ready after ${Date.now() - startTime}ms`);
90
- }
91
- return;
92
- }
93
- }
122
+ await this.bootstrapWithRetries();
94
123
  }
95
- catch (error) {
96
- // Not ready yet, continue checking
124
+ catch {
125
+ // Bootstrap itself failed continue to next poll attempt
126
+ continue;
127
+ }
128
+ }
129
+ const ready = await this.pollForCreateRoot();
130
+ if (ready) {
131
+ this.reactReadySubject.next(true);
132
+ if (this.debug) {
133
+ console.log(`React is fully ready after ${Date.now() - overallStart}ms (attempt ${attempt + 1})`);
97
134
  }
98
- // Wait before next check
99
- await new Promise(resolve => setTimeout(resolve, this.checkInterval));
135
+ return;
100
136
  }
101
- // If we've exhausted the wait time, throw error
102
- console.error('React readiness test failed after maximum wait time');
103
- this.firstComponentAttempted = false;
104
- throw new Error(`ReactDOM.createRoot not available after ${this.maxWaitTime}ms`);
105
137
  }
106
- else {
107
- // Subsequent components wait for the ready signal
108
- await firstValueFrom(this.reactReady$.pipe(filter(ready => ready)));
138
+ // All attempts exhausted
139
+ const totalTime = Date.now() - overallStart;
140
+ console.error(`React readiness test failed after ${this.maxBootstrapRetries + 1} attempts (${totalTime}ms total)`);
141
+ this.firstComponentAttempted = false;
142
+ throw new Error(`ReactDOM.createRoot not available after ${this.maxBootstrapRetries + 1} attempts (${totalTime}ms). ` +
143
+ `Try a hard refresh (Ctrl+Shift+R) to clear the browser cache.`);
144
+ }
145
+ /**
146
+ * Poll for ReactDOM.createRoot for up to maxWaitTime ms.
147
+ * @returns true if createRoot became available, false if the poll window expired.
148
+ */
149
+ async pollForCreateRoot() {
150
+ const startTime = Date.now();
151
+ while (Date.now() - startTime < this.maxWaitTime) {
152
+ try {
153
+ const testDiv = document.createElement('div');
154
+ const context = this.adapter.getRuntimeContext();
155
+ if (context.ReactDOM?.createRoot) {
156
+ const testRoot = context.ReactDOM.createRoot(testDiv);
157
+ if (testRoot) {
158
+ testRoot.unmount();
159
+ return true;
160
+ }
161
+ }
162
+ }
163
+ catch {
164
+ // Not ready yet, continue checking
165
+ }
166
+ await new Promise(resolve => setTimeout(resolve, this.checkInterval));
109
167
  }
168
+ return false;
110
169
  }
111
170
  /**
112
- * Get the current React context if loaded
171
+ * Get the current React context if loaded.
172
+ * Awaits the bootstrap (with retries) rather than calling adapter.initialize() independently.
113
173
  * @returns React context with React, ReactDOM, Babel, and libraries
114
174
  */
115
175
  async getReactContext() {
116
- await this.adapter.initialize(undefined, undefined, { debug: this.debug });
176
+ await this.bootstrapWithRetries();
117
177
  return this.adapter.getRuntimeContext();
118
178
  }
119
179
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"react-bridge.service.js","sourceRoot":"","sources":["../../../src/lib/services/react-bridge.service.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * @fileoverview Service to manage React and ReactDOM instances with proper lifecycle.\n * Bridges Angular components with React rendering capabilities.\n * @module @memberjunction/ng-react\n */\n\nimport { Injectable, OnDestroy } from '@angular/core';\nimport { BehaviorSubject, firstValueFrom } from 'rxjs';\nimport { filter } from 'rxjs/operators';\nimport { AngularAdapterService } from './angular-adapter.service';\nimport { RuntimeContext, reactRootManager } from '@memberjunction/react-runtime';\nimport { ReactDebugConfig } from '../config/react-debug.config';\nimport { createAntdDropdownPositionHook } from '../hooks/antd-dropdown-position-hook';\n\n/**\n * Service to manage React and ReactDOM instances with proper lifecycle.\n * Provides methods for creating and managing React roots in Angular applications.\n */\n@Injectable({ providedIn: 'root' })\nexport class ReactBridgeService implements OnDestroy {\n private reactRoots = new Set<any>();\n \n // Track React readiness state\n private reactReadySubject = new BehaviorSubject<boolean>(false);\n public reactReady$ = this.reactReadySubject.asObservable();\n \n // Track if this is the first component trying to use React\n private firstComponentAttempted = false;\n private maxWaitTime = 5000; // Maximum 5 seconds wait time\n private checkInterval = 200; // Check every 200ms\n \n // Debug flag from project configuration\n public debug: boolean = ReactDebugConfig.getDebugMode();\n\n constructor(private adapter: AngularAdapterService) {\n // Bootstrap React immediately on service initialization\n this.bootstrapReact();\n }\n\n ngOnDestroy(): void {\n this.cleanup();\n }\n\n /**\n * Bootstrap React early during service initialization\n */\n private async bootstrapReact(): Promise<void> {\n try {\n // Log the debug mode being used\n console.log(`ReactBridgeService: Initializing React with debug mode = ${this.debug} (from ReactDebugConfig)`);\n \n // Pass debug flag to get development builds when debug is enabled\n await this.adapter.initialize(undefined, undefined, { debug: this.debug });\n\n // Register Angular-specific runtime hooks for library compatibility\n reactRootManager.RegisterHook(createAntdDropdownPositionHook());\n\n if (this.debug) {\n console.log('React ecosystem pre-loaded successfully with DEVELOPMENT builds (detailed error messages)');\n } else {\n console.log('React ecosystem pre-loaded successfully with PRODUCTION builds (minified)');\n }\n } catch (error) {\n console.error('Failed to pre-load React ecosystem:', error);\n }\n }\n\n /**\n * Wait for React to be ready, with special handling for first component\n */\n async waitForReactReady(): Promise<void> {\n // If already ready, return immediately\n if (this.reactReadySubject.value) {\n return;\n }\n\n // Check if this is the first component attempting to use React\n const isFirstComponent = !this.firstComponentAttempted;\n this.firstComponentAttempted = true;\n\n if (isFirstComponent) {\n // First component - check periodically until React is ready\n if (this.debug) {\n console.log('First React component loading - checking for React initialization');\n }\n \n const startTime = Date.now();\n \n while (Date.now() - startTime < this.maxWaitTime) {\n try {\n const testDiv = document.createElement('div');\n const context = this.adapter.getRuntimeContext();\n \n if (context.ReactDOM?.createRoot) {\n // Try to create a test root\n const testRoot = context.ReactDOM.createRoot(testDiv);\n if (testRoot) {\n testRoot.unmount();\n // React is ready!\n this.reactReadySubject.next(true);\n if (this.debug) {\n console.log(`React is fully ready after ${Date.now() - startTime}ms`);\n }\n return;\n }\n }\n } catch (error) {\n // Not ready yet, continue checking\n }\n \n // Wait before next check\n await new Promise(resolve => setTimeout(resolve, this.checkInterval));\n }\n \n // If we've exhausted the wait time, throw error\n console.error('React readiness test failed after maximum wait time');\n this.firstComponentAttempted = false;\n throw new Error(`ReactDOM.createRoot not available after ${this.maxWaitTime}ms`);\n } else {\n // Subsequent components wait for the ready signal\n await firstValueFrom(this.reactReady$.pipe(filter(ready => ready)));\n }\n }\n\n /**\n * Get the current React context if loaded\n * @returns React context with React, ReactDOM, Babel, and libraries\n */\n async getReactContext(): Promise<RuntimeContext> {\n await this.adapter.initialize(undefined, undefined, {debug: this.debug});\n return this.adapter.getRuntimeContext();\n }\n\n /**\n * Get the current React context synchronously\n * @returns React context or null if not loaded\n */\n getCurrentContext(): RuntimeContext | null {\n if (!this.adapter.isInitialized()) {\n return null;\n }\n return this.adapter.getRuntimeContext();\n }\n\n /**\n * Create a React root for rendering\n * @param container - DOM element to render into\n * @returns React root instance\n */\n createRoot(container: HTMLElement): any {\n const context = this.getCurrentContext();\n if (!context?.ReactDOM?.createRoot) {\n throw new Error('ReactDOM.createRoot not available');\n }\n\n const root = context.ReactDOM.createRoot(container);\n this.reactRoots.add(root);\n return root;\n }\n\n /**\n * Unmount and clean up a React root\n * @param root - React root to unmount\n */\n unmountRoot(root: any): void {\n if (root && typeof root.unmount === 'function') {\n try {\n root.unmount();\n } catch (error) {\n console.warn('Failed to unmount React root:', error);\n }\n }\n this.reactRoots.delete(root);\n }\n\n /**\n * Transpile JSX code to JavaScript\n * @param code - JSX code to transpile\n * @param filename - Optional filename for error messages\n * @returns Transpiled JavaScript code\n */\n transpileJSX(code: string, filename: string): string {\n return this.adapter.transpileJSX(code, filename);\n }\n\n /**\n * Clean up all React roots and reset context\n */\n private cleanup(): void {\n // Unmount all tracked React roots\n for (const root of this.reactRoots) {\n try {\n root.unmount();\n } catch (error) {\n console.warn('Failed to unmount React root:', error);\n }\n }\n this.reactRoots.clear();\n \n // Reset readiness state\n this.reactReadySubject.next(false);\n this.firstComponentAttempted = false;\n\n // Clean up runtime hooks (disconnects observers, removes injected styles, etc.)\n reactRootManager.cleanup();\n\n // Clean up adapter\n this.adapter.destroy();\n }\n\n /**\n * Check if React is currently ready\n * @returns true if React is ready\n */\n isReady(): boolean {\n return this.reactReadySubject.value;\n }\n\n /**\n * Get the number of active React roots\n * @returns Number of active roots\n */\n getActiveRootsCount(): number {\n return this.reactRoots.size;\n }\n}"]}
1
+ {"version":3,"file":"react-bridge.service.js","sourceRoot":"","sources":["../../../src/lib/services/react-bridge.service.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * @fileoverview Service to manage React and ReactDOM instances with proper lifecycle.\n * Bridges Angular components with React rendering capabilities.\n * @module @memberjunction/ng-react\n */\n\nimport { Injectable, OnDestroy } from '@angular/core';\nimport { BehaviorSubject, firstValueFrom } from 'rxjs';\nimport { filter } from 'rxjs/operators';\nimport { AngularAdapterService } from './angular-adapter.service';\nimport { RuntimeContext, reactRootManager } from '@memberjunction/react-runtime';\nimport { ReactDebugConfig } from '../config/react-debug.config';\nimport { createAntdDropdownPositionHook } from '../hooks/antd-dropdown-position-hook';\n\n/**\n * Service to manage React and ReactDOM instances with proper lifecycle.\n * Provides methods for creating and managing React roots in Angular applications.\n */\n@Injectable({ providedIn: 'root' })\nexport class ReactBridgeService implements OnDestroy {\n private reactRoots = new Set<any>();\n\n // Track React readiness state\n private reactReadySubject = new BehaviorSubject<boolean>(false);\n public reactReady$ = this.reactReadySubject.asObservable();\n\n // Track if this is the first component trying to use React\n private firstComponentAttempted = false;\n private maxWaitTime = 5000; // Maximum 5 seconds wait time for createRoot poll\n private checkInterval = 200; // Check every 200ms\n private maxBootstrapRetries = 2; // Retry bootstrap up to 2 additional times\n private retryBaseDelay = 2000; // Base delay between bootstrap retries (ms)\n\n // Debug flag from project configuration\n public debug: boolean = ReactDebugConfig.getDebugMode();\n\n // The current bootstrap attempt — shared between constructor and getReactContext()\n private bootstrapPromise: Promise<void> | null = null;\n\n constructor(private adapter: AngularAdapterService) {\n // Bootstrap React immediately on service initialization\n this.bootstrapWithRetries();\n }\n\n ngOnDestroy(): void {\n this.cleanup();\n }\n\n /**\n * Bootstrap React from CDN with automatic retries.\n * Deduplicates: concurrent callers share the same in-flight promise.\n * On failure, clears the promise so the next call can retry.\n */\n private bootstrapWithRetries(): Promise<void> {\n if (!this.bootstrapPromise) {\n this.bootstrapPromise = this.doBootstrapWithRetries();\n }\n return this.bootstrapPromise;\n }\n\n /**\n * Attempt to load the React ecosystem from CDN, retrying with exponential\n * backoff if the CDN is unreachable or returns an error.\n */\n private async doBootstrapWithRetries(): Promise<void> {\n console.log(`ReactBridgeService: Initializing React with debug mode = ${this.debug} (from ReactDebugConfig)`);\n let lastError: unknown;\n\n for (let attempt = 0; attempt <= this.maxBootstrapRetries; attempt++) {\n try {\n if (attempt > 0) {\n const delay = this.retryBaseDelay * Math.pow(2, attempt - 1);\n console.warn(\n `React CDN bootstrap attempt ${attempt + 1} of ${this.maxBootstrapRetries + 1} — retrying in ${delay}ms...`\n );\n await new Promise(resolve => setTimeout(resolve, delay));\n }\n\n await this.adapter.initialize(undefined, undefined, { debug: this.debug });\n\n // Register Angular-specific runtime hooks for library compatibility\n reactRootManager.RegisterHook(createAntdDropdownPositionHook());\n\n if (this.debug) {\n console.log('React ecosystem pre-loaded successfully with DEVELOPMENT builds (detailed error messages)');\n } else {\n console.log('React ecosystem pre-loaded successfully with PRODUCTION builds (minified)');\n }\n return; // Success\n } catch (error) {\n lastError = error;\n console.error(`React bootstrap attempt ${attempt + 1} failed:`, error);\n }\n }\n\n // All attempts exhausted — clear promise so future calls can retry\n this.bootstrapPromise = null;\n throw lastError;\n }\n\n /**\n * Wait for React to be ready, with special handling for first component.\n * If bootstrap succeeded but createRoot isn't usable (stale browser cache),\n * forces a re-bootstrap and retries.\n */\n async waitForReactReady(): Promise<void> {\n // If already ready, return immediately\n if (this.reactReadySubject.value) {\n return;\n }\n\n // Check if this is the first component attempting to use React\n const isFirstComponent = !this.firstComponentAttempted;\n this.firstComponentAttempted = true;\n\n if (isFirstComponent) {\n await this.pollForReactReadyWithRetries();\n } else {\n // Subsequent components wait for the ready signal\n await firstValueFrom(this.reactReady$.pipe(filter(ready => ready)));\n }\n }\n\n /**\n * Poll for ReactDOM.createRoot availability. If the poll window expires\n * (bootstrap succeeded but createRoot is broken, e.g. stale cache),\n * force a fresh re-bootstrap and retry.\n */\n private async pollForReactReadyWithRetries(): Promise<void> {\n const overallStart = Date.now();\n\n for (let attempt = 0; attempt <= this.maxBootstrapRetries; attempt++) {\n if (attempt > 0) {\n console.warn(\n `React createRoot check failed (attempt ${attempt}) — forcing fresh CDN re-bootstrap...`\n );\n // Clear the cached bootstrap promise so doBootstrapWithRetries runs fresh\n this.bootstrapPromise = null;\n this.adapter.destroy();\n try {\n await this.bootstrapWithRetries();\n } catch {\n // Bootstrap itself failed — continue to next poll attempt\n continue;\n }\n }\n\n const ready = await this.pollForCreateRoot();\n if (ready) {\n this.reactReadySubject.next(true);\n if (this.debug) {\n console.log(`React is fully ready after ${Date.now() - overallStart}ms (attempt ${attempt + 1})`);\n }\n return;\n }\n }\n\n // All attempts exhausted\n const totalTime = Date.now() - overallStart;\n console.error(\n `React readiness test failed after ${this.maxBootstrapRetries + 1} attempts (${totalTime}ms total)`\n );\n this.firstComponentAttempted = false;\n throw new Error(\n `ReactDOM.createRoot not available after ${this.maxBootstrapRetries + 1} attempts (${totalTime}ms). ` +\n `Try a hard refresh (Ctrl+Shift+R) to clear the browser cache.`\n );\n }\n\n /**\n * Poll for ReactDOM.createRoot for up to maxWaitTime ms.\n * @returns true if createRoot became available, false if the poll window expired.\n */\n private async pollForCreateRoot(): Promise<boolean> {\n const startTime = Date.now();\n\n while (Date.now() - startTime < this.maxWaitTime) {\n try {\n const testDiv = document.createElement('div');\n const context = this.adapter.getRuntimeContext();\n\n if (context.ReactDOM?.createRoot) {\n const testRoot = context.ReactDOM.createRoot(testDiv);\n if (testRoot) {\n testRoot.unmount();\n return true;\n }\n }\n } catch {\n // Not ready yet, continue checking\n }\n\n await new Promise(resolve => setTimeout(resolve, this.checkInterval));\n }\n\n return false;\n }\n\n /**\n * Get the current React context if loaded.\n * Awaits the bootstrap (with retries) rather than calling adapter.initialize() independently.\n * @returns React context with React, ReactDOM, Babel, and libraries\n */\n async getReactContext(): Promise<RuntimeContext> {\n await this.bootstrapWithRetries();\n return this.adapter.getRuntimeContext();\n }\n\n /**\n * Get the current React context synchronously\n * @returns React context or null if not loaded\n */\n getCurrentContext(): RuntimeContext | null {\n if (!this.adapter.isInitialized()) {\n return null;\n }\n return this.adapter.getRuntimeContext();\n }\n\n /**\n * Create a React root for rendering\n * @param container - DOM element to render into\n * @returns React root instance\n */\n createRoot(container: HTMLElement): any {\n const context = this.getCurrentContext();\n if (!context?.ReactDOM?.createRoot) {\n throw new Error('ReactDOM.createRoot not available');\n }\n\n const root = context.ReactDOM.createRoot(container);\n this.reactRoots.add(root);\n return root;\n }\n\n /**\n * Unmount and clean up a React root\n * @param root - React root to unmount\n */\n unmountRoot(root: any): void {\n if (root && typeof root.unmount === 'function') {\n try {\n root.unmount();\n } catch (error) {\n console.warn('Failed to unmount React root:', error);\n }\n }\n this.reactRoots.delete(root);\n }\n\n /**\n * Transpile JSX code to JavaScript\n * @param code - JSX code to transpile\n * @param filename - Optional filename for error messages\n * @returns Transpiled JavaScript code\n */\n transpileJSX(code: string, filename: string): string {\n return this.adapter.transpileJSX(code, filename);\n }\n\n /**\n * Clean up all React roots and reset context\n */\n private cleanup(): void {\n // Unmount all tracked React roots\n for (const root of this.reactRoots) {\n try {\n root.unmount();\n } catch (error) {\n console.warn('Failed to unmount React root:', error);\n }\n }\n this.reactRoots.clear();\n \n // Reset readiness state\n this.reactReadySubject.next(false);\n this.firstComponentAttempted = false;\n\n // Clean up runtime hooks (disconnects observers, removes injected styles, etc.)\n reactRootManager.cleanup();\n\n // Clean up adapter\n this.adapter.destroy();\n }\n\n /**\n * Check if React is currently ready\n * @returns true if React is ready\n */\n isReady(): boolean {\n return this.reactReadySubject.value;\n }\n\n /**\n * Get the number of active React roots\n * @returns Number of active roots\n */\n getActiveRootsCount(): number {\n return this.reactRoots.size;\n }\n}"]}
package/package.json CHANGED
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "name": "@memberjunction/ng-react",
3
- "version": "5.23.0",
3
+ "version": "5.24.0",
4
4
  "description": "Angular components for hosting React components in MemberJunction applications",
5
5
  "scripts": {
6
6
  "build": "ngc -p tsconfig.json",
7
7
  "watch": "ngc -p tsconfig.json --watch",
8
- "test": "echo \"Tests not yet implemented\"",
8
+ "test": "vitest run",
9
9
  "patchVersion": "npm version patch",
10
10
  "test:watch": "vitest"
11
11
  },
@@ -41,14 +41,14 @@
41
41
  "styles"
42
42
  ],
43
43
  "dependencies": {
44
- "@memberjunction/ai-vectors-memory": "5.23.0",
45
- "@memberjunction/core": "5.23.0",
46
- "@memberjunction/core-entities": "5.23.0",
47
- "@memberjunction/global": "5.23.0",
48
- "@memberjunction/graphql-dataprovider": "5.23.0",
49
- "@memberjunction/interactive-component-types": "5.23.0",
50
- "@memberjunction/ng-notifications": "5.23.0",
51
- "@memberjunction/react-runtime": "5.23.0",
44
+ "@memberjunction/ai-vectors-memory": "5.24.0",
45
+ "@memberjunction/core": "5.24.0",
46
+ "@memberjunction/core-entities": "5.24.0",
47
+ "@memberjunction/global": "5.24.0",
48
+ "@memberjunction/graphql-dataprovider": "5.24.0",
49
+ "@memberjunction/interactive-component-types": "5.24.0",
50
+ "@memberjunction/ng-notifications": "5.24.0",
51
+ "@memberjunction/react-runtime": "5.24.0",
52
52
  "@angular/common": "21.1.3",
53
53
  "@angular/core": "21.1.3",
54
54
  "@angular/platform-browser": "21.1.3",