diff --git a/configs/webpack/dev.js b/configs/webpack/dev.js index 0d027ff..2af8ef9 100644 --- a/configs/webpack/dev.js +++ b/configs/webpack/dev.js @@ -9,7 +9,7 @@ module.exports = merge(commonConfig, { 'react-hot-loader/patch', // activate HMR for React 'webpack-dev-server/client?http://localhost:8080',// bundle the client for webpack-dev-server and connect to the provided endpoint 'webpack/hot/only-dev-server', // bundle the client for hot reloading, only- means to only hot reload for successful updates - './index.tsx' // the entry point of our app + './index.ts' // the entry point of our app ], devServer: { hot: true, // enable HMR on the server diff --git a/configs/webpack/prod.js b/configs/webpack/prod.js index 3e6e069..2d9a3ad 100644 --- a/configs/webpack/prod.js +++ b/configs/webpack/prod.js @@ -6,7 +6,7 @@ const commonConfig = require('./common') module.exports = merge(commonConfig, { mode: 'production', - entry: './index.tsx', + entry: './index.ts', output: { filename: 'js/bundle.[hash].min.js', path: resolve(__dirname, '../../dist'), diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..8885e25 --- /dev/null +++ b/src/index.ts @@ -0,0 +1,12 @@ +import renderApp from './render' +import { isClashX, setupJsBridge } from './lib/jsBridge' + +/** + * Global entry + * Will check if need setup jsbridge + */ +if (isClashX()) { + setupJsBridge(renderApp) +} else { + renderApp() +} diff --git a/src/index.tsx b/src/index.tsx deleted file mode 100644 index 5f24aa7..0000000 --- a/src/index.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import * as React from 'react' -import { render } from 'react-dom' -import { AppContainer } from 'react-hot-loader' -import App from './components/App' - -const rootEl = document.getElementById('root') - -render( - - - , - rootEl -) - -// Hot Module Replacement API -declare let module: { hot: any } - -if (module.hot) { - module.hot.accept('./components/App', () => { - const NewApp = require('./components/App').default - render( - - - , - rootEl - ) - }) -} diff --git a/src/lib/jsBridge.ts b/src/lib/jsBridge.ts new file mode 100644 index 0000000..36a24b1 --- /dev/null +++ b/src/lib/jsBridge.ts @@ -0,0 +1,91 @@ +/** + * For support ClashX runtime + * + * Clash Dashboard will use jsbridge to + * communicate with ClashX + * + * Before React app rendered, jsbridge + * should be checked if initialized, + * and also should checked if it's + * ClashX runtime + * + * @author jas0ncn + */ + +/** + * declare javascript bridge API + */ +export interface JsBridge { + + /** + * Register a javascript bridge event handle + */ + registerHandler: (eventName: string, callback: (data: any, responseCallback: (param: any) => void) => void) => void + + /** + * Call a native handle + */ + callHandler: (handleName: string, data: any, responseCallback: (responseData: any) => void) => void + + /** + * Who knows + */ + disableJavscriptAlertBoxSafetyTimeout: () => void + +} + +declare global { + + interface Window { + + /** + * Global jsbridge instance + */ + WebViewJavascriptBridge?: JsBridge | null + + /** + * Global jsbridge init callback + */ + WVJBCallbacks?: Function[] + + } + +} + +/** + * setup a jsbridge before app render + * @param {Function} cb callback when jsbridge initialized + * @see https://github.com/marcuswestin/WebViewJavascriptBridge + */ +export function setupJsBridge (callback = jsBridge => {}) { + /** + * You need check if inClashX first + */ + if (!isClashX()) { + return callback(null) + } + + if (window.WebViewJavascriptBridge) { + return callback(window.WebViewJavascriptBridge) + } + + // setup callback + if (window.WVJBCallbacks) { + return window.WVJBCallbacks.push(callback) + } + + window.WVJBCallbacks = [callback] + + const WVJBIframe = document.createElement('iframe') + WVJBIframe.style.display = 'none' + WVJBIframe.src = 'https://__bridge_loaded__' + document.documentElement.appendChild(WVJBIframe) + setTimeout(() => document.documentElement.removeChild(WVJBIframe), 0) +} + +/** + * Check if perched in ClashX Runtime + */ +export function isClashX () { + return navigator.userAgent === 'ClashX Runtime' +} diff --git a/src/render.tsx b/src/render.tsx new file mode 100644 index 0000000..0bf0971 --- /dev/null +++ b/src/render.tsx @@ -0,0 +1,30 @@ +import * as React from 'react' +import { render } from 'react-dom' +import { AppContainer } from 'react-hot-loader' +import App from './components/App' + +const rootEl = document.getElementById('root') + +// Hot Module Replacement API +declare let module: { hot: any } + +export default function renderApp () { + render( + + + , + rootEl + ) + + if (module.hot) { + module.hot.accept('./components/App', () => { + const NewApp = require('./components/App').default + render( + + + , + rootEl + ) + }) + } +} diff --git a/tsconfig.json b/tsconfig.json index e63aa60..d284554 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -4,7 +4,6 @@ "sourceMap": true, "noImplicitAny": false, "noUnusedLocals": true, - "noUnusedParameters": true, "module": "commonjs", "target": "es5", "jsx": "react", diff --git a/tslint.json b/tslint.json index 503326f..cebb2eb 100644 --- a/tslint.json +++ b/tslint.json @@ -2,6 +2,7 @@ "extends": "tslint-config-standard", "rules": { "indent": [true, "spaces", 4], - "ter-indent": [true, 4] + "ter-indent": [true, 4], + "no-empty": false } }