@prosopo/client-bundle-example 2.10.24 → 2.11.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 (51) hide show
  1. package/.turbo/turbo-build$colon$cjs.log +5 -4
  2. package/.turbo/turbo-build$colon$tsc.log +11 -11
  3. package/.turbo/turbo-build.log +6 -5
  4. package/CHANGELOG.md +16 -0
  5. package/dist/cjs/index.cjs +13 -41
  6. package/dist/cjs/plugins/pages.cjs +113 -0
  7. package/dist/index.js +2 -42
  8. package/dist/plugins/pages.js +103 -0
  9. package/package.json +2 -2
  10. package/src/frictionless-explicit-web3.html +10 -23
  11. package/src/frictionless-explicit.html +10 -22
  12. package/src/frictionless-implicit.html +60 -86
  13. package/src/frictionless-manual-start.html +13 -33
  14. package/src/image-explicit-web3.html +8 -24
  15. package/src/image-explicit.html +8 -23
  16. package/src/image-implicit.html +192 -0
  17. package/src/index.html +11 -26
  18. package/src/index.ts +1 -62
  19. package/src/invisible-frictionless-explicit.html +8 -17
  20. package/src/invisible-frictionless-implicit.html +8 -25
  21. package/src/invisible-image-explicit.html +8 -23
  22. package/src/invisible-image-implicit.html +8 -24
  23. package/src/invisible-pow-explicit.html +8 -23
  24. package/src/invisible-pow-implicit.html +9 -21
  25. package/src/invisible-puzzle-explicit.html +8 -23
  26. package/src/invisible-puzzle-implicit.html +9 -21
  27. package/src/plugins/code-snippet-injector.ts +179 -0
  28. package/src/plugins/form-filler-injector.ts +36 -121
  29. package/src/plugins/layout-injector.ts +238 -0
  30. package/src/plugins/logo.ts +15 -0
  31. package/src/plugins/pages.ts +219 -0
  32. package/src/plugins/placement-injector.ts +25 -64
  33. package/src/plugins/slots.ts +32 -0
  34. package/src/plugins/status-log-injector.ts +35 -114
  35. package/src/pow-explicit-web3.html +8 -24
  36. package/src/pow-explicit.html +8 -23
  37. package/src/pow-implicit-sessionid.html +10 -24
  38. package/src/pow-implicit.html +9 -24
  39. package/src/puzzle-bind-explicit.html +9 -24
  40. package/src/puzzle-explicit.html +8 -23
  41. package/src/puzzle-implicit.html +9 -24
  42. package/src/styles/demo.css +803 -0
  43. package/src/tests/plugins.unit.test.ts +285 -118
  44. package/tsconfig.tsbuildinfo +1 -1
  45. package/vite.config.ts +5 -5
  46. package/src/plugins/explanation-injector.ts +0 -294
  47. package/src/plugins/navigation-injector.ts +0 -606
  48. package/src/styles/captcha.css +0 -78
  49. package/src/styles/field.css +0 -12
  50. package/src/tests/floatLabel.unit.test.ts +0 -195
  51. package/src/tests/floatLabelReady.unit.test.ts +0 -36
@@ -1,14 +1,9 @@
1
1
  <!doctype html>
2
2
  <html lang="en">
3
3
  <head>
4
- <link href="https://cdn.muicss.com/mui-0.10.3/css/mui.min.css" rel="stylesheet" type="text/css"/>
5
- <link href="styles/field.css" rel="stylesheet" type="text/css"/>
6
4
 
7
- <script src="index.js"></script>
8
- <title>Procaptcha Invisible Puzzle Mode - Implicit Rendering</title>
9
- <link href="styles/captcha.css" rel="stylesheet" type="text/css"/>
5
+ <title>Procaptcha demo: Puzzle, invisible, implicit rendering</title>
10
6
  <script type="module">
11
- // Function to update CAPTCHA status display is now provided by status-log-injector
12
7
 
13
8
  document.addEventListener('DOMContentLoaded', function() {
14
9
  updateCaptchaStatus('Page loaded - Initializing CAPTCHA system', 'info');
@@ -67,27 +62,24 @@
67
62
  <script type="module" src="%VITE_BUNDLE_URL%" async defer></script>
68
63
  </head>
69
64
  <body>
70
- <div class="mui-container">
71
- <h1>Invisible Puzzle CAPTCHA - Implicit Rendering</h1>
72
- <p>This example demonstrates how to use Procaptcha in invisible Puzzle mode with implicit rendering directly on a button. The puzzle overlay appears when the button is clicked.</p>
65
+ <div class="demo-card">
73
66
 
74
- <!-- CAPTCHA Status Display will be injected by the status-log-injector plugin -->
75
-
76
- <form id="demo-form" action="javascript:void(0);" method="POST" class="mui-form">
77
- <h2>Example Form</h2>
78
- <div class="mui-textfield mui-textfield--float-label">
67
+ <!-- nosemgrep: python.django.security.django-no-csrf-token.django-no-csrf-token -->
68
+ <form id="demo-form" action="javascript:void(0);" method="POST" class="demo-form">
69
+ <h2>Create an account</h2>
70
+ <div class="demo-field">
79
71
  <label for="name">Name</label>
80
72
  <input type="text" id="name" name="name" required />
81
73
  </div>
82
74
 
83
- <div class="mui-textfield mui-textfield--float-label">
75
+ <div class="demo-field">
84
76
  <label for="email">Email</label>
85
77
  <input type="email" id="email" name="email" required />
86
78
  </div>
87
79
 
88
80
  <!-- Submit button with Procaptcha attributes -->
89
81
  <button
90
- class="mui-btn mui-btn--raised procaptcha"
82
+ class="demo-button procaptcha"
91
83
  data-sitekey="%PROSOPO_SITE_KEY_PUZZLE%"
92
84
  data-callback="onSubmit"
93
85
  data-size="invisible">
@@ -95,12 +87,8 @@
95
87
  </button>
96
88
  </form>
97
89
 
98
- <div id="result" class="info-box" style="display: none;"></div>
99
-
100
- <!-- Console output display area -->
101
- <div id="console-output" class="console-output" style="display: none;"></div>
90
+ <div id="result" class="demo-result" style="display: none;"></div>
102
91
 
103
- <!-- Explanation will be injected by the explanation-injector plugin -->
104
92
  </div>
105
93
  </body>
106
94
  </html>
@@ -0,0 +1,179 @@
1
+ // Copyright 2021-2026 Prosopo (UK) Ltd.
2
+ //
3
+ // Licensed under the Apache License, Version 2.0 (the "License");
4
+ // you may not use this file except in compliance with the License.
5
+ // You may obtain a copy of the License at
6
+ //
7
+ // http://www.apache.org/licenses/LICENSE-2.0
8
+ //
9
+ // Unless required by applicable law or agreed to in writing, software
10
+ // distributed under the License is distributed on an "AS IS" BASIS,
11
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ // See the License for the specific language governing permissions and
13
+ // limitations under the License.
14
+
15
+ // Shows the code a site would add to get the setup on the current demo page.
16
+ import type { IndexHtmlTransformContext, Plugin } from "vite";
17
+ import {
18
+ type DemoPage,
19
+ findPageByPath,
20
+ pagePathFromFilename,
21
+ } from "./pages.js";
22
+ import { fillSlot } from "./slots.js";
23
+
24
+ const BUNDLE_URL = "https://js.prosopo.io/js/procaptcha.bundle.js";
25
+
26
+ const implicitSnippet = `<script src="${BUNDLE_URL}" async defer></script>
27
+
28
+ <form action="/signup" method="POST">
29
+ <!-- your fields -->
30
+ <div class="procaptcha" data-sitekey="YOUR_SITE_KEY"></div>
31
+ <button type="submit">Submit</button>
32
+ </form>`;
33
+
34
+ const explicitSnippet = `<div id="procaptcha-container"></div>
35
+
36
+ <script type="module">
37
+ import { render } from "${BUNDLE_URL}";
38
+
39
+ render(document.getElementById("procaptcha-container"), {
40
+ siteKey: "YOUR_SITE_KEY",
41
+ callback: (token) => {
42
+ // send the token to your server with the form
43
+ },
44
+ });
45
+ </script>`;
46
+
47
+ const manualSnippet = `<script src="${BUNDLE_URL}" async defer></script>
48
+
49
+ <div class="procaptcha" data-sitekey="YOUR_SITE_KEY"
50
+ data-start-mode="manual"></div>
51
+
52
+ <script>
53
+ // nothing runs until this is called or the box is ticked
54
+ window.procaptcha.start();
55
+ </script>`;
56
+
57
+ const boundSnippet = `<div id="procaptcha-container"></div>
58
+ <button id="submit-button" type="submit">Submit</button>
59
+
60
+ <script type="module">
61
+ import { render } from "${BUNDLE_URL}";
62
+
63
+ render(document.getElementById("procaptcha-container"), {
64
+ siteKey: "YOUR_SITE_KEY",
65
+ bind: "#submit-button",
66
+ callback: (token) => {
67
+ // send the token to your server with the form
68
+ },
69
+ });
70
+ </script>`;
71
+
72
+ const invisibleImplicitSnippet = `<script src="${BUNDLE_URL}" async defer></script>
73
+
74
+ <button class="procaptcha" data-sitekey="YOUR_SITE_KEY"
75
+ data-size="invisible" data-callback="onSubmit">Submit</button>
76
+
77
+ <script>
78
+ function onSubmit(token) {
79
+ // send the token to your server with the form
80
+ }
81
+ </script>`;
82
+
83
+ const invisibleExplicitSnippet = `<div id="procaptcha-container"></div>
84
+
85
+ <script type="module">
86
+ import { render, execute } from "${BUNDLE_URL}";
87
+
88
+ render(document.getElementById("procaptcha-container"), {
89
+ siteKey: "YOUR_SITE_KEY",
90
+ size: "invisible",
91
+ callback: (token) => {
92
+ // send the token to your server with the form
93
+ },
94
+ });
95
+
96
+ document.querySelector("form").addEventListener("submit", (event) => {
97
+ event.preventDefault();
98
+ execute();
99
+ });
100
+ </script>`;
101
+
102
+ export const snippetFor = (page: DemoPage): string => {
103
+ if (page.mode === "invisible") {
104
+ return page.rendering === "explicit"
105
+ ? invisibleExplicitSnippet
106
+ : invisibleImplicitSnippet;
107
+ }
108
+ switch (page.rendering) {
109
+ case "explicit":
110
+ return explicitSnippet;
111
+ case "manual":
112
+ return manualSnippet;
113
+ case "bound":
114
+ return boundSnippet;
115
+ default:
116
+ return implicitSnippet;
117
+ }
118
+ };
119
+
120
+ const escapeHtml = (text: string): string =>
121
+ text
122
+ .replace(/&/g, "&amp;")
123
+ .replace(/</g, "&lt;")
124
+ .replace(/>/g, "&gt;")
125
+ .replace(/"/g, "&quot;")
126
+ .replace(/'/g, "&#39;");
127
+
128
+ const copyScript = `
129
+ <script>
130
+ document.addEventListener("click", function (event) {
131
+ var button = event.target.closest && event.target.closest("[data-demo-copy]");
132
+ if (!button) return;
133
+ var code = button.closest(".demo-code-block").querySelector("code");
134
+ navigator.clipboard.writeText(code.textContent).then(function () {
135
+ button.textContent = "Copied";
136
+ setTimeout(function () {
137
+ button.textContent = "Copy";
138
+ }, 1500);
139
+ });
140
+ });
141
+ </script>`;
142
+
143
+ // A plain button, not type="button": several specs probe
144
+ // button[type='button']:nth-of-type(2) and must not find this one.
145
+ const snippetMarkup = (page: DemoPage): string => `
146
+ <div class="demo-code-block">
147
+ <div class="demo-code-bar"><button class="demo-copy" data-demo-copy>Copy</button></div>
148
+ <pre class="demo-code"><code>${escapeHtml(snippetFor(page))}</code></pre>
149
+ </div>
150
+ <p class="demo-note">Pick the captcha type for your site key in the <a href="https://portal.prosopo.io/">portal</a>.</p>
151
+ ${copyScript}`;
152
+
153
+ export default function codeSnippetInjector(): Plugin {
154
+ return {
155
+ name: "code-snippet-injector",
156
+ transformIndexHtml: {
157
+ order: "post",
158
+ handler(html: string, ctx: IndexHtmlTransformContext): string {
159
+ if (!html.includes("<body") || !html.includes("</body>")) {
160
+ return html;
161
+ }
162
+ if (html.includes("data-demo-copy")) {
163
+ return html;
164
+ }
165
+
166
+ const page = findPageByPath(pagePathFromFilename(ctx.filename));
167
+ if (!page) {
168
+ return html;
169
+ }
170
+
171
+ const markup = snippetMarkup(page);
172
+ return (
173
+ fillSlot(html, "code", markup) ??
174
+ html.replace("</body>", () => `${markup}</body>`)
175
+ );
176
+ },
177
+ },
178
+ };
179
+ }
@@ -11,183 +11,105 @@
11
11
  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
12
  // See the License for the specific language governing permissions and
13
13
  // limitations under the License.
14
- // Vite plugin to inject a form filler button into HTML files
14
+
15
+ // Vite plugin to inject a button that fills the demo form with sample values.
15
16
  import type { IndexHtmlTransformContext, Plugin } from "vite";
17
+ import { fillSlot } from "./slots.js";
16
18
 
17
19
  export default function formFillerInjector(): Plugin {
18
- // Style for the form filler button
19
- const buttonStyle = `
20
- <style>
21
- .form-filler-button {
22
- position: fixed;
23
- bottom: 20px;
24
- right: 20px;
25
- background-color: #2196F3;
26
- color: white;
27
- border: none;
28
- border-radius: 30px;
29
- padding: 12px 20px;
30
- font-size: 14px;
31
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
32
- cursor: pointer;
33
- box-shadow: 0 4px 10px rgba(0, 0, 0, 0.2);
34
- z-index: 9999;
35
- display: flex;
36
- align-items: center;
37
- gap: 8px;
38
- transition: all 0.2s ease;
39
- outline: none;
40
- }
41
-
42
- .form-filler-button:hover {
43
- background-color: #1976D2;
44
- box-shadow: 0 6px 12px rgba(0, 0, 0, 0.25);
45
- transform: translateY(-2px);
46
- }
47
-
48
- .form-filler-button:active {
49
- transform: translateY(1px);
50
- box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
51
- }
52
-
53
- .form-filler-button svg {
54
- width: 18px;
55
- height: 18px;
56
- fill: white;
57
- }
58
-
59
- .form-filler-button span {
60
- font-weight: 500;
61
- }
62
-
63
- @media (max-width: 768px) {
64
- .form-filler-button {
65
- bottom: 15px;
66
- right: 15px;
67
- padding: 10px 15px;
68
- font-size: 12px;
69
- }
70
-
71
- .form-filler-button svg {
72
- width: 14px;
73
- height: 14px;
74
- }
75
- }
76
- </style>
77
- `;
78
-
79
- // HTML for the form filler button
20
+ // No type attribute: specs probe button[type='button']:nth-of-type(2).
80
21
  const buttonHtml = `
81
- <button id="form-filler-button" class="form-filler-button" title="Fill all form fields with default values">
82
- <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
22
+ <button id="form-filler-button" class="form-filler-button" title="Fill the form with sample values">
23
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" aria-hidden="true">
83
24
  <path d="M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM20.71 7.04c.39-.39.39-1.02 0-1.41l-2.34-2.34c-.39-.39-1.02-.39-1.41 0l-1.83 1.83 3.75 3.75 1.83-1.83z"/>
84
25
  </svg>
85
- <span>Fill Form</span>
26
+ <span>Fill form</span>
86
27
  </button>
87
28
  `;
88
29
 
89
- // JavaScript for form filling functionality
90
30
  const fillerScript = `
91
31
  <script>
92
32
  document.addEventListener('DOMContentLoaded', function() {
93
- // Create a function to fill the form with sensible default values
94
33
  function fillFormWithDefaults() {
95
- // Get all input elements
96
34
  const inputs = document.querySelectorAll('input, select, textarea');
97
-
98
- // Record which elements were filled for status feedback
99
35
  const filledElements = [];
100
-
101
- // Process each input
36
+
102
37
  inputs.forEach(input => {
103
38
  let filled = true;
104
-
105
- // Skip hidden inputs and those with existing values
39
+
106
40
  if (input.type === 'hidden' || (input.value && input.value.trim() !== '')) {
107
41
  filled = false;
108
42
  }
109
- // Handle different input types
110
43
  else if (input.type === 'text') {
111
44
  if (input.id.toLowerCase().includes('name') || input.name.toLowerCase().includes('name')) {
112
45
  input.value = 'John Doe';
113
46
  } else {
114
47
  input.value = 'Default Text';
115
48
  }
116
- }
49
+ }
117
50
  else if (input.type === 'email') {
118
51
  input.value = 'test@example.com';
119
- }
52
+ }
120
53
  else if (input.type === 'password') {
121
54
  input.value = 'Password123!';
122
- }
55
+ }
123
56
  else if (input.type === 'tel') {
124
57
  input.value = '+1234567890';
125
- }
58
+ }
126
59
  else if (input.type === 'number') {
127
60
  input.value = '42';
128
- }
61
+ }
129
62
  else if (input.type === 'url') {
130
63
  input.value = 'https://example.com';
131
- }
64
+ }
132
65
  else if (input.type === 'date') {
133
66
  const today = new Date();
134
67
  const yyyy = today.getFullYear();
135
68
  const mm = String(today.getMonth() + 1).padStart(2, '0');
136
69
  const dd = String(today.getDate()).padStart(2, '0');
137
70
  input.value = \`\${yyyy}-\${mm}-\${dd}\`;
138
- }
71
+ }
139
72
  else if (input.type === 'time') {
140
73
  input.value = '12:00';
141
- }
74
+ }
142
75
  else if (input.type === 'checkbox' || input.type === 'radio') {
143
76
  input.checked = true;
144
- }
77
+ }
145
78
  else if (input.tagName === 'SELECT') {
146
79
  if (input.options.length > 0) {
147
- input.selectedIndex = 1 >= input.options.length ? 0 : 1; // Select second option if available, first otherwise
80
+ input.selectedIndex = 1 >= input.options.length ? 0 : 1;
148
81
  }
149
- }
82
+ }
150
83
  else if (input.tagName === 'TEXTAREA') {
151
84
  input.value = 'This is a sample text for the textarea field.';
152
- }
85
+ }
153
86
  else {
154
- // For any other input types
155
87
  input.value = 'Default Value';
156
88
  }
157
-
158
- // Add to filled elements if we actually filled it
89
+
159
90
  if (filled) {
160
91
  filledElements.push(input);
161
-
162
- // Dispatch input event to trigger any listeners
163
- const event = new Event('input', { bubbles: true });
164
- input.dispatchEvent(event);
165
-
166
- // For select elements, also dispatch change event
92
+ input.dispatchEvent(new Event('input', { bubbles: true }));
167
93
  if (input.tagName === 'SELECT') {
168
- const changeEvent = new Event('change', { bubbles: true });
169
- input.dispatchEvent(changeEvent);
94
+ input.dispatchEvent(new Event('change', { bubbles: true }));
170
95
  }
171
96
  }
172
97
  });
173
-
174
- // Show a notification about what was filled
98
+
175
99
  if (filledElements.length > 0) {
176
- // If an updateCaptchaStatus function exists (from our other demos), use it
177
100
  if (typeof window.updateCaptchaStatus === 'function') {
178
101
  window.updateCaptchaStatus(\`Filled \${filledElements.length} form fields with default values\`, 'info');
179
102
  } else {
180
103
  console.log(\`Filled \${filledElements.length} form fields with default values\`);
181
104
  }
182
-
183
- // Highlight the filled fields briefly
105
+
184
106
  filledElements.forEach(el => {
185
107
  const originalBackground = el.style.backgroundColor;
186
108
  const originalTransition = el.style.transition;
187
-
109
+
188
110
  el.style.transition = 'background-color 1.5s ease';
189
- el.style.backgroundColor = '#e3f2fd';
190
-
111
+ el.style.backgroundColor = '#EDECF5';
112
+
191
113
  setTimeout(() => {
192
114
  el.style.backgroundColor = originalBackground;
193
115
  setTimeout(() => {
@@ -197,8 +119,7 @@ export default function formFillerInjector(): Plugin {
197
119
  });
198
120
  }
199
121
  }
200
-
201
- // Add the click event listener to the form filler button
122
+
202
123
  const formFillerButton = document.getElementById('form-filler-button');
203
124
  if (formFillerButton) {
204
125
  formFillerButton.addEventListener('click', fillFormWithDefaults);
@@ -210,23 +131,17 @@ export default function formFillerInjector(): Plugin {
210
131
  return {
211
132
  name: "form-filler-injector",
212
133
  transformIndexHtml: {
213
- order: "post", // Run after other HTML transforms
214
- handler(html: string, ctx: IndexHtmlTransformContext): string {
215
- // Skip if no HTML body is found
134
+ order: "post",
135
+ handler(html: string, _ctx: IndexHtmlTransformContext): string {
216
136
  if (!html.includes("<body") || !html.includes("</body>")) {
217
137
  return html;
218
138
  }
219
139
 
220
- // Inject style into the head
221
- let updatedHtml = html.replace("</head>", `${buttonStyle}</head>`);
222
-
223
- // Inject button and script before closing body tag
224
- updatedHtml = updatedHtml.replace(
225
- "</body>",
226
- `${buttonHtml}${fillerScript}</body>`,
227
- );
140
+ const withButton =
141
+ fillSlot(html, "toolbar", buttonHtml) ??
142
+ html.replace("</body>", () => `${buttonHtml}</body>`);
228
143
 
229
- return updatedHtml;
144
+ return withButton.replace("</body>", () => `${fillerScript}</body>`);
230
145
  },
231
146
  },
232
147
  };