solid-hcaptcha
Advanced tools
Comparing version 0.1.0 to 0.2.0
import { effect, setAttribute, template } from 'solid-js/web'; | ||
import { createSignal, onMount, onCleanup } from 'solid-js'; | ||
import { createScriptLoader } from '@solid-primitives/script-loader'; | ||
import { onMount, onCleanup } from 'solid-js'; | ||
import { createStore } from 'solid-js/store'; | ||
const generateQuery = params => { | ||
const entries = Object.entries(params); | ||
const values = entries.filter(([_key, value]) => value || value === false); | ||
const queries = values.map(([key, value]) => { | ||
if (!value) return; | ||
return `${encodeURIComponent(key)}=${encodeURIComponent(value)}`; | ||
}); | ||
const query = queries.join("&"); | ||
return query; | ||
const generateScriptUrl = (params, onLoadFunctionName, apihost) => { | ||
const domain = apihost || "https://js.hcaptcha.com"; | ||
const url = new URL(domain); | ||
url.pathname = "/1/api.js"; | ||
for (const [key, value] of Object.entries(params)) { | ||
if (!value) continue; | ||
url.searchParams.set(encodeURIComponent(key), encodeURIComponent(value)); | ||
} // Tell hCaptcha to not automatically render the widget. | ||
url.searchParams.set("render", "explicit"); | ||
url.searchParams.set("onload", onLoadFunctionName); | ||
return url.toString(); | ||
}; | ||
const _tmpl$ = /*#__PURE__*/template(`<div></div>`, 2); | ||
// Create script to init hCaptcha. | ||
const [_onLoadListeners, setOnLoadListeners] = createSignal([]); | ||
const [apiScriptRequested, setApiScriptRequested] = createSignal(false); | ||
/** Generate hCaptcha API script. */ | ||
const mountCaptchaScript = (params = {}) => { | ||
setApiScriptRequested(true); // Create global onload callback. | ||
/** The name of the function that will be triggered when hCaptcha is loaded. */ | ||
const HCAPTCHA_ONLOAD_FUNCTION_NAME = "__hCaptchaOnLoad__"; | ||
window._hcaptchaOnLoad = () => { | ||
// Iterate over onload listeners, call each listener. | ||
setOnLoadListeners(listeners => listeners.filter(listener => { | ||
listener(); | ||
return false; | ||
})); | ||
}; | ||
const domain = params.apihost || "https://js.hcaptcha.com"; | ||
delete params.apihost; | ||
const script = document.createElement("script"); | ||
script.src = `${domain}/1/api.js?render=explicit&onload=_hcaptchaOnLoad`; | ||
script.async = true; | ||
const query = generateQuery(params); | ||
script.src += query !== "" ? `&${query}` : ""; | ||
document.head.appendChild(script); | ||
}; | ||
const HCaptcha = props => { | ||
/** Reference of the div captcha element. */ | ||
let captcha_ref; | ||
const [state, setState] = createStore({ | ||
isApiReady: false, | ||
isRemoved: false, | ||
elementId: props.id || "solid-hcaptcha-script", | ||
captchaId: null | ||
}); | ||
const config = props.config || {}; | ||
const script_url = generateScriptUrl({ | ||
assethost: config.assethost, | ||
endpoint: config.endpoint, | ||
hl: config.hl, | ||
host: config.host, | ||
imghost: config.imghost, | ||
recaptchacompat: config.recaptchacompat === false ? "off" : null, | ||
reportapi: config.reportapi, | ||
sentry: config.sentry, | ||
custom: config.custom | ||
}, HCAPTCHA_ONLOAD_FUNCTION_NAME, config.apihost); | ||
/** Whether the hCaptcha API (in `window`) is ready. */ | ||
@@ -60,52 +48,18 @@ | ||
const { | ||
isApiReady, | ||
isRemoved | ||
} = state; | ||
return isApiReady && !isRemoved; | ||
return isApiReady() && !isRemoved; | ||
}; | ||
/** Once component is mounted, intialize hCaptcha. */ | ||
/** Reference of the hCaptcha widget element. */ | ||
onMount(() => { | ||
// Check if hCaptcha has already been loaded, | ||
// if not create script tag and wait to render captcha. | ||
if (!isApiReady()) { | ||
// Only create the script tag once, use a global variable to track. | ||
if (!apiScriptRequested()) { | ||
const config = props.config || {}; | ||
mountCaptchaScript({ | ||
apihost: config.apihost, | ||
assethost: config.assethost, | ||
endpoint: config.endpoint, | ||
hl: config.hl, | ||
host: config.host, | ||
imghost: config.imghost, | ||
recaptchacompat: config.recaptchacompat === false ? "off" : null, | ||
reportapi: config.reportapi, | ||
sentry: config.sentry, | ||
custom: config.custom | ||
}); | ||
} // Add onLoad callback to global onLoad listeners. | ||
setOnLoadListeners(listeners => [...listeners, handleOnLoad]); | ||
} else renderCaptcha(); | ||
let captcha_ref; | ||
const [state, setState] = createStore({ | ||
isRemoved: false, | ||
elementId: props.id, | ||
captchaId: null | ||
}); | ||
/** On unmount, reset the hCaptcha widget. */ | ||
onCleanup(() => { | ||
const { | ||
captchaId | ||
} = state; | ||
if (!isReady() || !captchaId) return; // Reset any stored variables / timers when unmounting. | ||
hcaptcha.reset(captchaId); | ||
hcaptcha.remove(captchaId); | ||
}); | ||
const renderCaptcha = onReady => { | ||
const { | ||
isApiReady | ||
} = state; | ||
if (!isApiReady || !captcha_ref) return; | ||
if (!captcha_ref) return; | ||
/** Parameters for the hCaptcha widget. */ | ||
@@ -224,6 +178,4 @@ | ||
const handleOnLoad = () => { | ||
setState({ | ||
isApiReady: true | ||
}); | ||
/** Render captcha and wait for captcha ID. */ | ||
/** Remove the function when it has been loaded. */ | ||
window[HCAPTCHA_ONLOAD_FUNCTION_NAME] = () => undefined; | ||
@@ -234,4 +186,2 @@ renderCaptcha(() => { | ||
} = props; | ||
/** Trigger `onLoad` prop if it exists. */ | ||
if (onLoad) onLoad(hcaptcha_functions); | ||
@@ -314,3 +264,37 @@ }); | ||
}; | ||
/** On mount, initialize and load the hCaptcha script. */ | ||
onMount(() => { | ||
if (!isApiReady()) { | ||
/** Create the hCaptcha main load function. */ | ||
window[HCAPTCHA_ONLOAD_FUNCTION_NAME] = () => handleOnLoad(); | ||
/** Insert the script in the `head` element. */ | ||
createScriptLoader({ | ||
src: script_url | ||
}); | ||
} else handleOnLoad(); | ||
}); | ||
/** On unmount, reset and remove the hCaptcha widget. */ | ||
onCleanup(() => { | ||
const { | ||
captchaId | ||
} = state; | ||
if (!isReady() || !captchaId) return; // Reset any stored variables / timers when unmounting. | ||
hcaptcha.reset(captchaId); | ||
hcaptcha.remove(captchaId); | ||
/** | ||
* We need to remove also the hCaptcha API on cleanup | ||
* because `script-loader` automatically removes the script | ||
* also on cleanup. | ||
* | ||
* See here: <https://github.com/solidjs-community/solid-primitives/blob/main/packages/script-loader/src/index.ts>. | ||
*/ | ||
window.hcaptcha = undefined; | ||
}); | ||
return (() => { | ||
@@ -317,0 +301,0 @@ const _el$ = _tmpl$.cloneNode(true); |
@@ -1,36 +0,20 @@ | ||
import { onMount, onCleanup, createSignal } from "solid-js"; | ||
import { createScriptLoader } from "@solid-primitives/script-loader"; | ||
import { onCleanup, onMount } from "solid-js"; | ||
import { createStore } from "solid-js/store"; | ||
import { generateQuery } from "./utils"; | ||
// Create script to init hCaptcha. | ||
const [_onLoadListeners, setOnLoadListeners] = createSignal([]); | ||
const [apiScriptRequested, setApiScriptRequested] = createSignal(false); | ||
/** Generate hCaptcha API script. */ | ||
const mountCaptchaScript = (params = {}) => { | ||
setApiScriptRequested(true); | ||
// Create global onload callback. | ||
window._hcaptchaOnLoad = () => { | ||
// Iterate over onload listeners, call each listener. | ||
setOnLoadListeners(listeners => listeners.filter(listener => { | ||
listener(); | ||
return false; | ||
})); | ||
}; | ||
const domain = params.apihost || "https://js.hcaptcha.com"; | ||
delete params.apihost; | ||
const script = document.createElement("script"); | ||
script.src = `${domain}/1/api.js?render=explicit&onload=_hcaptchaOnLoad`; | ||
script.async = true; | ||
const query = generateQuery(params); | ||
script.src += query !== "" ? `&${query}` : ""; | ||
document.head.appendChild(script); | ||
}; | ||
import { generateScriptUrl } from "./utils"; | ||
/** The name of the function that will be triggered when hCaptcha is loaded. */ | ||
const HCAPTCHA_ONLOAD_FUNCTION_NAME = "__hCaptchaOnLoad__"; | ||
const HCaptcha = (props) => { | ||
/** Reference of the div captcha element. */ | ||
let captcha_ref; | ||
const [state, setState] = createStore({ | ||
isApiReady: false, | ||
isRemoved: false, | ||
elementId: props.id || "solid-hcaptcha-script", | ||
captchaId: null | ||
}); | ||
const config = props.config || {}; | ||
const script_url = generateScriptUrl({ | ||
assethost: config.assethost, | ||
endpoint: config.endpoint, | ||
hl: config.hl, | ||
host: config.host, | ||
imghost: config.imghost, | ||
recaptchacompat: config.recaptchacompat === false ? "off" : null, | ||
reportapi: config.reportapi, | ||
sentry: config.sentry, | ||
custom: config.custom | ||
}, HCAPTCHA_ONLOAD_FUNCTION_NAME, config.apihost); | ||
/** Whether the hCaptcha API (in `window`) is ready. */ | ||
@@ -40,44 +24,14 @@ const isApiReady = () => typeof window.hcaptcha !== "undefined"; | ||
const isReady = () => { | ||
const { isApiReady, isRemoved } = state; | ||
return isApiReady && !isRemoved; | ||
const { isRemoved } = state; | ||
return isApiReady() && !isRemoved; | ||
}; | ||
/** Once component is mounted, intialize hCaptcha. */ | ||
onMount(() => { | ||
// Check if hCaptcha has already been loaded, | ||
// if not create script tag and wait to render captcha. | ||
if (!isApiReady()) { | ||
// Only create the script tag once, use a global variable to track. | ||
if (!apiScriptRequested()) { | ||
const config = props.config || {}; | ||
mountCaptchaScript({ | ||
apihost: config.apihost, | ||
assethost: config.assethost, | ||
endpoint: config.endpoint, | ||
hl: config.hl, | ||
host: config.host, | ||
imghost: config.imghost, | ||
recaptchacompat: config.recaptchacompat === false ? "off" : null, | ||
reportapi: config.reportapi, | ||
sentry: config.sentry, | ||
custom: config.custom | ||
}); | ||
} | ||
// Add onLoad callback to global onLoad listeners. | ||
setOnLoadListeners(listeners => [...listeners, handleOnLoad]); | ||
} | ||
else | ||
renderCaptcha(); | ||
/** Reference of the hCaptcha widget element. */ | ||
let captcha_ref; | ||
const [state, setState] = createStore({ | ||
isRemoved: false, | ||
elementId: props.id, | ||
captchaId: null | ||
}); | ||
/** On unmount, reset the hCaptcha widget. */ | ||
onCleanup(() => { | ||
const { captchaId } = state; | ||
if (!isReady() || !captchaId) | ||
return; | ||
// Reset any stored variables / timers when unmounting. | ||
hcaptcha.reset(captchaId); | ||
hcaptcha.remove(captchaId); | ||
}); | ||
const renderCaptcha = (onReady) => { | ||
const { isApiReady } = state; | ||
if (!isApiReady || !captcha_ref) | ||
if (!captcha_ref) | ||
return; | ||
@@ -171,7 +125,6 @@ /** Parameters for the hCaptcha widget. */ | ||
const handleOnLoad = () => { | ||
setState({ isApiReady: true }); | ||
/** Render captcha and wait for captcha ID. */ | ||
/** Remove the function when it has been loaded. */ | ||
window[HCAPTCHA_ONLOAD_FUNCTION_NAME] = () => undefined; | ||
renderCaptcha(() => { | ||
const { onLoad } = props; | ||
/** Trigger `onLoad` prop if it exists. */ | ||
if (onLoad) | ||
@@ -239,4 +192,34 @@ onLoad(hcaptcha_functions); | ||
}; | ||
/** On mount, initialize and load the hCaptcha script. */ | ||
onMount(() => { | ||
if (!isApiReady()) { | ||
/** Create the hCaptcha main load function. */ | ||
window[HCAPTCHA_ONLOAD_FUNCTION_NAME] = () => handleOnLoad(); | ||
/** Insert the script in the `head` element. */ | ||
createScriptLoader({ | ||
src: script_url | ||
}); | ||
} | ||
else | ||
handleOnLoad(); | ||
}); | ||
/** On unmount, reset and remove the hCaptcha widget. */ | ||
onCleanup(() => { | ||
const { captchaId } = state; | ||
if (!isReady() || !captchaId) | ||
return; | ||
// Reset any stored variables / timers when unmounting. | ||
hcaptcha.reset(captchaId); | ||
hcaptcha.remove(captchaId); | ||
/** | ||
* We need to remove also the hCaptcha API on cleanup | ||
* because `script-loader` automatically removes the script | ||
* also on cleanup. | ||
* | ||
* See here: <https://github.com/solidjs-community/solid-primitives/blob/main/packages/script-loader/src/index.ts>. | ||
*/ | ||
window.hcaptcha = undefined; | ||
}); | ||
return (<div ref={captcha_ref} id={state.elementId}/>); | ||
}; | ||
export default HCaptcha; |
@@ -1,11 +0,14 @@ | ||
export const generateQuery = (params) => { | ||
const entries = Object.entries(params); | ||
const values = entries.filter(([_key, value]) => value || value === false); | ||
const queries = values.map(([key, value]) => { | ||
export const generateScriptUrl = (params, onLoadFunctionName, apihost) => { | ||
const domain = apihost || "https://js.hcaptcha.com"; | ||
const url = new URL(domain); | ||
url.pathname = "/1/api.js"; | ||
for (const [key, value] of Object.entries(params)) { | ||
if (!value) | ||
return; | ||
return `${encodeURIComponent(key)}=${encodeURIComponent(value)}`; | ||
}); | ||
const query = queries.join("&"); | ||
return query; | ||
continue; | ||
url.searchParams.set(encodeURIComponent(key), encodeURIComponent(value)); | ||
} | ||
// Tell hCaptcha to not automatically render the widget. | ||
url.searchParams.set("render", "explicit"); | ||
url.searchParams.set("onload", onLoadFunctionName); | ||
return url.toString(); | ||
}; |
import type { Component } from "solid-js"; | ||
import type { HCaptchaProps } from "./types"; | ||
/** The name of the function that will be triggered when hCaptcha is loaded. */ | ||
declare const HCAPTCHA_ONLOAD_FUNCTION_NAME = "__hCaptchaOnLoad__"; | ||
declare global { | ||
interface Window { | ||
/** Function called when the hCaptcha script is loaded. */ | ||
_hcaptchaOnLoad: () => void; | ||
[HCAPTCHA_ONLOAD_FUNCTION_NAME]: () => void; | ||
} | ||
@@ -8,0 +9,0 @@ } |
/// <reference types="@hcaptcha/types" /> | ||
export interface HCaptchaState { | ||
/** Whether the captcha is ready or not. */ | ||
isApiReady: boolean; | ||
/** Whether the captcha was removed or not. */ | ||
isRemoved: boolean; | ||
/** ID of the `div` element that contains the hCaptcha widget. */ | ||
elementId: string; | ||
elementId?: string; | ||
/** Captcha identifier given by hCaptcha. */ | ||
@@ -44,3 +42,3 @@ captchaId: string | null; | ||
* | ||
* Defaults to `normal`. | ||
* @default "normal". | ||
*/ | ||
@@ -51,3 +49,3 @@ size?: "normal" | "compact" | "invisible"; | ||
* | ||
* Defaults to `light`. | ||
* @default "light". | ||
*/ | ||
@@ -59,11 +57,9 @@ theme?: "light" | "dark"; | ||
* | ||
* Defaults to 0. | ||
* @default 0 | ||
*/ | ||
tabindex?: number; | ||
/** | ||
* Manually set the ID of the hCaptcha component. | ||
* Set an ID to the hCaptcha widget. | ||
* Make sure each hCaptcha component generated on a single | ||
* page has its own unique ID when using this prop. | ||
* | ||
* Defaults to "solid-hcaptcha-script". | ||
*/ | ||
@@ -70,0 +66,0 @@ id?: string; |
@@ -1,4 +0,4 @@ | ||
export interface GenerateQueryParams { | ||
export interface HCaptchaUrlParams { | ||
[key: string]: string | boolean | number | undefined | null; | ||
} | ||
export declare const generateQuery: (params: GenerateQueryParams) => string; | ||
export declare const generateScriptUrl: (params: HCaptchaUrlParams, onLoadFunctionName: string, apihost?: string | undefined) => string; |
{ | ||
"name": "solid-hcaptcha", | ||
"description": "Unofficial port of react-hcaptcha for Solid.", | ||
"version": "0.1.0", | ||
"version": "0.2.0", | ||
"license": "MIT", | ||
@@ -46,3 +46,6 @@ "source": "./src/index.tsx", | ||
"solid-js": "^1.4.2" | ||
}, | ||
"dependencies": { | ||
"@solid-primitives/script-loader": "^1.1.0" | ||
} | ||
} |
# Solid hCaptcha Component Library | ||
> This is an unofficial port of [`react-hcaptcha`](https://github.com/hCaptcha/react-hcaptcha) for [Solid](https://www.solidjs.com). | ||
> This is an unofficial port of [`@hcaptcha/react-hcaptcha`](https://github.com/hCaptcha/react-hcaptcha) for [Solid](https://www.solidjs.com). | ||
@@ -13,7 +13,5 @@ ## Description | ||
This package is in development ! Please come back later... | ||
You can install this library via your favorite package manager. | ||
<!-- You can install this library via your favorite package manager. --> | ||
<!-- ```bash | ||
```bash | ||
# NPM | ||
@@ -27,3 +25,3 @@ npm install solid-hcaptcha --save | ||
pnpm add solid-hcaptcha | ||
``` --> | ||
``` | ||
@@ -33,10 +31,58 @@ ## Usage | ||
> You can see multiple use cases on the [example website](https://vexcited.github.io/solid-hcaptcha). | ||
<!-- ```typescript | ||
### Basic Usage | ||
```tsx | ||
import HCaptcha from "solid-hcaptcha"; | ||
const App: Component = () => { | ||
return ( | ||
<HCaptcha | ||
sitekey="10000000-ffff-ffff-ffff-000000000001" | ||
onVerify={token => console.log(token)} | ||
/> | ||
); | ||
}; | ||
``` --> | ||
export default App; | ||
``` | ||
Please, come back later, it's coming very soon... | ||
### Programmatic Usage | ||
```tsx | ||
import type { HCaptchaFunctions } from "solid-hcaptcha"; | ||
import HCaptcha from "solid-hcaptcha"; | ||
const App: Component = () => { | ||
let hcaptcha: HCaptchaFunctions | undefined; | ||
const submitCaptcha = async () => { | ||
if (!hcaptcha) return; // Check if the widget has loaded. | ||
// Execute the captcha and get the response. | ||
const response = await hcaptcha.execute(); | ||
console.log(response); | ||
} | ||
return ( | ||
<div> | ||
<HCaptcha | ||
sitekey="10000000-ffff-ffff-ffff-000000000001" | ||
onLoad={hcaptcha_instance => (hcaptcha = hcaptcha_instance)} | ||
size="invisible" | ||
/> | ||
<button onClick={submitCaptcha}> | ||
Open captcha | ||
</button> | ||
</div> | ||
); | ||
}; | ||
export default App; | ||
``` | ||
<!-- ## API --> | ||
## Development | ||
@@ -43,0 +89,0 @@ |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
99
42163
2
622
+ Added@solid-primitives/script-loader@1.1.3(transitive)