@tagadapay/plugin-sdk 3.0.14 → 3.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.
@@ -0,0 +1,242 @@
1
+ 'use client';
2
+ import { useEffect, useRef } from 'react';
3
+ /**
4
+ * FunnelScriptInjector - Handles injection of funnel scripts into the page.
5
+ *
6
+ * This component:
7
+ * - Sets up Tagada on the window object
8
+ * - Injects and manages funnel scripts from the context
9
+ * - Prevents duplicate script injection (handles React StrictMode)
10
+ * - Cleans up scripts when context changes or component unmounts
11
+ */
12
+ export function FunnelScriptInjector({ context, isInitialized }) {
13
+ console.log('FunnelScriptInjector here', context, isInitialized); // Track last injected script to prevent duplicate execution
14
+ const lastInjectedScriptRef = useRef(null);
15
+ useEffect(() => {
16
+ // Only run in browser environment
17
+ if (typeof document === 'undefined') {
18
+ return;
19
+ }
20
+ // Set up Tagada for funnel scripts (similar to HtmlScript.tsx)
21
+ const setupTagada = () => {
22
+ // @ts-expect-error - Adding utilities to window
23
+ if (window.Tagada) {
24
+ // Update pageType if context is available
25
+ if (context?.currentStepId) {
26
+ // @ts-expect-error - Updating window property
27
+ window.Tagada.pageType = context.currentStepId;
28
+ }
29
+ // Update isInitialized
30
+ // @ts-expect-error - Updating window property
31
+ window.Tagada.isInitialized = isInitialized;
32
+ // Update ressources
33
+ // @ts-expect-error - Updating window property
34
+ window.Tagada.ressources = context?.resources || null;
35
+ return; // Utils already exist, just update properties
36
+ }
37
+ // @ts-expect-error - Adding utilities to window
38
+ window.Tagada = {
39
+ // Wait for DOM to be ready
40
+ ready: (callback) => {
41
+ if (document.readyState === 'loading') {
42
+ document.addEventListener('DOMContentLoaded', callback);
43
+ }
44
+ else {
45
+ callback();
46
+ }
47
+ },
48
+ // Wait for window to be fully loaded AND funnel to be initialized
49
+ loaded: (callback) => {
50
+ const checkBothConditions = () => {
51
+ const pageLoaded = document.readyState === 'complete';
52
+ // @ts-expect-error - Accessing window property
53
+ const funnelInitialized = window.Tagada?.isInitialized === true;
54
+ if (pageLoaded && funnelInitialized) {
55
+ callback();
56
+ return true;
57
+ }
58
+ return false;
59
+ };
60
+ // Check immediately
61
+ if (checkBothConditions()) {
62
+ return;
63
+ }
64
+ // Set up listeners for both conditions
65
+ let loadListener = null;
66
+ let initCheckInterval = null;
67
+ let hasCalled = false;
68
+ const cleanup = () => {
69
+ if (loadListener) {
70
+ window.removeEventListener('load', loadListener);
71
+ loadListener = null;
72
+ }
73
+ if (initCheckInterval) {
74
+ clearInterval(initCheckInterval);
75
+ initCheckInterval = null;
76
+ }
77
+ };
78
+ // Listen for page load
79
+ loadListener = () => {
80
+ if (checkBothConditions() && !hasCalled) {
81
+ hasCalled = true;
82
+ cleanup();
83
+ }
84
+ };
85
+ window.addEventListener('load', loadListener);
86
+ // Poll for initialization status (in case page loads before initialization)
87
+ initCheckInterval = setInterval(() => {
88
+ if (checkBothConditions() && !hasCalled) {
89
+ hasCalled = true;
90
+ cleanup();
91
+ }
92
+ }, 100);
93
+ // Timeout fallback (10 seconds max wait)
94
+ setTimeout(() => {
95
+ if (!hasCalled) {
96
+ hasCalled = true;
97
+ cleanup();
98
+ // Call anyway if page is loaded (graceful degradation)
99
+ if (document.readyState === 'complete') {
100
+ callback();
101
+ }
102
+ }
103
+ }, 10000);
104
+ },
105
+ // Execute with delay
106
+ delay: (callback, ms = 1000) => {
107
+ setTimeout(callback, ms);
108
+ },
109
+ // Retry until condition is met
110
+ retry: (condition, callback, maxAttempts = 10, interval = 500) => {
111
+ let attempts = 0;
112
+ const check = () => {
113
+ attempts++;
114
+ if (condition() || attempts >= maxAttempts) {
115
+ callback();
116
+ }
117
+ else {
118
+ setTimeout(check, interval);
119
+ }
120
+ };
121
+ check();
122
+ },
123
+ // Wait for element to exist
124
+ waitForElement: (selector, callback, timeout = 10000) => {
125
+ const element = document.querySelector(selector);
126
+ if (element) {
127
+ callback(element);
128
+ return;
129
+ }
130
+ const observer = new MutationObserver(() => {
131
+ const element = document.querySelector(selector);
132
+ if (element) {
133
+ observer.disconnect();
134
+ callback(element);
135
+ }
136
+ });
137
+ observer.observe(document.body, {
138
+ childList: true,
139
+ subtree: true,
140
+ });
141
+ // Timeout fallback
142
+ setTimeout(() => {
143
+ observer.disconnect();
144
+ }, timeout);
145
+ },
146
+ // Page type helper (current step ID)
147
+ pageType: context?.currentStepId || null,
148
+ // Funnel initialization status
149
+ isInitialized: isInitialized,
150
+ // Expose resources directly (convenience access)
151
+ ressources: context?.resources || null,
152
+ // Expose funnel context data
153
+ funnel: context
154
+ ? {
155
+ sessionId: context.sessionId,
156
+ funnelId: context.funnelId,
157
+ currentStepId: context.currentStepId,
158
+ previousStepId: context.previousStepId,
159
+ ressources: context.resources,
160
+ }
161
+ : null,
162
+ };
163
+ };
164
+ // Set up utilities before injecting script
165
+ setupTagada();
166
+ const scriptContent = context?.script;
167
+ const scriptId = 'tagada-funnel-script';
168
+ if (!scriptContent || !scriptContent.trim()) {
169
+ // Clear ref if script is removed
170
+ lastInjectedScriptRef.current = null;
171
+ // Remove existing script if it exists
172
+ const existingScript = document.getElementById(scriptId);
173
+ if (existingScript) {
174
+ existingScript.remove();
175
+ }
176
+ return;
177
+ }
178
+ // Extract script content (remove <script> tags if present)
179
+ let scriptBody = scriptContent.trim();
180
+ // Check if script is wrapped in <script> tags
181
+ const scriptTagMatch = scriptBody.match(/^<script[^>]*>([\s\S]*)<\/script>$/i);
182
+ if (scriptTagMatch) {
183
+ scriptBody = scriptTagMatch[1].trim();
184
+ }
185
+ // Skip if script body is empty after extraction
186
+ if (!scriptBody) {
187
+ return;
188
+ }
189
+ // Prevent duplicate injection of the same script content
190
+ // This handles React StrictMode double-execution in development
191
+ if (lastInjectedScriptRef.current === scriptBody) {
192
+ return;
193
+ }
194
+ // Remove existing script if it exists (for script updates)
195
+ const existingScript = document.getElementById(scriptId);
196
+ if (existingScript) {
197
+ existingScript.remove();
198
+ }
199
+ // Wrap script content with error handling and context checks
200
+ const wrappedScript = `
201
+ (function() {
202
+ try {
203
+ // Check if we have basic DOM access
204
+ if (typeof document === 'undefined') {
205
+ console.error('[TagadaPay] Document not available');
206
+ return;
207
+ }
208
+
209
+ // Check if we have Tagada
210
+ if (!window.Tagada) {
211
+ console.error('[TagadaPay] Tagada not available');
212
+ return;
213
+ }
214
+
215
+ // Execute the original script
216
+ ${scriptBody}
217
+ } catch (error) {
218
+ console.error('[TagadaPay] Script execution error:', error);
219
+ }
220
+ })();
221
+ `;
222
+ // Create and inject new script element
223
+ const scriptElement = document.createElement('script');
224
+ scriptElement.id = scriptId;
225
+ scriptElement.textContent = wrappedScript;
226
+ document.body.appendChild(scriptElement);
227
+ // Track this script content to prevent re-injection (handles React StrictMode double-execution)
228
+ lastInjectedScriptRef.current = scriptBody;
229
+ // Cleanup: remove script element but keep ref to prevent re-injection on StrictMode second run
230
+ return () => {
231
+ const scriptToRemove = document.getElementById(scriptId);
232
+ if (scriptToRemove) {
233
+ scriptToRemove.remove();
234
+ }
235
+ // Note: We intentionally DON'T clear lastInjectedScriptRef here
236
+ // This prevents React StrictMode from re-injecting the same script on the second run
237
+ // The ref will be cleared when script content actually changes (next effect run)
238
+ };
239
+ }, [context?.script, context?.currentStepId, isInitialized]);
240
+ // This component doesn't render anything
241
+ return null;
242
+ }
@@ -4,11 +4,12 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
4
4
  * TagadaProvider - Main provider component for the Tagada Pay React SDK
5
5
  */
6
6
  import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
7
- import { createContext, useCallback, useContext, useEffect, useMemo, useState, } from 'react';
7
+ import { createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react';
8
8
  import { ApiService } from '../../../react/services/apiService';
9
9
  import { convertCurrency, formatMoney, formatMoneyWithoutSymbol, formatSimpleMoney, getCurrencyInfo, minorUnitsToMajorUnits, moneyStringOrNumberToMinorUnits, } from '../../../react/utils/money';
10
10
  import { TagadaClient } from '../../core/client';
11
11
  import { default as DebugDrawer } from '../components/DebugDrawer';
12
+ import { FunnelScriptInjector } from '../components/FunnelScriptInjector';
12
13
  import { setGlobalApiClient } from '../hooks/useApiQuery';
13
14
  // Professional, subtle loading component for initialization
14
15
  const InitializationLoader = () => (_jsxs("div", { style: {
@@ -36,11 +37,11 @@ const InitializationLoader = () => (_jsxs("div", { style: {
36
37
  borderTop: '1.5px solid #9ca3af',
37
38
  borderRadius: '50%',
38
39
  animation: 'tagada-spin 1s linear infinite',
39
- } }), _jsx("span", { children: "Loading..." }), _jsx("style", { children: `
40
- @keyframes tagada-spin {
41
- 0% { transform: rotate(0deg); }
42
- 100% { transform: rotate(360deg); }
43
- }
40
+ } }), _jsx("span", { children: "Loading..." }), _jsx("style", { children: `
41
+ @keyframes tagada-spin {
42
+ 0% { transform: rotate(0deg); }
43
+ 100% { transform: rotate(360deg); }
44
+ }
44
45
  ` })] }));
45
46
  const TagadaContext = createContext(null);
46
47
  export function TagadaProvider({ children, environment, customApiConfig, debugMode, localConfig, blockUntilSessionReady = false, rawPluginConfig, features, funnelId, autoInitializeFunnel = true, onNavigate, onFunnelError, debugScripts = [], }) {
@@ -318,7 +319,7 @@ export function TagadaProvider({ children, environment, customApiConfig, debugMo
318
319
  // Loading State Logic
319
320
  const shouldShowLoading = state.isLoading || state.pluginConfigLoading || (blockUntilSessionReady && !state.isSessionInitialized);
320
321
  const canRenderChildren = !state.pluginConfigLoading && (!blockUntilSessionReady || state.isSessionInitialized);
321
- return (_jsx(QueryClientProvider, { client: queryClient, children: _jsxs(TagadaContext.Provider, { value: contextValue, children: [shouldShowLoading && _jsx(InitializationLoader, {}), state.debugMode && canRenderChildren && (_jsxs(_Fragment, { children: [_jsx("button", { onClick: () => setIsDebugDrawerOpen(true), style: {
322
+ return (_jsx(QueryClientProvider, { client: queryClient, children: _jsxs(TagadaContext.Provider, { value: contextValue, children: [_jsx(FunnelScriptInjector, { ...funnelState }), shouldShowLoading && _jsx(InitializationLoader, {}), state.debugMode && canRenderChildren && (_jsxs(_Fragment, { children: [_jsx("button", { onClick: () => setIsDebugDrawerOpen(true), style: {
322
323
  position: 'fixed',
323
324
  bottom: '16px',
324
325
  right: '16px',
package/package.json CHANGED
@@ -1,112 +1,112 @@
1
- {
2
- "name": "@tagadapay/plugin-sdk",
3
- "version": "3.0.14",
4
- "description": "Modern React SDK for building Tagada Pay plugins",
5
- "main": "dist/index.js",
6
- "types": "dist/index.d.ts",
7
- "exports": {
8
- ".": {
9
- "types": "./dist/index.d.ts",
10
- "import": "./dist/index.js",
11
- "require": "./dist/index.js"
12
- },
13
- "./react": {
14
- "types": "./dist/react/index.d.ts",
15
- "import": "./dist/react/index.js",
16
- "require": "./dist/react/index.js"
17
- },
18
- "./v2": {
19
- "types": "./dist/v2/index.d.ts",
20
- "import": "./dist/v2/index.js",
21
- "require": "./dist/v2/index.js"
22
- },
23
- "./v2/standalone": {
24
- "types": "./dist/v2/standalone/index.d.ts",
25
- "import": "./dist/v2/standalone/index.js",
26
- "require": "./dist/v2/standalone/index.js"
27
- },
28
- "./external-tracker": {
29
- "browser": "./dist/external-tracker.min.js",
30
- "default": "./dist/external-tracker.min.js"
31
- }
32
- },
33
- "scripts": {
34
- "build": "tsc && npm run build:cdn",
35
- "build:cdn": "node build-cdn.js",
36
- "build:ts": "tsc",
37
- "clean": "rm -rf dist",
38
- "lint": "echo \"No linting configured\"",
39
- "test": "jest --no-watchman",
40
- "test:watch": "jest --watch --no-watchman",
41
- "test:coverage": "jest --coverage --no-watchman",
42
- "dev": "tsc --watch",
43
- "prepublishOnly": "npm run clean && npm run build",
44
- "publish:patch": "npm version patch && npm publish",
45
- "publish:minor": "npm version minor && npm publish",
46
- "publish:major": "npm version major && npm publish",
47
- "publish:beta": "npm version prerelease --preid=beta && npm publish --tag beta",
48
- "publish:alpha": "npm version prerelease --preid=alpha && npm publish --tag alpha",
49
- "version:patch": "npm version patch",
50
- "version:minor": "npm version minor",
51
- "version:major": "npm version major",
52
- "version:beta": "npm version prerelease --preid=beta",
53
- "version:alpha": "npm version prerelease --preid=alpha",
54
- "version:check": "node version-sync.js check",
55
- "version:sync": "node version-sync.js sync",
56
- "version:list": "node version-sync.js list",
57
- "version:next": "node version-sync.js next",
58
- "postversion": "echo \"✅ Version updated to $(node -p 'require(\"./package.json\").version')\" && (git push && git push --tags || echo \"⚠️ Git push failed - you may need to pull and push manually\")"
59
- },
60
- "keywords": [
61
- "tagadapay",
62
- "cms",
63
- "plugin",
64
- "sdk",
65
- "react",
66
- "typescript"
67
- ],
68
- "author": "Tagada Pay",
69
- "license": "MIT",
70
- "dependencies": {
71
- "@basis-theory/apple-pay-js": "^2.0.2",
72
- "@basis-theory/basis-theory-js": "^4.30.0",
73
- "@basis-theory/basis-theory-react": "^1.32.5",
74
- "@basis-theory/web-threeds": "^1.0.1",
75
- "@google-pay/button-react": "^3.0.10",
76
- "@tagadapay/plugin-sdk": "link:",
77
- "@tanstack/react-query": "^5.90.2",
78
- "@ua-parser-js/pro-enterprise": "^2.0.6",
79
- "axios": "^1.10.0",
80
- "iso3166-2-db": "^2.3.11",
81
- "path-to-regexp": "^8.2.0",
82
- "react-intl": "^7.1.11",
83
- "swr": "^2.3.6"
84
- },
85
- "devDependencies": {
86
- "@types/jest": "^29.5.0",
87
- "@types/node": "^18.0.0",
88
- "@types/react": "^19",
89
- "@types/react-dom": "^19",
90
- "esbuild": "^0.24.2",
91
- "jest": "^29.5.0",
92
- "ts-jest": "^29.1.0",
93
- "typescript": "^5.0.0"
94
- },
95
- "peerDependencies": {
96
- "react": "^18.0.0 || ^19.0.0",
97
- "react-dom": "^18.0.0 || ^19.0.0"
98
- },
99
- "files": [
100
- "dist/**/*",
101
- "README.md",
102
- "build-cdn.js"
103
- ],
104
- "repository": {
105
- "type": "git",
106
- "url": "git+https://github.com/tagadapay/plugin-sdk.git"
107
- },
108
- "bugs": {
109
- "url": "https://github.com/tagadapay/plugin-sdk/issues"
110
- },
111
- "homepage": "https://github.com/tagadapay/plugin-sdk#readme"
112
- }
1
+ {
2
+ "name": "@tagadapay/plugin-sdk",
3
+ "version": "3.1.0",
4
+ "description": "Modern React SDK for building Tagada Pay plugins",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "exports": {
8
+ ".": {
9
+ "types": "./dist/index.d.ts",
10
+ "import": "./dist/index.js",
11
+ "require": "./dist/index.js"
12
+ },
13
+ "./react": {
14
+ "types": "./dist/react/index.d.ts",
15
+ "import": "./dist/react/index.js",
16
+ "require": "./dist/react/index.js"
17
+ },
18
+ "./v2": {
19
+ "types": "./dist/v2/index.d.ts",
20
+ "import": "./dist/v2/index.js",
21
+ "require": "./dist/v2/index.js"
22
+ },
23
+ "./v2/standalone": {
24
+ "types": "./dist/v2/standalone/index.d.ts",
25
+ "import": "./dist/v2/standalone/index.js",
26
+ "require": "./dist/v2/standalone/index.js"
27
+ },
28
+ "./external-tracker": {
29
+ "browser": "./dist/external-tracker.min.js",
30
+ "default": "./dist/external-tracker.min.js"
31
+ }
32
+ },
33
+ "scripts": {
34
+ "build": "tsc && npm run build:cdn",
35
+ "build:cdn": "node build-cdn.js",
36
+ "build:ts": "tsc",
37
+ "clean": "rm -rf dist",
38
+ "lint": "echo \"No linting configured\"",
39
+ "test": "jest --no-watchman",
40
+ "test:watch": "jest --watch --no-watchman",
41
+ "test:coverage": "jest --coverage --no-watchman",
42
+ "dev": "tsc --watch",
43
+ "prepublishOnly": "npm run clean && npm run build",
44
+ "publish:patch": "npm version patch && npm publish",
45
+ "publish:minor": "npm version minor && npm publish",
46
+ "publish:major": "npm version major && npm publish",
47
+ "publish:beta": "npm version prerelease --preid=beta && npm publish --tag beta",
48
+ "publish:alpha": "npm version prerelease --preid=alpha && npm publish --tag alpha",
49
+ "version:patch": "npm version patch",
50
+ "version:minor": "npm version minor",
51
+ "version:major": "npm version major",
52
+ "version:beta": "npm version prerelease --preid=beta",
53
+ "version:alpha": "npm version prerelease --preid=alpha",
54
+ "version:check": "node version-sync.js check",
55
+ "version:sync": "node version-sync.js sync",
56
+ "version:list": "node version-sync.js list",
57
+ "version:next": "node version-sync.js next",
58
+ "postversion": "echo \"✅ Version updated to $(node -p 'require(\"./package.json\").version')\" && (git push && git push --tags || echo \"⚠️ Git push failed - you may need to pull and push manually\")"
59
+ },
60
+ "keywords": [
61
+ "tagadapay",
62
+ "cms",
63
+ "plugin",
64
+ "sdk",
65
+ "react",
66
+ "typescript"
67
+ ],
68
+ "author": "Tagada Pay",
69
+ "license": "MIT",
70
+ "dependencies": {
71
+ "@basis-theory/apple-pay-js": "^2.0.2",
72
+ "@basis-theory/basis-theory-js": "^4.30.0",
73
+ "@basis-theory/basis-theory-react": "^1.32.5",
74
+ "@basis-theory/web-threeds": "^1.0.1",
75
+ "@google-pay/button-react": "^3.0.10",
76
+ "@tagadapay/plugin-sdk": "link:",
77
+ "@tanstack/react-query": "^5.90.2",
78
+ "@ua-parser-js/pro-enterprise": "^2.0.6",
79
+ "axios": "^1.10.0",
80
+ "iso3166-2-db": "^2.3.11",
81
+ "path-to-regexp": "^8.2.0",
82
+ "react-intl": "^7.1.11",
83
+ "swr": "^2.3.6"
84
+ },
85
+ "devDependencies": {
86
+ "@types/jest": "^29.5.0",
87
+ "@types/node": "^18.0.0",
88
+ "@types/react": "^19",
89
+ "@types/react-dom": "^19",
90
+ "esbuild": "^0.24.2",
91
+ "jest": "^29.5.0",
92
+ "ts-jest": "^29.1.0",
93
+ "typescript": "^5.0.0"
94
+ },
95
+ "peerDependencies": {
96
+ "react": "^18.0.0 || ^19.0.0",
97
+ "react-dom": "^18.0.0 || ^19.0.0"
98
+ },
99
+ "files": [
100
+ "dist/**/*",
101
+ "README.md",
102
+ "build-cdn.js"
103
+ ],
104
+ "repository": {
105
+ "type": "git",
106
+ "url": "git+https://github.com/tagadapay/plugin-sdk.git"
107
+ },
108
+ "bugs": {
109
+ "url": "https://github.com/tagadapay/plugin-sdk/issues"
110
+ },
111
+ "homepage": "https://github.com/tagadapay/plugin-sdk#readme"
112
+ }