Skip to content
Open
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
8 changes: 5 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "embed-react-native-sdk",
"version": "4.0.1",
"version": "4.0.3",
"description": "React Native SDK for Embedding TS",
"main": "dist/index.cjs.js",
"module": "dist/index.esm.js",
Expand All @@ -13,8 +13,7 @@
},
"peerDependencies": {
"react": ">=16.8.0",
"react-native": ">=0.60.0",
"react-native-webview": ">=11.0.0"
"react-native": ">=0.60.0"
},
"peerDependenciesMeta": {
"@types/react": {
Expand All @@ -32,6 +31,9 @@
"rollup-plugin-dts": "^6.1.1",
"typescript": "^5.7.3"
},
"optionalDependencies": {
"react-native-webview": "*"
},
"scripts": {
"build": "rollup -c"
}
Expand Down
6 changes: 3 additions & 3 deletions src/LiveboardEmbedClass.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@ import { TSEmbed } from './tsEmbed';
import { componentFactory } from './componentFactory';
import { LiveboardViewConfig } from './types';
import WebView from 'react-native-webview';
import { EmbedProps } from './util';
import { EmbedProps, ErrorCallback } from './util';
import React from 'react';

class LiveboardEmbedClass<T extends LiveboardViewConfig = LiveboardViewConfig> extends TSEmbed<T> {
constructor(webViewRef: React.RefObject<WebView>, config?: T) {
super(webViewRef, config);
constructor(webViewRef: React.RefObject<WebView>, onErrorSDK?: ErrorCallback, config?: T) {
super(webViewRef, onErrorSDK, config);
}
}

Expand Down
71 changes: 44 additions & 27 deletions src/componentFactory.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ import {
ViewConfig,
MessageCallback,
} from './types';
import { EmbedProps } from './util';
import { EmbedProps, ERROR_MESSAGE, notifyErrorSDK } from './util';
import { Text } from 'react-native';

export type EmbedEventHandlers = { [key in keyof typeof EmbedEvent as `on${Capitalize<key>}`]?: MessageCallback };

Expand Down Expand Up @@ -40,40 +41,56 @@ const getViewPropsAndListeners = <T extends EmbedProps, U extends ViewConfig>(
export const componentFactory = <T extends typeof TSEmbed, V extends ViewConfig, U extends EmbedProps>(
EmbedConstructor: T,
) => React.forwardRef<InstanceType<T>, U>((props, forwardedRef): JSX.Element | null => {
const { onErrorSDK, ...restProps } = props;
const originalEmbedProps = restProps as unknown as U;

const embedInstance = React.useRef<InstanceType<T> | null>(null);
const webViewRef = React.useRef<WebView>(null);
try{

if(!embedInstance.current) {
embedInstance.current = new EmbedConstructor(webViewRef) as InstanceType<T>;
}
if(!embedInstance.current) {
embedInstance.current = new EmbedConstructor(webViewRef, onErrorSDK) as InstanceType<T>;
}

const renderedWebView = React.useMemo((): JSX.Element | null => {
return embedInstance.current?.render() ?? null;
}, [props]);
const renderedWebView = React.useMemo((): JSX.Element | null => {
return embedInstance.current?.render() ?? null;
}, [originalEmbedProps]);

const { viewConfig, listeners } = React.useMemo(() => getViewPropsAndListeners<U, V>(props as U), [props]);
const { viewConfig, listeners } = React.useMemo(() => getViewPropsAndListeners<U, V>(originalEmbedProps), [originalEmbedProps]);

React.useEffect(() => {
return () => {
embedInstance.current?.destroy();
embedInstance.current = null;
}
}, [])
React.useEffect(() => {
try{
return () => {
embedInstance.current?.destroy();
embedInstance.current = null;
}
} catch(error) {
notifyErrorSDK(error as Error, onErrorSDK, ERROR_MESSAGE.COMPONENT_UNMOUNTED_ERROR);
}
}, [])

useDeepCompareEffect(() => {
try{
if(forwardedRef && typeof forwardedRef == 'object') {
(forwardedRef as React.MutableRefObject<InstanceType<T> | null>).current = embedInstance?.current;
}
embedInstance?.current?.updateConfig(viewConfig);

useDeepCompareEffect(() => {
if(forwardedRef && typeof forwardedRef == 'object') {
(forwardedRef as React.MutableRefObject<InstanceType<T> | null>).current = embedInstance?.current;
}
embedInstance?.current?.updateConfig(viewConfig);
Object.entries(listeners).forEach(([eventName, callback]) => {
embedInstance.current?.on(eventName as EmbedEvent, callback as MessageCallback);
});
} catch(error) {
notifyErrorSDK(error as Error, onErrorSDK, ERROR_MESSAGE.CONFIG_ERROR);
}
}, [viewConfig]);

Object.entries(listeners).forEach(([eventName, callback]) => {
embedInstance.current?.on(eventName as EmbedEvent, callback as MessageCallback);
});
}, [viewConfig]);
if(!embedInstance.current) {
return null;
}

if(!embedInstance.current) {
return null;
return renderedWebView ?? null;
} catch(error) {
notifyErrorSDK(error as Error, onErrorSDK, ERROR_MESSAGE.INIT_ERROR);
return <><Text>Unable to render the thoughtspot Webview</Text></>;
}

return renderedWebView ?? null;
});
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://embed-vercel-shell-git-class-based-final-yinstardevs-projects.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%',
},
};
23 changes: 12 additions & 11 deletions src/event-bridge.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import type { WebView } from "react-native-webview";
import { authFunctionCache } from "./init";
import { MSG_TYPE } from './constants';

export interface EmbedMessage {
type: string;
type: MSG_TYPE;
eventName?: string;
eventId?: string;
payload?: any;
Expand All @@ -14,7 +15,7 @@ export class EmbedBridge {
private events: Record<string, Function[]> = {};
private pendingReplies: Record<string, Function> = {};

constructor(private webViewRef: React.RefObject<WebView>) {}
constructor(private webViewRef: React.RefObject<WebView> | null) {}

registerEmbedEvent(eventName: string, callback: Function) {
if (!this.events[eventName]) {
Expand All @@ -24,15 +25,15 @@ export class EmbedBridge {
}

public trigger(hostEventName: string, payload?: any): Promise<any> {
if (!this.webViewRef.current) {
if (!this.webViewRef?.current) {
console.warn("webview is not ready for host event");
return Promise.resolve(undefined);
}
return new Promise((resolve) => {
const eventId = this.generateEventId();
this.pendingReplies[eventId] = resolve;
const message = {
type: "HOST_EVENT",
type: MSG_TYPE.HOST_EVENT,
eventId,
eventName: hostEventName,
payload,
Expand All @@ -43,25 +44,25 @@ export class EmbedBridge {

handleMessage(msg: any) {
switch (msg.type) {
case "REQUEST_AUTH_TOKEN": {
case MSG_TYPE.REQUEST_AUTH_TOKEN: {
authFunctionCache?.().then((token: string) => {
const replyTokenData = {
type: 'AUTH_TOKEN_RESPONSE',
type: MSG_TYPE.AUTH_TOKEN_RESPONSE,
token,
};
this.sendMessage(replyTokenData);
})
break;
}
case "EMBED_EVENT": {
case MSG_TYPE.EMBED_EVENT: {
if(msg?.hasResponder) {
this.triggerEventWithResponder(msg.eventName, msg.payload, msg.eventId);
} else {
this.triggerEvent(msg.eventName, msg.payload);
}
break;
}
case "HOST_EVENT_REPLY": {
case MSG_TYPE.HOST_EVENT_REPLY: {
if (msg.eventId && this.pendingReplies[msg.eventId]) {
this.pendingReplies[msg.eventId](msg.payload);
delete this.pendingReplies[msg.eventId];
Expand All @@ -83,7 +84,7 @@ export class EmbedBridge {
handlers.forEach(handler => {
handler(data, (responseData: any) => {
this.sendMessage({
type: 'EVENT_REPLY',
type: MSG_TYPE.EMBED_EVENT_REPLY,
eventId,
payload: responseData
});
Expand All @@ -94,7 +95,7 @@ export class EmbedBridge {
public sendMessage(msg: EmbedMessage) {
const msgString = JSON.stringify(msg);
const jsCode = `window.postMessage(${msgString}, "*");true;`;
this.webViewRef.current?.injectJavaScript(jsCode);
this.webViewRef?.current?.injectJavaScript(jsCode);
}

private generateEventId(): string {
Expand All @@ -104,6 +105,6 @@ export class EmbedBridge {
public destroy() {
this.events = {};
this.pendingReplies = {};
this.webViewRef = { current: null };
this.webViewRef = null;
}
}
12 changes: 7 additions & 5 deletions src/init.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import { WebView } from "react-native-webview";
import React, { useRef } from "react";

import { EmbedConfig } from "./types";
export let embedConfigCache: any = null;
export let authFunctionCache: (() => Promise<string>) | null = null;
export let authFunctionCache: (() => Promise<string>) | null | undefined = null;

interface EmbedConfigMobile extends Omit<EmbedConfig, 'getTokenFromSDK'> {
getTokenFromSDK?: boolean;
}

// TODO : add the webview at the time of the init.
export const init = (embedConfig: any) => {
export const init = (embedConfig: EmbedConfigMobile) => {
embedConfigCache = embedConfig;
authFunctionCache = embedConfig.getAuthToken;
embedConfigCache.getTokenFromSDK = true;
Expand Down
Loading