zh-web-sdk 1.0.2
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.
- package/.eslintrc.js +14 -0
- package/.github/workflows/pr.yaml +13 -0
- package/.github/workflows/release.yaml +57 -0
- package/.github/workflows/tag.yaml +55 -0
- package/README.md +36 -0
- package/dist/constants.d.ts +2 -0
- package/dist/index.d.ts +37 -0
- package/dist/index.js +102 -0
- package/dist/index.js.map +7 -0
- package/dist/onboarding/OnboardingApp.d.ts +13 -0
- package/dist/onboarding/constants.d.ts +1 -0
- package/dist/redux/actions/index.d.ts +8 -0
- package/dist/redux/reducers/app.d.ts +13 -0
- package/dist/redux/reducers/constants.d.ts +3 -0
- package/dist/redux/reducers/index.d.ts +4 -0
- package/dist/redux/store/index.d.ts +4 -0
- package/dist/styles.d.ts +15 -0
- package/dist/types.d.ts +101 -0
- package/dist/utils/strings.d.ts +6 -0
- package/dist/utils.d.ts +8 -0
- package/package.json +46 -0
- package/scripts/build.js +34 -0
- package/scripts/zip.js +49 -0
- package/src/constants.ts +2 -0
- package/src/index.tsx +197 -0
- package/src/onboarding/OnboardingApp.tsx +128 -0
- package/src/onboarding/constants.ts +2 -0
- package/src/redux/actions/index.ts +12 -0
- package/src/redux/reducers/app.ts +61 -0
- package/src/redux/reducers/constants.ts +3 -0
- package/src/redux/reducers/index.ts +8 -0
- package/src/redux/store/index.ts +6 -0
- package/src/styles.ts +85 -0
- package/src/types.ts +110 -0
- package/src/utils/strings.ts +19 -0
- package/src/utils.ts +13 -0
- package/tsconfig.json +26 -0
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
interface AppProps {
|
|
3
|
+
isOnboardingAppActive: boolean;
|
|
4
|
+
isOnboardingAppLoaded: boolean;
|
|
5
|
+
userOnboardingJWT: string;
|
|
6
|
+
zeroHashOnboardingURL: string;
|
|
7
|
+
}
|
|
8
|
+
declare const _default: import("react-redux").ConnectedComponent<({ isOnboardingAppActive, isOnboardingAppLoaded, userOnboardingJWT, zeroHashOnboardingURL }: AppProps) => import("react/jsx-runtime").JSX.Element, {
|
|
9
|
+
zeroHashOnboardingURL: string;
|
|
10
|
+
context?: React.Context<import("react-redux").ReactReduxContextValue<any, import("redux").AnyAction>> | undefined;
|
|
11
|
+
store?: import("redux").Store<any, import("redux").AnyAction> | undefined;
|
|
12
|
+
}>;
|
|
13
|
+
export default _default;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const MESSAGE_TYPE_SEND_JWT_TOKEN: string;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export interface IState {
|
|
2
|
+
isOnboardingAppActive: boolean;
|
|
3
|
+
isOnboardingAppLoaded: boolean;
|
|
4
|
+
userOnboardingJWT: string;
|
|
5
|
+
}
|
|
6
|
+
export interface IAction {
|
|
7
|
+
type: string;
|
|
8
|
+
isOnboardingAppActive?: boolean;
|
|
9
|
+
isOnboardingAppLoaded?: boolean;
|
|
10
|
+
userOnboardingJWT?: string;
|
|
11
|
+
}
|
|
12
|
+
declare const appReducer: (state: IState | undefined, action: IAction) => IState;
|
|
13
|
+
export default appReducer;
|
package/dist/styles.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { CSSProperties } from "react";
|
|
2
|
+
export declare const screenSizes: {
|
|
3
|
+
id: string;
|
|
4
|
+
size: number;
|
|
5
|
+
}[];
|
|
6
|
+
export declare const minWidthMatcher: () => string;
|
|
7
|
+
export declare const containerStyle: CSSProperties;
|
|
8
|
+
export declare const containerMediaStyles: {
|
|
9
|
+
[id: string]: CSSProperties;
|
|
10
|
+
};
|
|
11
|
+
export declare const appWrapperStyle: CSSProperties;
|
|
12
|
+
export declare const appWrapperHiddenStyle: CSSProperties;
|
|
13
|
+
export declare const modalStyle: CSSProperties;
|
|
14
|
+
export declare const iframeWrapperStyle: CSSProperties;
|
|
15
|
+
export declare const iframeStyle: CSSProperties;
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
declare global {
|
|
2
|
+
interface Window {
|
|
3
|
+
zerohash: any;
|
|
4
|
+
}
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* IInitializeParameters describes the parameters
|
|
8
|
+
* required for initializing the platform SDK
|
|
9
|
+
*/
|
|
10
|
+
export interface IInitializeParameters {
|
|
11
|
+
/**
|
|
12
|
+
* zeroHashOnboardingURL should be set to the URL of
|
|
13
|
+
* the webapp for onboarding users onto the ZeroHash
|
|
14
|
+
* platform.
|
|
15
|
+
*/
|
|
16
|
+
zeroHashOnboardingURL: string;
|
|
17
|
+
/**
|
|
18
|
+
* rootQuerySelector is a query selector string that
|
|
19
|
+
* allows creating the ZeroHash UI subtree under a
|
|
20
|
+
* custom element.
|
|
21
|
+
*
|
|
22
|
+
* The default #zh-root query selector will be used
|
|
23
|
+
* instead otherwise. #zh-root will be a <div> appended
|
|
24
|
+
* as a child of the body in no specified order.
|
|
25
|
+
*/
|
|
26
|
+
rootQuerySelector?: string;
|
|
27
|
+
/**
|
|
28
|
+
* userOnboardingJWT is the JWT that you received from
|
|
29
|
+
* the ZeroHash HTTP API that pertains to the customer
|
|
30
|
+
* to be onboarded.
|
|
31
|
+
*
|
|
32
|
+
* This is optional in the constructor and can be
|
|
33
|
+
* deferred to setUserOnboardingJWT() at a later time
|
|
34
|
+
* but must be provided before opening the onboarding
|
|
35
|
+
* UI in order for the onboarding UI to be loaded.
|
|
36
|
+
*/
|
|
37
|
+
userOnboardingJWT?: string;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* ISetUserOnboardingJWTParameters describes the
|
|
41
|
+
* parameters required for setting the user onboarding
|
|
42
|
+
* JWT.
|
|
43
|
+
*/
|
|
44
|
+
export interface ISetUserOnboardingJWTParameters {
|
|
45
|
+
/**
|
|
46
|
+
* userOnboardingJWT is the JWT that you received from
|
|
47
|
+
* the ZeroHash HTTP API that pertains to the customer
|
|
48
|
+
* to be onboarded.
|
|
49
|
+
*/
|
|
50
|
+
userOnboardingJWT: string;
|
|
51
|
+
}
|
|
52
|
+
export interface IOpenOnboardingModalParameters {
|
|
53
|
+
/**
|
|
54
|
+
* userOnboardingJWT is the JWT that you received from
|
|
55
|
+
* the ZeroHash HTTP API that pertains to the customer
|
|
56
|
+
* to be onboarded.
|
|
57
|
+
*/
|
|
58
|
+
userOnboardingJWT?: string;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* IncomingMessageType are enums of message types that
|
|
62
|
+
* are registered that the host can handle.
|
|
63
|
+
*/
|
|
64
|
+
export declare enum IncomingMessageType {
|
|
65
|
+
/**
|
|
66
|
+
* OnboardingAppLoaded is received when the onboarding
|
|
67
|
+
* app has initialized.
|
|
68
|
+
*/
|
|
69
|
+
OnboardingAppLoaded = "ONBOARDING_APP_LOADED",
|
|
70
|
+
/**
|
|
71
|
+
* OnboardingCloseButtonClicked is received when the close button
|
|
72
|
+
* has been clicked in the onboarding app
|
|
73
|
+
*/
|
|
74
|
+
OnboardingCloseButtonClicked = "ONBOARDING_CLOSE_BUTTON_CLICKED"
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* IncomingMessage defines the structure of an incoming
|
|
78
|
+
* message
|
|
79
|
+
*/
|
|
80
|
+
export interface IncomingMessage {
|
|
81
|
+
/**
|
|
82
|
+
* type is the type of message that is used for routing
|
|
83
|
+
* the request to the appropriate handler
|
|
84
|
+
*/
|
|
85
|
+
type: IncomingMessageType;
|
|
86
|
+
/**
|
|
87
|
+
* payload is any freeform value that pertains to the
|
|
88
|
+
* request
|
|
89
|
+
*/
|
|
90
|
+
payload: unknown;
|
|
91
|
+
}
|
|
92
|
+
export type IncomingMessageHandler = (payload: unknown) => void;
|
|
93
|
+
/**
|
|
94
|
+
* IZeroHashSDK is the interface that you may use to interact with the ZeroHash SDK.
|
|
95
|
+
*/
|
|
96
|
+
export interface IZeroHashSDK {
|
|
97
|
+
setUserOnboardingJWT(params: ISetUserOnboardingJWTParameters): void;
|
|
98
|
+
isOnboardingModalOpen(): boolean;
|
|
99
|
+
openOnboardingModal(params: IOpenOnboardingModalParameters): void;
|
|
100
|
+
closeOnboardingModal(): void;
|
|
101
|
+
}
|
package/dist/utils.d.ts
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "zh-web-sdk",
|
|
3
|
+
"version": "1.0.2",
|
|
4
|
+
"private": false,
|
|
5
|
+
"description": "ZeroHash Web SDK",
|
|
6
|
+
"homepage": "https://github.com/seedcx/zh-web-sdk",
|
|
7
|
+
"main": "dist/index.js",
|
|
8
|
+
"module": "dist/index.js",
|
|
9
|
+
"types": "dist/index.d.ts",
|
|
10
|
+
"keywords": [],
|
|
11
|
+
"author": "support@zerohash.com",
|
|
12
|
+
"license": "MIT",
|
|
13
|
+
"scripts": {
|
|
14
|
+
"prepublish": "npm run build",
|
|
15
|
+
"ts-types": "tsc --emitDeclarationOnly --outDir dist",
|
|
16
|
+
"build": "rimraf dist && node ./scripts/build.js && npm run ts-types",
|
|
17
|
+
"build:zip": "npm run build && npm run zip",
|
|
18
|
+
"zip": "rimraf build/* && node scripts/zip.js",
|
|
19
|
+
"lint": "eslint src --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
|
|
20
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
21
|
+
},
|
|
22
|
+
"devDependencies": {
|
|
23
|
+
"@typescript-eslint/eslint-plugin": "^6.4.0",
|
|
24
|
+
"archiver": "^5.3.1",
|
|
25
|
+
"esbuild": "0.18.3",
|
|
26
|
+
"eslint": "^8.47.0",
|
|
27
|
+
"eslint-config-prettier": "6.15.0",
|
|
28
|
+
"eslint-config-standard": "^17.1.0",
|
|
29
|
+
"eslint-plugin-import": "^2.28.1",
|
|
30
|
+
"eslint-plugin-n": "^16.0.1",
|
|
31
|
+
"eslint-plugin-prettier": "3.4.1",
|
|
32
|
+
"eslint-plugin-promise": "^6.1.1",
|
|
33
|
+
"eslint-plugin-react-hooks": "^4.6.0",
|
|
34
|
+
"eslint-plugin-react-refresh": "^0.3.4",
|
|
35
|
+
"rimraf": "^5.0.1",
|
|
36
|
+
"typescript": "^4.9.5"
|
|
37
|
+
},
|
|
38
|
+
"dependencies": {
|
|
39
|
+
"@types/react": "^18.2.12",
|
|
40
|
+
"@types/react-dom": "^18.2.5",
|
|
41
|
+
"react": "^18.2.0",
|
|
42
|
+
"react-dom": "^18.2.0",
|
|
43
|
+
"react-redux": "^8.1.1",
|
|
44
|
+
"redux": "^4.2.1"
|
|
45
|
+
}
|
|
46
|
+
}
|
package/scripts/build.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
const esbuild = require("esbuild");
|
|
2
|
+
const {name, version} = require("../package.json");
|
|
3
|
+
|
|
4
|
+
const build = async () => {
|
|
5
|
+
console.log(`Building ${name}@${version}`);
|
|
6
|
+
|
|
7
|
+
const outputDir = "./dist/";
|
|
8
|
+
const minFileName = `${outputDir}zerohash_sdk.min.js`;
|
|
9
|
+
const bundleFileName = `${outputDir}zerohash_sdk.js`;
|
|
10
|
+
|
|
11
|
+
// Build minified dist
|
|
12
|
+
await esbuild
|
|
13
|
+
.build({
|
|
14
|
+
entryPoints: ['src/index.tsx'],
|
|
15
|
+
outdir: 'dist',
|
|
16
|
+
bundle: true,
|
|
17
|
+
sourcemap: true,
|
|
18
|
+
minify: true,
|
|
19
|
+
splitting: false,
|
|
20
|
+
format: 'esm',
|
|
21
|
+
target: ['esnext']
|
|
22
|
+
})
|
|
23
|
+
.catch(err => {
|
|
24
|
+
console.error('Error: build failed - unable to build minified dist');
|
|
25
|
+
throw err;
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
build()
|
|
30
|
+
.then(() => console.log("successfully built"))
|
|
31
|
+
.catch(err => {
|
|
32
|
+
console.error(err);
|
|
33
|
+
process.exit(1);
|
|
34
|
+
});
|
package/scripts/zip.js
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
const fs = require("fs");
|
|
2
|
+
const archiver = require("archiver");
|
|
3
|
+
const {name, version} = require("../package.json");
|
|
4
|
+
|
|
5
|
+
const zip = async () => {
|
|
6
|
+
const zipFile = `./build/zh_web_sdk_v${version}.zip`;
|
|
7
|
+
console.log(`Zipping to ${zipFile}`);
|
|
8
|
+
|
|
9
|
+
// create the directory forcibly
|
|
10
|
+
fs.mkdirSync("./build", {recursive: true});
|
|
11
|
+
|
|
12
|
+
// zip output as a package
|
|
13
|
+
const output = fs.createWriteStream(zipFile);
|
|
14
|
+
const archive = archiver('zip', {
|
|
15
|
+
zlib: { level: 9 }
|
|
16
|
+
});
|
|
17
|
+
output.on('close', () => {
|
|
18
|
+
console.log(archive.pointer() + ' total bytes');
|
|
19
|
+
console.log('archiver has been finalized and the output file descriptor has closed.');
|
|
20
|
+
});
|
|
21
|
+
output.on('end', () => {
|
|
22
|
+
console.log('Data has been drained');
|
|
23
|
+
});
|
|
24
|
+
archive.on('warning', (err) => {
|
|
25
|
+
if (err.code === "ENOENT") {
|
|
26
|
+
console.warn("ENOENT: ", err);
|
|
27
|
+
} else {
|
|
28
|
+
console.error(err);
|
|
29
|
+
throw err;
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
archive.on('error', (err) => {
|
|
33
|
+
console.error(err);
|
|
34
|
+
throw err;
|
|
35
|
+
});
|
|
36
|
+
archive.pipe(output);
|
|
37
|
+
archive.file("README.md");
|
|
38
|
+
archive.file("package.json");
|
|
39
|
+
archive.file("package-lock.json");
|
|
40
|
+
archive.directory("./dist/");
|
|
41
|
+
await archive.finalize();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
zip()
|
|
45
|
+
.then(() => console.log("successfully zipped"))
|
|
46
|
+
.catch(err => {
|
|
47
|
+
console.error(err);
|
|
48
|
+
process.exit(1);
|
|
49
|
+
});
|
package/src/constants.ts
ADDED
package/src/index.tsx
ADDED
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import ReactDOM from "react-dom/client";
|
|
3
|
+
import {
|
|
4
|
+
IInitializeParameters,
|
|
5
|
+
IncomingMessage,
|
|
6
|
+
IncomingMessageHandler,
|
|
7
|
+
IncomingMessageType, IOpenOnboardingModalParameters,
|
|
8
|
+
ISetUserOnboardingJWTParameters, IZeroHashSDK
|
|
9
|
+
} from './types';
|
|
10
|
+
import {generateRootID} from "./utils";
|
|
11
|
+
import {DEFAULT_ZH_ONBOARDING_URL} from "./constants";
|
|
12
|
+
import OnboardingApp from "./onboarding/OnboardingApp";
|
|
13
|
+
import {Provider} from "react-redux";
|
|
14
|
+
import store from "./redux/store";
|
|
15
|
+
import {
|
|
16
|
+
ACTION_ONBOARDING_APP_LOADED,
|
|
17
|
+
ACTION_SET_USER_ONBOARDING_JWT
|
|
18
|
+
} from "./redux/reducers/constants";
|
|
19
|
+
import {closeOnboardingModal, openOnboardingModal} from "./redux/actions";
|
|
20
|
+
|
|
21
|
+
let _zeroHashOnboardingURL: string = DEFAULT_ZH_ONBOARDING_URL;
|
|
22
|
+
let _zeroHashOnboardingOrigin: string = "";
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* onboardingMessageHandlers are the message handlers that
|
|
26
|
+
* handle incoming messages from the onboarding app iframe.
|
|
27
|
+
* @private - not meant to be invoked publicly
|
|
28
|
+
*/
|
|
29
|
+
const onboardingMessageHandlers: { [messageType in IncomingMessageType]: IncomingMessageHandler } = {
|
|
30
|
+
[IncomingMessageType.OnboardingAppLoaded]: () => {
|
|
31
|
+
store.dispatch({
|
|
32
|
+
type: ACTION_ONBOARDING_APP_LOADED,
|
|
33
|
+
isOnboardingAppLoaded: true,
|
|
34
|
+
});
|
|
35
|
+
},
|
|
36
|
+
[IncomingMessageType.OnboardingCloseButtonClicked]: () => {
|
|
37
|
+
closeOnboardingModal();
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* messageRouter routes the message to the appropriate message handler based on the host
|
|
43
|
+
* @private - not meant to be invoked publicly
|
|
44
|
+
*/
|
|
45
|
+
const messageRouter = (event: MessageEvent): void => {
|
|
46
|
+
if (event.origin === _zeroHashOnboardingOrigin) {
|
|
47
|
+
const incomingMessage: IncomingMessage = event.data;
|
|
48
|
+
if (incomingMessage && incomingMessage.type && onboardingMessageHandlers[incomingMessage.type]) {
|
|
49
|
+
try {
|
|
50
|
+
onboardingMessageHandlers[incomingMessage.type](incomingMessage.payload);
|
|
51
|
+
} catch (e) {
|
|
52
|
+
console.error(e);
|
|
53
|
+
// TODO: emit event that informs the platform of failure event, to inform ZH of bug
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export class ZeroHashSDK implements IZeroHashSDK {
|
|
60
|
+
private rootQuerySelector: string = "";
|
|
61
|
+
private onboardingInitialized: boolean = false;
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Sets up the ZeroHash SDK and appends the ZeroHash DOM elements onto the page.
|
|
65
|
+
*
|
|
66
|
+
* For more information, see {@code IInitializeParameters}
|
|
67
|
+
*/
|
|
68
|
+
constructor(
|
|
69
|
+
{
|
|
70
|
+
zeroHashOnboardingURL = DEFAULT_ZH_ONBOARDING_URL,
|
|
71
|
+
rootQuerySelector,
|
|
72
|
+
userOnboardingJWT
|
|
73
|
+
}: IInitializeParameters
|
|
74
|
+
) {
|
|
75
|
+
_zeroHashOnboardingURL = zeroHashOnboardingURL
|
|
76
|
+
const onboardingURL = new URL(_zeroHashOnboardingURL);
|
|
77
|
+
_zeroHashOnboardingOrigin = onboardingURL.origin;
|
|
78
|
+
if (rootQuerySelector) {
|
|
79
|
+
this.rootQuerySelector = rootQuerySelector as string
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// register message handler
|
|
83
|
+
window.addEventListener(
|
|
84
|
+
"message",
|
|
85
|
+
messageRouter,
|
|
86
|
+
false
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
if (userOnboardingJWT) {
|
|
90
|
+
this.setUserOnboardingJWT({
|
|
91
|
+
userOnboardingJWT,
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* setUserOnboardingJWT sets the JWT to be whatever value is provided.
|
|
98
|
+
* The JWT should be the UserJWT provided by ZeroHash via the platform
|
|
99
|
+
* API proxied through your servers. As ZeroHash cannot authenticate
|
|
100
|
+
* your users' requests, it is paramount that the user be authenticated
|
|
101
|
+
* and validated on your servers, and exchanged for the JWT using your
|
|
102
|
+
* API key. DO NOT have the JWT exchange logic be on your front-end.
|
|
103
|
+
*
|
|
104
|
+
* As a precaution, we may restrict traffic to the JWT exchange API to
|
|
105
|
+
* whitelisted IPs that come from your server.
|
|
106
|
+
*/
|
|
107
|
+
setUserOnboardingJWT(params: ISetUserOnboardingJWTParameters): void {
|
|
108
|
+
store.dispatch({
|
|
109
|
+
type: ACTION_SET_USER_ONBOARDING_JWT,
|
|
110
|
+
userOnboardingJWT: params.userOnboardingJWT,
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* isOnboardingModalOpen returns true if the onboarding modal is open,
|
|
116
|
+
* false otherwise
|
|
117
|
+
*/
|
|
118
|
+
isOnboardingModalOpen(): boolean {
|
|
119
|
+
return store.getState().appState.isOnboardingAppActive
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* openOnboardingModal opens the onboarding modal
|
|
124
|
+
*/
|
|
125
|
+
openOnboardingModal(params: IOpenOnboardingModalParameters): void {
|
|
126
|
+
if (params.userOnboardingJWT) {
|
|
127
|
+
this.setUserOnboardingJWT({
|
|
128
|
+
userOnboardingJWT: params.userOnboardingJWT,
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
openOnboardingModal();
|
|
132
|
+
if (this.onboardingInitialized) {
|
|
133
|
+
// if it was previously initialized already,
|
|
134
|
+
// simply open the modal, do not re-inject.
|
|
135
|
+
return
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
let root: HTMLElement | null = null;
|
|
139
|
+
if (this.rootQuerySelector) {
|
|
140
|
+
root = document.querySelector(this.rootQuerySelector);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (!root) {
|
|
144
|
+
// Create the default ZeroHash div, e.g. <div id="zerohash-49vt8y"/>
|
|
145
|
+
root = document.createElement("div");
|
|
146
|
+
const rootID = generateRootID();
|
|
147
|
+
root.id = rootID;
|
|
148
|
+
root.style.position = "absolute";
|
|
149
|
+
root.style.top = "0";
|
|
150
|
+
root.style.left = "0";
|
|
151
|
+
this.rootQuerySelector = `#${rootID}`;
|
|
152
|
+
|
|
153
|
+
// Append the ZeroHash div as a child of <body>
|
|
154
|
+
const destinationBox = document.querySelector("body") as HTMLBodyElement;
|
|
155
|
+
destinationBox.appendChild(root);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (root) {
|
|
159
|
+
// Create a shadow wrapper around ZeroHash app for encapsulation
|
|
160
|
+
const shadowRoot = root.attachShadow({mode: 'closed'});
|
|
161
|
+
|
|
162
|
+
// Render the React modal
|
|
163
|
+
ReactDOM
|
|
164
|
+
.createRoot(shadowRoot)
|
|
165
|
+
.render(
|
|
166
|
+
<Provider store={store}>
|
|
167
|
+
<OnboardingApp
|
|
168
|
+
zeroHashOnboardingURL={_zeroHashOnboardingURL}
|
|
169
|
+
/>
|
|
170
|
+
</Provider>
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
// set as initialized
|
|
174
|
+
this.onboardingInitialized = true;
|
|
175
|
+
} else {
|
|
176
|
+
const errMsg = "failed to append ZeroHash root to the page: root not found";
|
|
177
|
+
console.error(errMsg);
|
|
178
|
+
throw new Error(errMsg)
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* closeOnboardingModalModal hides the onboarding modal
|
|
184
|
+
*/
|
|
185
|
+
closeOnboardingModal(): void {
|
|
186
|
+
closeOnboardingModal();
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (window) {
|
|
191
|
+
// if the window object exists, the ZeroHash SDK
|
|
192
|
+
// constructor will be appended to it for easier
|
|
193
|
+
// access.
|
|
194
|
+
window.zerohash = ZeroHashSDK;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
export default ZeroHashSDK;
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import React, {useEffect, useRef, useState} from "react";
|
|
2
|
+
import {
|
|
3
|
+
appWrapperStyle,
|
|
4
|
+
containerMediaStyles,
|
|
5
|
+
containerStyle,
|
|
6
|
+
iframeStyle,
|
|
7
|
+
iframeWrapperStyle,
|
|
8
|
+
minWidthMatcher,
|
|
9
|
+
modalStyle,
|
|
10
|
+
screenSizes,
|
|
11
|
+
appWrapperHiddenStyle
|
|
12
|
+
} from "../styles";
|
|
13
|
+
import {connect} from "react-redux";
|
|
14
|
+
import {MESSAGE_TYPE_SEND_JWT_TOKEN} from "./constants";
|
|
15
|
+
import {closeOnboardingModal} from "../redux/actions";
|
|
16
|
+
|
|
17
|
+
interface AppProps {
|
|
18
|
+
isOnboardingAppActive: boolean
|
|
19
|
+
isOnboardingAppLoaded: boolean
|
|
20
|
+
userOnboardingJWT: string
|
|
21
|
+
zeroHashOnboardingURL: string
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const appLoadTime = Date.now();
|
|
25
|
+
|
|
26
|
+
// eslint-disable-next-line react-refresh/only-export-components
|
|
27
|
+
const OnboardingApp = (
|
|
28
|
+
{
|
|
29
|
+
isOnboardingAppActive,
|
|
30
|
+
isOnboardingAppLoaded,
|
|
31
|
+
userOnboardingJWT,
|
|
32
|
+
zeroHashOnboardingURL
|
|
33
|
+
}: AppProps
|
|
34
|
+
) => {
|
|
35
|
+
const [container, setContainerStyle] = useState(containerStyle);
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* setContainer sets the CSS styles based on the current matching media queries
|
|
39
|
+
*/
|
|
40
|
+
const setContainer = () => {
|
|
41
|
+
const sizeId = minWidthMatcher();
|
|
42
|
+
const containerMediaStyle = containerMediaStyles[sizeId]
|
|
43
|
+
setContainerStyle({
|
|
44
|
+
...containerStyle,
|
|
45
|
+
...containerMediaStyle,
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const iRef: React.MutableRefObject<HTMLIFrameElement | null> = useRef<HTMLIFrameElement | null>(null);
|
|
50
|
+
useEffect(() => {
|
|
51
|
+
// Send JWT to iframe when all 3 props are ready
|
|
52
|
+
if (zeroHashOnboardingURL && isOnboardingAppLoaded && userOnboardingJWT && iRef.current?.contentWindow) {
|
|
53
|
+
iRef.current.contentWindow.postMessage({
|
|
54
|
+
type: MESSAGE_TYPE_SEND_JWT_TOKEN,
|
|
55
|
+
userOnboardingJWT: userOnboardingJWT,
|
|
56
|
+
}, zeroHashOnboardingURL);
|
|
57
|
+
}
|
|
58
|
+
}, [isOnboardingAppLoaded, userOnboardingJWT, zeroHashOnboardingURL])
|
|
59
|
+
|
|
60
|
+
useEffect(() => {
|
|
61
|
+
// set the styles when the screen size changes
|
|
62
|
+
setContainer();
|
|
63
|
+
if (window) {
|
|
64
|
+
screenSizes.forEach(({size}) => {
|
|
65
|
+
window
|
|
66
|
+
.matchMedia(`(min-width: ${size}px)`)
|
|
67
|
+
.addEventListener('change', setContainer)
|
|
68
|
+
})
|
|
69
|
+
return () => screenSizes.forEach(({size}) => {
|
|
70
|
+
window
|
|
71
|
+
.matchMedia(`(min-width: ${size}px)`)
|
|
72
|
+
.removeEventListener('change', setContainer)
|
|
73
|
+
})
|
|
74
|
+
}
|
|
75
|
+
}, []);
|
|
76
|
+
|
|
77
|
+
const iframeURL = new URL(zeroHashOnboardingURL);
|
|
78
|
+
iframeURL.searchParams.set(
|
|
79
|
+
'name',
|
|
80
|
+
appLoadTime.toString(),
|
|
81
|
+
)
|
|
82
|
+
if (window) {
|
|
83
|
+
iframeURL.searchParams.set(
|
|
84
|
+
'origin',
|
|
85
|
+
window.location.origin,
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return <div
|
|
90
|
+
style={isOnboardingAppActive ? appWrapperStyle : appWrapperHiddenStyle}
|
|
91
|
+
onClick={closeOnboardingModal}
|
|
92
|
+
>
|
|
93
|
+
<div
|
|
94
|
+
style={{
|
|
95
|
+
...modalStyle,
|
|
96
|
+
...container,
|
|
97
|
+
}}
|
|
98
|
+
>
|
|
99
|
+
<div
|
|
100
|
+
style={{
|
|
101
|
+
...iframeWrapperStyle,
|
|
102
|
+
}}
|
|
103
|
+
>
|
|
104
|
+
<iframe
|
|
105
|
+
ref={iRef}
|
|
106
|
+
title="Zerohash Onboarding"
|
|
107
|
+
src={iframeURL.toString()}
|
|
108
|
+
style={iframeStyle}
|
|
109
|
+
/>
|
|
110
|
+
</div>
|
|
111
|
+
</div>
|
|
112
|
+
</div>;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const mapStateToProps = (state: {
|
|
116
|
+
appState: {
|
|
117
|
+
isOnboardingAppActive: boolean,
|
|
118
|
+
isOnboardingAppLoaded: boolean,
|
|
119
|
+
userOnboardingJWT: string,
|
|
120
|
+
};
|
|
121
|
+
}) => ({
|
|
122
|
+
isOnboardingAppActive: state.appState.isOnboardingAppActive,
|
|
123
|
+
isOnboardingAppLoaded: state.appState.isOnboardingAppLoaded,
|
|
124
|
+
userOnboardingJWT: state.appState.userOnboardingJWT,
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
// eslint-disable-next-line react-refresh/only-export-components
|
|
128
|
+
export default connect(mapStateToProps)(OnboardingApp);
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import store from "../store";
|
|
2
|
+
import {ACTION_SET_ONBOARDING_MODAL_STATE} from "../reducers/constants";
|
|
3
|
+
|
|
4
|
+
export const openOnboardingModal = () => store.dispatch({
|
|
5
|
+
type: ACTION_SET_ONBOARDING_MODAL_STATE,
|
|
6
|
+
isOnboardingAppActive: true,
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
export const closeOnboardingModal = () => store.dispatch({
|
|
10
|
+
type: ACTION_SET_ONBOARDING_MODAL_STATE,
|
|
11
|
+
isOnboardingAppActive: false,
|
|
12
|
+
});
|