PluginProbe
Extendify / 3.1.5
Extendify v3.1.5
3.2.1 3.2.0 3.1.6 3.1.5 3.1.4 3.1.3 3.1.2 3.1.1 3.1.0 3.0.6 3.0.5 3.0.4 trunk 0.1.0 0.10.0 0.10.1 0.10.2 0.11.0 0.11.1 0.2.0 0.3.0 0.3.1 0.4.0 0.5.0 0.6.0 All 127 releases
extendify / src / Shared / api / pluginsActivation.js

pluginsActivation.js in Extendify 3.1.5, at src/Shared/api/pluginsActivation.js

175 lines 4.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 import apiFetch from '@wordpress/api-fetch';
2 import { addQueryArgs } from '@wordpress/url';
3
4 let recaptchaReady;
5 const loadRecaptcha = () => {
6 recaptchaReady ??= new Promise((resolve, reject) => {
7 const ready = () => window.grecaptcha.enterprise.ready(resolve);
8 if (window.grecaptcha?.enterprise) {
9 ready();
10 return;
11 }
12
13 const existing = document.querySelector(
14 'script[src*="recaptcha/enterprise"]',
15 );
16 if (existing) {
17 existing.addEventListener('load', ready);
18 return;
19 }
20
21 const script = document.createElement('script');
22 script.src =
23 'https://www.google.com/recaptcha/enterprise.js?render=explicit';
24 script.async = true;
25 script.onload = ready;
26 script.onerror = () => {
27 // A cached rejection would block every retry.
28 recaptchaReady = undefined;
29 reject(new Error('Failed to load the reCAPTCHA script'));
30 };
31 document.head.appendChild(script);
32 });
33 return recaptchaReady;
34 };
35
36 // enterprise.js can't load twice, and execute() needs a rendered site key —
37 // one widget per key.
38 const recaptchaWidgets = new Map();
39 const getRecaptchaToken = async (action, siteKey, timings = {}) => {
40 if (!siteKey) {
41 throw new Error(`No reCAPTCHA site key for the ${action} action`);
42 }
43
44 const start = Date.now();
45
46 try {
47 await loadRecaptcha();
48
49 if (!recaptchaWidgets.has(siteKey)) {
50 const container = document.createElement('div');
51 document.body.appendChild(container);
52 recaptchaWidgets.set(
53 siteKey,
54 window.grecaptcha.enterprise.render(container, {
55 sitekey: siteKey,
56 size: 'invisible',
57 }),
58 );
59 }
60
61 // Without await, finally runs before execute settles and records ~0ms.
62 return await window.grecaptcha.enterprise.execute(
63 recaptchaWidgets.get(siteKey),
64 { action },
65 );
66 } finally {
67 timings.captchaTimeInMs = Date.now() - start;
68 }
69 };
70
71 const createAccount = async ({
72 slug,
73 email,
74 marketingConsent,
75 termsAgreed,
76 signal,
77 scriptData,
78 }) => {
79 await apiFetch({
80 path: `extendify/v1/${slug}/create-account`,
81 method: 'POST',
82 data: {
83 email,
84 marketingConsent,
85 termsAgreed,
86 ...scriptData,
87 },
88 signal,
89 });
90 };
91
92 /*
93 * Plugin entries shape:
94 * createAccountCallback: (data) => Promise<void> — performs the account creation request
95 * idempotent: boolean (default true) — false skips retries; an aborted fetch does not stop the PHP call, so a retry creates a second account
96 * data.timings: out-param — write captchaTimeInMs here; it survives a throw
97 */
98 export const pluginsActivation = {
99 simplybook: {
100 idempotent: false,
101 createAccountCallback: async ({
102 scriptData,
103 email,
104 marketingConsent,
105 termsAgreed,
106 signal,
107 timings,
108 }) => {
109 const captchaToken = await getRecaptchaToken(
110 scriptData?.recaptchaAction,
111 scriptData?.recaptchaSiteKey,
112 timings,
113 );
114
115 // Hit the endpoint via ?rest_route= so the request URL contains "simplybook" —
116 // SimplyBook only registers its onboarding routes when it does, else they 404.
117 const url = addQueryArgs(`${window.extSharedData.homeUrl}/`, {
118 rest_route: '/extendify/v1/simplybook/create-account',
119 });
120
121 await apiFetch({
122 url,
123 method: 'POST',
124 data: {
125 email,
126 marketingConsent,
127 termsAgreed,
128 captcha_token: captchaToken,
129 },
130 signal,
131 });
132 },
133 },
134 'translatepress-multilingual': {
135 idempotent: false,
136 createAccountCallback: (data) =>
137 createAccount({ slug: 'translatepress-multilingual', ...data }),
138 },
139 imagify: {
140 idempotent: false,
141 createAccountCallback: (data) =>
142 createAccount({ slug: 'imagify', ...data }),
143 },
144 metricool: {
145 idempotent: false,
146 createAccountCallback: async ({
147 scriptData,
148 email,
149 marketingConsent,
150 termsAgreed,
151 signal,
152 timings,
153 }) => {
154 const captchaToken = await getRecaptchaToken(
155 scriptData?.recaptchaAction,
156 scriptData?.recaptchaSiteKey,
157 timings,
158 );
159
160 // The "/v1" segment is what makes Metricool register its logout route.
161 await apiFetch({
162 path: 'extendify/v1/metricool/v1/create-account',
163 method: 'POST',
164 data: {
165 email,
166 marketingConsent,
167 termsAgreed,
168 captcha_token: captchaToken,
169 },
170 signal,
171 });
172 },
173 },
174 };
175