Skip to content

Commit

Permalink
Procaptcha-bundle: renderLogic improvements and introducing "for-devs…
Browse files Browse the repository at this point in the history
….md" (#1563)
  • Loading branch information
light-source authored Dec 10, 2024
1 parent 2fdbbf4 commit daad137
Show file tree
Hide file tree
Showing 11 changed files with 454 additions and 95 deletions.
78 changes: 78 additions & 0 deletions for-devs.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
## Developer Information

This file is intended for developers. It provides details on the project's structure and instructions on how to work
with it.

### 1. Commands

* Installation: `npm install`
* Building packages: `npm run build:all`
* Building the bundle: `npm run build:bundle`
* Linting: `npm run lint-fix` (formatting & code validation)

### 2. Environment Setup for Tests (Once)

To set up the environment for testing, run the following commands:

```
cp demos/client-example-server/env.development demos/client-example-server/.env.test
cp demos/client-example/env.development demos/client-example/.env.test
cp demos/client-bundle-example/env.development demos/client-bundle-example/.env.test
cp dev/scripts/env.test .env.test
cp dev/scripts/env.test dev/scripts/.env.test
cp dev/scripts/env.test packages/cli/.env.test
cp dev/scripts/env.test packages/procaptcha-bundle/.env.test
```

### 3. Running E2E Client Tests Locally

#### 3.1) Launching services

The DB is docked, and to start the DB service, run the following:

```
docker compose --file ./docker/docker-compose.test.yml up -d --remove-orphans --force-recreate --always-recreate-deps
NODE_ENV="test" npm run setup
```

> Note: the second command should be called once per the container lifetime, and adds the initial data, like domains,
> siteKeys, etc.
Then start the services:

```
npm run -w @prosopo/client-example-server build && NODE_ENV=test npm run start:server
NODE_ENV=test npm run start:demo
NODE_ENV=test npm run start:provider:admin
```

#### 3.2) Running the Tests

```
NODE_ENV=test npm run -w @prosopo/cypress-shared cypress:open:client-example
```

#### 3.3) Stopping Docker Services

After the tests finish, stop the Docker services with:

```
docker compose --file ./docker/docker-compose.test.yml down
```

### 4. Running E2E Bundle Tests Locally

#### 4.1) Launching Services

For bundle tests, use the same services as for the E2E client tests, plus the following:

```
NODE_ENV="development" npm -w @prosopo/procaptcha-bundle run bundle
NODE_ENV=test npm run start:bundle
```

#### 4.2) Running the Tests

```
NODE_ENV=test npm -w @prosopo/cypress-shared run cypress:open:client-bundle-example
```
24 changes: 13 additions & 11 deletions packages/procaptcha-bundle/src/util/defaultCallbacks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,18 @@ export const getWindowCallback = (callbackName: string) => {
return fn;
};

export const getDefaultCallbacks = (element: Element) => ({
export interface Callbacks {
onHuman: (token: ProcaptchaToken) => void;
onChallengeExpired: () => void;
onExpired: () => void;
onError: (error: Error) => void;
onClose: () => void;
onOpen: () => void;
onFailed: () => void;
onReset: () => void;
}

export const getDefaultCallbacks = (element: Element): Callbacks => ({
onHuman: (token: ProcaptchaToken) => handleOnHuman(element, token),
onChallengeExpired: () => {
removeProcaptchaResponse();
Expand Down Expand Up @@ -60,16 +71,7 @@ export const getDefaultCallbacks = (element: Element) => ({

export function setUserCallbacks(
renderOptions: ProcaptchaRenderOptions | undefined,
callbacks: {
onHuman: (token: ProcaptchaToken) => void;
onChallengeExpired: () => void;
onExpired: () => void;
onError: (error: Error) => void;
onClose: () => void;
onOpen: () => void;
onFailed: () => void;
onReset: () => void;
},
callbacks: Callbacks,
element: Element,
) {
if (typeof renderOptions?.callback === "function") {
Expand Down
106 changes: 22 additions & 84 deletions packages/procaptcha-bundle/src/util/renderLogic.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import createCache from "@emotion/cache";
import { CacheProvider } from "@emotion/react";
// Copyright 2021-2024 Prosopo (UK) Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
Expand All @@ -13,94 +11,34 @@ import { CacheProvider } from "@emotion/react";
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import { ProcaptchaFrictionless } from "@prosopo/procaptcha-frictionless";
import { ProcaptchaPow } from "@prosopo/procaptcha-pow";
import { Procaptcha } from "@prosopo/procaptcha-react";
import type {
ProcaptchaClientConfigOutput,
ProcaptchaRenderOptions,
import {
FeaturesEnum,
type ProcaptchaClientConfigOutput,
type ProcaptchaRenderOptions,
} from "@prosopo/types";
import { type Root, createRoot } from "react-dom/client";
import { getDefaultCallbacks, setUserCallbacks } from "./defaultCallbacks.js";
import { setLanguage } from "./language.js";
import { setTheme } from "./theme.js";
import { setValidChallengeLength } from "./timeout.js";
import { CaptchaRenderer } from "./renderLogic/captcha/captchaRenderer.js";
import { WebComponent } from "./renderLogic/webComponent.js";
import { WidgetRenderer } from "./renderLogic/widgetRenderer.js";

const identifierPrefix = "procaptcha-";

function makeShadowRoot(
element: Element,
renderOptions?: ProcaptchaRenderOptions,
): ShadowRoot {
// todo maybe introduce customCSS in renderOptions.
const customCss = "";

const wrapperElement = document.createElement("prosopo-procaptcha");

const wrapperShadow = wrapperElement.attachShadow({ mode: "open" });
wrapperShadow.innerHTML +=
'<style>:host{all:initial!important;}:host *{font-family: ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";}</style>';
wrapperShadow.innerHTML +=
"" !== customCss ? `<style>${customCss}</style>` : "";

element.appendChild(wrapperElement);

return wrapperShadow;
}
const widgetRenderer = new WidgetRenderer(
new WebComponent(),
new CaptchaRenderer(),
);

export const renderLogic = (
elements: Element[],
config: ProcaptchaClientConfigOutput,
renderOptions?: ProcaptchaRenderOptions,
) => {
const roots: Root[] = [];

for (const element of elements) {
const callbacks = getDefaultCallbacks(element);
const shadowRoot = makeShadowRoot(element, renderOptions);

setUserCallbacks(renderOptions, callbacks, element);
setTheme(renderOptions, element, config);
setValidChallengeLength(renderOptions, element, config);
setLanguage(renderOptions, element, config);

const emotionCache = createCache({
key: "procaptcha",
prepend: true,
container: shadowRoot,
});

let root: Root | null = null;
switch (renderOptions?.captchaType) {
case "pow":
console.log("rendering pow");
root = createRoot(shadowRoot, { identifierPrefix });
root.render(
<CacheProvider value={emotionCache}>
<ProcaptchaPow config={config} callbacks={callbacks} />
</CacheProvider>,
);
break;
case "image":
console.log("rendering image");
root = createRoot(shadowRoot, { identifierPrefix });
root.render(
<CacheProvider value={emotionCache}>
<Procaptcha config={config} callbacks={callbacks} />
</CacheProvider>,
);
break;
default:
console.log("rendering frictionless");
root = createRoot(shadowRoot, { identifierPrefix });
root.render(
<CacheProvider value={emotionCache}>
<ProcaptchaFrictionless config={config} callbacks={callbacks} />
</CacheProvider>,
);
break;
}
roots.push(root);
}
return roots;
return widgetRenderer.renderElements(
{
identifierPrefix: "procaptcha-",
emotionCacheKey: "procaptcha",
webComponentTag: "prosopo-procaptcha",
defaultCaptchaType: FeaturesEnum.Frictionless,
},
elements,
config,
renderOptions,
);
};
25 changes: 25 additions & 0 deletions packages/procaptcha-bundle/src/util/renderLogic/captcha/captcha.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// Copyright 2021-2024 Prosopo (UK) Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import type { ProcaptchaClientConfigOutput } from "@prosopo/types";
import React from "react";
import type { Callbacks } from "../../defaultCallbacks.js";

interface CaptchaProps {
config: ProcaptchaClientConfigOutput;
callbacks: Callbacks;
}

abstract class CaptchaElement extends React.Component<CaptchaProps> {}

export { type CaptchaProps, CaptchaElement };
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// Copyright 2021-2024 Prosopo (UK) Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import type { FeaturesEnum } from "@prosopo/types";
import type { ReactNode } from "react";
import type { CaptchaProps } from "./captcha.js";
import { componentsList } from "./componentsList.js";

class CaptchaRenderer {
public render(
captchaType: FeaturesEnum,
captchaProps: CaptchaProps,
): ReactNode {
const CaptchaComponent = componentsList[captchaType];

console.log(`rendering ${captchaType}`);

return (
<CaptchaComponent
config={captchaProps.config}
callbacks={captchaProps.callbacks}
/>
);
}
}

export { CaptchaRenderer };
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// Copyright 2021-2024 Prosopo (UK) Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import { ProcaptchaFrictionless } from "@prosopo/procaptcha-frictionless";
import React from "react";
import { CaptchaElement } from "../captcha.js";

class FrictionlessCaptcha extends CaptchaElement {
public override render() {
const { config, callbacks } = this.props;

return <ProcaptchaFrictionless config={config} callbacks={callbacks} />;
}
}

export { FrictionlessCaptcha };
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// Copyright 2021-2024 Prosopo (UK) Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import { Procaptcha } from "@prosopo/procaptcha-react";
import React from "react";
import { CaptchaElement } from "../captcha.js";

class ImageCaptcha extends CaptchaElement {
public override render() {
const { config, callbacks } = this.props;

return <Procaptcha config={config} callbacks={callbacks} />;
}
}

export { ImageCaptcha };
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// Copyright 2021-2024 Prosopo (UK) Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import { ProcaptchaPow } from "@prosopo/procaptcha-pow";
import React from "react";
import { CaptchaElement } from "../captcha.js";

class PowCaptcha extends CaptchaElement {
public override render() {
const { config, callbacks } = this.props;

return <ProcaptchaPow config={config} callbacks={callbacks} />;
}
}

export { PowCaptcha };
Loading

0 comments on commit daad137

Please sign in to comment.