Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 13 additions & 8 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,38 +3,43 @@ on:
workflow_dispatch:
inputs:
version:
description: 'Version to publish (patch, minor, major)'
description: 'Version to publish (patch, minor, major, current)'
required: true
default: 'patch'
type: choice
options:
- patch
- minor
- major
- current

jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '18.x'
registry-url: 'https://registry.npmjs.org'

- name: Install dependencies
run: npm ci

- name: Build
run: npm run build

- name: Version and Publish
run: |
git config --global user.email "${{ secrets.NPM_EMAIL }}"
git config --global user.name "${{ secrets.NPM_USERNAME }}"
npm version ${{ inputs.version }}
npm publish --access public
if [[ "${{ inputs.version }}" == "current" ]]; then
npm publish --access public
else
npm version ${{ inputs.version }}
npm publish --access public
fi
env:
NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 4 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,16 +1,13 @@
{
"name": "embed-react-native-sdk",
"version": "4.0.1",
"version": "5.0.1",
"description": "React Native SDK for Embedding TS",
"main": "dist/index.cjs.js",
"module": "dist/index.esm.js",
"types": "dist/index.d.ts",
"files": [
"dist/**/*"
],
"dependencies": {
"use-deep-compare-effect": "^1.8.1"
},
"peerDependencies": {
"react": ">=16.8.0",
"react-native": ">=0.60.0",
Expand All @@ -34,5 +31,8 @@
},
"scripts": {
"build": "rollup -c"
},
"dependencies": {
"use-deep-compare-effect": "^1.8.1"
}
}
129 changes: 129 additions & 0 deletions src/BaseEmbed.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
import React, {
useRef,
useEffect,
useImperativeHandle,
forwardRef,
useMemo,
useState,
useCallback,
} from "react";
import { WebView, WebViewMessageEvent } from "react-native-webview";
import { EmbedBridge } from "./event-bridge";
import { embedConfigCache } from "./init";
import * as Constants from './constants';
import { MSG_TYPE, DEFAULT_WEBVIEW_CONFIG } from './constants';
import { ERROR_MESSAGE, notifyErrorSDK } from "./utils";
import { ViewConfig, EmbedEventHandlers, EmbedEvent } from "./types";
import useDeepCompareEffect from "use-deep-compare-effect";

interface BaseEmbedProps extends ViewConfig, EmbedEventHandlers {
typeofEmbed: string;
onErrorSDK?: (error: Error) => void;
[key: string]: any;
}

export interface TSEmbedRef {
trigger: (hostEventName: string, payload?: any) => Promise<any>;
}

export const BaseEmbed = forwardRef<TSEmbedRef, BaseEmbedProps>(
(props, ref) => {
const webViewRef = useRef<WebView>(null);
const [embedBridge, setEmbedBridge] = useState<EmbedBridge | null>(null);
const [vercelShellLoaded, setVercelShellLoaded] = useState(false);
const [viewConfig, setViewConfig] = useState<Record<string, any>>({});
const [pendingHandlers, setPendingHandlers] = useState<Array<[string, Function]>>([]);
const [isWebViewReady, setIsWebViewReady] = useState(false);

useDeepCompareEffect(() => {
const newViewConfig: Record<string, any> = {};
Object.keys(props).forEach((key) => {
if (key.startsWith("on")) {
const eventName = key.substring(2);
const embedEventName = EmbedEvent[eventName as keyof typeof EmbedEvent];
setPendingHandlers((prev) => [...prev, [embedEventName, props[key]]]);
} else if (key !== 'embedType') {
newViewConfig[key] = props[key];
}
});
setViewConfig(newViewConfig);
}, [props]);

const sendConfigToShell = useCallback((bridge: EmbedBridge, config: Record<string, any>) => {
if (!webViewRef.current || !vercelShellLoaded) {
console.info("Waiting for Vercel shell to load...");
return;
}

const initMsg = {
type: MSG_TYPE.INIT,
payload: embedConfigCache,
};

bridge.sendMessage(initMsg);

const message = {
type: MSG_TYPE.EMBED,
embedType: props.embedType,
viewConfig: config,
};

bridge.sendMessage(message);
}, [props.embedType, vercelShellLoaded]);

useDeepCompareEffect(() => {
if (embedBridge && vercelShellLoaded && isWebViewReady) {
sendConfigToShell(embedBridge, viewConfig);
}
}, [viewConfig, embedBridge, vercelShellLoaded, isWebViewReady, sendConfigToShell]);

useImperativeHandle(ref, () => ({
trigger: (hostEventName: string, payload?: any) => {
return embedBridge?.trigger(hostEventName, payload) || Promise.resolve(undefined);
},
}));

const handleInitVercelShell = () => {
setVercelShellLoaded(true);
const newEmbedBridge = new EmbedBridge(webViewRef);
setEmbedBridge(newEmbedBridge);

pendingHandlers.forEach(([eventName, callback]) => {
newEmbedBridge.registerEmbedEvent(eventName, callback);
});
setPendingHandlers([]);
sendConfigToShell(newEmbedBridge, viewConfig);
};

const handleMessage = (event: WebViewMessageEvent) => {
try {
const msg = JSON.parse(event.nativeEvent.data);
if (msg.type === MSG_TYPE.INIT_VERCEL_SHELL) {
handleInitVercelShell();
setIsWebViewReady(true);
}
embedBridge?.handleMessage(msg);
} catch (err) {
notifyErrorSDK(err as Error, props.onErrorSDK, ERROR_MESSAGE.EVENT_ERROR);
}
};

return (
<WebView
ref={webViewRef}
source={{ uri: Constants.VERCEL_SHELL_URL }}
onMessage={handleMessage}
{...DEFAULT_WEBVIEW_CONFIG}
onError={(syntheticEvent) => {
const { nativeEvent } = syntheticEvent;
console.warn("[BaseEmbed] WebView error: ", nativeEvent);
}}
onHttpError={(syntheticEvent) => {
const { nativeEvent } = syntheticEvent;
console.warn("[BaseEmbed] HTTP error: ", nativeEvent);
}}
style={{ flex: 1 }}
/>
);
}
);
21 changes: 21 additions & 0 deletions src/LiveboardEmbed.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import React, { forwardRef } from "react";
import { BaseEmbed, TSEmbedRef } from "./BaseEmbed";
import { LiveboardViewConfig, EmbedEvent } from "./types";

type EventHandlers = {
[K in keyof typeof EmbedEvent as `on${Capitalize<string & K>}`]?: (event: any) => void;
};

export type LiveboardEmbedRef = TSEmbedRef;

export const LiveboardEmbed = forwardRef<TSEmbedRef, LiveboardViewConfig & EventHandlers>(
(props, ref) => {
return (
<BaseEmbed
ref={ref}
embedType="Liveboard"
{...props}
/>
);
}
);
22 changes: 0 additions & 22 deletions src/LiveboardEmbedClass.ts

This file was deleted.

79 changes: 0 additions & 79 deletions src/componentFactory.tsx

This file was deleted.

29 changes: 29 additions & 0 deletions src/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { WebViewProps } from 'react-native-webview';

export const VERCEL_SHELL_URL = "https://mobile-embed-shell.vercel.app";

export enum MSG_TYPE {
INIT = "INIT",
EMBED = "EMBED",
INIT_VERCEL_SHELL = "INIT_VERCEL_SHELL",
REQUEST_AUTH_TOKEN = "REQUEST_AUTH_TOKEN",
EMBED_EVENT = "EMBED_EVENT",
HOST_EVENT_REPLY = "HOST_EVENT_REPLY",
EMBED_EVENT_REPLY = "EVENT_REPLY",
HOST_EVENT = "HOST_EVENT",
AUTH_TOKEN_RESPONSE = "AUTH_TOKEN_RESPONSE",
}

export const DEFAULT_WEBVIEW_CONFIG: WebViewProps = {
javaScriptEnabled: true,
domStorageEnabled: true,
mixedContentMode: 'always',
keyboardDisplayRequiresUserAction: false,
automaticallyAdjustContentInsets: false,
scrollEnabled: false,
style: {
flex: 1,
height: '100%',
width: '100%',
},
};
Loading