├── .npmrc ├── lib ├── copy.d.ts ├── detector.d.ts ├── utils.d.ts ├── copy.js ├── utils.js ├── index.d.ts ├── detector.js ├── wla.min.js └── index.js ├── examples ├── index.less ├── index.html └── index.ts ├── .gitignore ├── .npmignore ├── tsconfig.json ├── LICENSE ├── package.json ├── webpack.config.js ├── src ├── utils.ts ├── copy.ts ├── detector.ts └── index.ts └── README.md /.npmrc: -------------------------------------------------------------------------------- 1 | registry=http://registry.npmjs.org 2 | -------------------------------------------------------------------------------- /lib/copy.d.ts: -------------------------------------------------------------------------------- 1 | export declare function copy(text: string, options?: any): boolean; 2 | -------------------------------------------------------------------------------- /examples/index.less: -------------------------------------------------------------------------------- 1 | .demo{ 2 | padding:30px; 3 | a{ 4 | display:block; 5 | margin:20px 0; 6 | } 7 | } 8 | .tip{ 9 | border:1px solid red; 10 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # OS 2 | .DS_Store 3 | 4 | # node.js 5 | node_modules/ 6 | npm-debug.log 7 | yarn-error.log 8 | 9 | # vs 10 | .vs/ 11 | *.njsproj 12 | *.sln 13 | .vscode/* 14 | !.vscode/settings.json 15 | 16 | # Jetbrains 17 | .idea 18 | 19 | # project 20 | output 21 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # OS 2 | .DS_Store 3 | 4 | # node.js 5 | node_modules/ 6 | npm-debug.log 7 | yarn-error.log 8 | 9 | # vs 10 | .vs/ 11 | *.njsproj 12 | *.sln 13 | .vscode/* 14 | !.vscode/settings.json 15 | 16 | examples/ 17 | output/ 18 | src/ 19 | tsconfig.json 20 | webpack.config.js -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "module": "commonjs", 5 | "moduleResolution": "node", 6 | "noImplicitAny": false, 7 | "experimentalDecorators": true, 8 | "preserveConstEnums": true, 9 | "outDir": "lib", 10 | "declaration": true 11 | }, 12 | "exclude": [ 13 | "node_modules", 14 | "lib", 15 | "output" 16 | ], 17 | "include": [ 18 | "src/**/*.*" 19 | ] 20 | } -------------------------------------------------------------------------------- /lib/detector.d.ts: -------------------------------------------------------------------------------- 1 | export declare class Detector { 2 | _rules: { 3 | os: any[]; 4 | browser: any[]; 5 | }; 6 | constructor(rules: any); 7 | _detect(name: string, expression: any, ua: string): { 8 | name: string; 9 | version: string; 10 | codename: string; 11 | }; 12 | _parseItem(ua: string, patterns: any[], factory: any, detector: any): void; 13 | /** 14 | * parse ua 15 | * @param ua 16 | */ 17 | parse(ua: string): any; 18 | } 19 | declare const ua: string; 20 | declare const d: any; 21 | export { d as detector, ua }; 22 | -------------------------------------------------------------------------------- /examples/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 |
13 | 点击链接

14 | open
15 | down 16 |
17 |
18 | 19 | 20 | -------------------------------------------------------------------------------- /lib/utils.d.ts: -------------------------------------------------------------------------------- 1 | export declare const isIos: boolean; 2 | export declare const isAndroid: boolean; 3 | export declare const inWeixin: boolean; 4 | export declare const inQQ: boolean; 5 | export declare const inWeibo: boolean; 6 | export declare const inBaidu: boolean; 7 | export declare const enableULink: boolean; 8 | export declare const enableApplink: boolean; 9 | export declare const isIOSWithLocationCallSupport: boolean; 10 | export declare const isAndroidWithLocationCallSupport: boolean; 11 | /** 12 | * detect support link 13 | */ 14 | export declare const supportLink: () => boolean; 15 | /** 16 | * location call 17 | * @param url 18 | */ 19 | export declare const locationCall: (url: string) => void; 20 | /** 21 | * iframe call 22 | * @param url 23 | */ 24 | export declare const iframeCall: (url: string) => void; 25 | /** 26 | * merge object 27 | */ 28 | export declare const deepMerge: (firstObj: any, secondObj: any) => any; 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "web-launch-app", 3 | "version": "2.2.8", 4 | "description": "launch app from web page", 5 | "main": "./lib/index.js", 6 | "scripts": { 7 | "tsc": "tsc", 8 | "start": "http-server output", 9 | "build": "webpack -w" 10 | }, 11 | "keywords": [ 12 | "deeplink,scheme,universal link,applink,invoke,launch app" 13 | ], 14 | "author": "jawidx", 15 | "repository": { 16 | "type": "git", 17 | "url": "https://github.com/jawidx/web-launch-app.git" 18 | }, 19 | "bugs": { 20 | "url": "https://github.com/jawidx/web-launch-app/issues" 21 | }, 22 | "homepage": "https://github.com/jawidx/web-launch-app#readme", 23 | "license": "MIT", 24 | "dependencies": {}, 25 | "devDependencies": { 26 | "@babel/core": "^7.10.4", 27 | "@babel/preset-env": "^7.10.4", 28 | "babel-loader": "^8.0.4", 29 | "clean-webpack-plugin": "^0.1.17", 30 | "css-loader": "^0.28.7", 31 | "html-loader": "^0.5.1", 32 | "html-webpack-plugin": "^3.2.0", 33 | "http-server": "^0.11.1", 34 | "less": "^2.7.3", 35 | "less-loader": "^4.0.5", 36 | "ts-loader": "^4.4.2", 37 | "webpack": "^4.43.0", 38 | "webpack-cli": "^3.3.12" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 3 | const CleanWebpackPlugin = require('clean-webpack-plugin'); 4 | 5 | module.exports = { 6 | mode: 'production', 7 | target: 'web', 8 | entry: { 9 | demo: ['./examples/index.ts'], 10 | wla: ['./src/index.ts'] 11 | }, 12 | output: { 13 | filename: '[name].[chunkhash:6].js', 14 | path: path.resolve(__dirname, 'output'), 15 | library: 'WLA', 16 | libraryTarget: 'window', 17 | }, 18 | plugins: [ 19 | new CleanWebpackPlugin(['output']), 20 | new HtmlWebpackPlugin({ 21 | filename: 'demo.html', 22 | template: './examples/index.html', 23 | title: 'Demo Title', 24 | // chunksSortMode: none 25 | }), 26 | ], 27 | resolve: { 28 | extensions: ['*', '.js', '.jsx', '.ts', '.tsx'] 29 | }, 30 | module: { 31 | rules: [ 32 | { 33 | test: /\.(js|jsx|ts|tsx)$/, 34 | include: path.resolve(__dirname, 'src'), 35 | use: [ 36 | { 37 | loader: 'babel-loader', 38 | options: { 39 | presets: ['@babel/env'], 40 | } 41 | }, 42 | { 43 | loader: 'ts-loader', 44 | options: { 45 | configFile: 'tsconfig.json', 46 | }, 47 | } 48 | ] 49 | }, 50 | { 51 | test: /\.(html)$/, 52 | use: { 53 | loader: 'html-loader', 54 | } 55 | }, 56 | { 57 | test: /\.(css|less)$/, 58 | use: [ 59 | 'css-loader', 60 | 'less-loader' 61 | ] 62 | }, 63 | ] 64 | } 65 | }; -------------------------------------------------------------------------------- /src/utils.ts: -------------------------------------------------------------------------------- 1 | import { detector } from './detector' 2 | 3 | export const isIos = detector.os.name === 'ios'; 4 | export const isAndroid = detector.os.name === 'android'; 5 | export const inWeixin = detector.browser.name === 'micromessenger'; 6 | export const inQQ = detector.browser.name === 'qq'; 7 | export const inWeibo = detector.browser.name === 'weibo'; 8 | export const inBaidu = detector.browser.name === 'baidu'; 9 | 10 | export const enableULink = isIos && detector.os.version >= 9; 11 | export const enableApplink = isAndroid && detector.os.version >= 6; 12 | 13 | export const isIOSWithLocationCallSupport = isIos && detector.browser.name == 'safari' && detector.os.version >= 9; 14 | const isChromeWithLocationCallSupport = detector.browser.name == 'chrome' && detector.browser.version > 55; 15 | const isSamsungWithLocationCallSupport = detector.browser.name == 'samsung'; 16 | export const isAndroidWithLocationCallSupport = isAndroid && (isChromeWithLocationCallSupport || isSamsungWithLocationCallSupport); 17 | 18 | /** 19 | * detect support link 20 | */ 21 | export const supportLink = () => { 22 | let supportLink = false; 23 | if (enableApplink) { 24 | switch (detector.browser.name) { 25 | case 'chrome': 26 | case 'samsung': 27 | case 'zhousi': 28 | supportLink = true; 29 | break; 30 | default: 31 | supportLink = false; 32 | break; 33 | } 34 | } 35 | if (enableULink) { 36 | switch (detector.browser.name) { 37 | case 'uc': 38 | case 'qq': 39 | supportLink = false; 40 | break; 41 | default: 42 | supportLink = true; 43 | break; 44 | } 45 | } 46 | return supportLink; 47 | } 48 | 49 | /** 50 | * location call 51 | * @param url 52 | */ 53 | export const locationCall = (url: string) => { 54 | (top.location || location).href = url; 55 | } 56 | 57 | /** 58 | * iframe call 59 | * @param url 60 | */ 61 | export const iframeCall = (url: string) => { 62 | const iframe = document.createElement('iframe'); 63 | iframe.setAttribute('src', url); 64 | iframe.setAttribute('style', 'display:none'); 65 | document.body.appendChild(iframe); 66 | setTimeout(function () { 67 | document.body.removeChild(iframe); 68 | }, 200); 69 | } 70 | 71 | /** 72 | * merge object 73 | */ 74 | export const deepMerge = (firstObj, secondObj) => { 75 | for (var key in secondObj) { 76 | firstObj[key] = firstObj[key] && firstObj[key].toString() === "[object Object]" ? 77 | deepMerge(firstObj[key], secondObj[key]) : firstObj[key] = secondObj[key]; 78 | } 79 | return firstObj; 80 | } 81 | -------------------------------------------------------------------------------- /src/copy.ts: -------------------------------------------------------------------------------- 1 | function select(element) { 2 | var selectedText; 3 | if (element.nodeName === 'SELECT') { 4 | element.focus(); 5 | selectedText = element.value; 6 | } 7 | else if (element.nodeName === 'INPUT' || element.nodeName === 'TEXTAREA') { 8 | var isReadOnly = element.hasAttribute('readonly'); 9 | if (!isReadOnly) { 10 | element.setAttribute('readonly', ''); 11 | } 12 | 13 | element.select(); 14 | element.setSelectionRange(0, element.value.length); 15 | 16 | if (!isReadOnly) { 17 | element.removeAttribute('readonly'); 18 | } 19 | selectedText = element.value; 20 | } 21 | else { 22 | if (element.hasAttribute('contenteditable')) { 23 | element.focus(); 24 | } 25 | 26 | var selection = window.getSelection(); 27 | var range = document.createRange(); 28 | 29 | range.selectNodeContents(element); 30 | selection.removeAllRanges(); 31 | selection.addRange(range); 32 | 33 | selectedText = selection.toString(); 34 | } 35 | return selectedText; 36 | } 37 | 38 | export function copy(text: string, options?: any) { 39 | var debug, fakeElem, success = false; 40 | options = options || {}; 41 | debug = options.debug || false; 42 | try { 43 | const isRTL = document.documentElement.getAttribute('dir') == 'rtl'; 44 | fakeElem = document.createElement('textarea'); 45 | // Prevent zooming on iOS 46 | fakeElem.style.fontSize = '12pt'; 47 | // Reset box model 48 | fakeElem.style.border = '0'; 49 | fakeElem.style.padding = '0'; 50 | fakeElem.style.margin = '0'; 51 | // Move element out of screen horizontally 52 | fakeElem.style.position = 'absolute'; 53 | fakeElem.style[isRTL ? 'right' : 'left'] = '-9999px'; 54 | // Move element to the same position vertically 55 | let yPosition = window.pageYOffset || document.documentElement.scrollTop; 56 | fakeElem.style.top = `${yPosition}px`; 57 | fakeElem.setAttribute('readonly', ''); 58 | fakeElem.value = text; 59 | document.body.appendChild(fakeElem); 60 | 61 | select(fakeElem); 62 | 63 | var successful = document.execCommand('copy'); 64 | if (!successful) { 65 | throw new Error('copy command was unsuccessful'); 66 | } 67 | success = true; 68 | } catch (err) { 69 | debug && console.error('unable to copy using execCommand: ', err); 70 | debug && console.warn('trying IE specific stuff'); 71 | try { 72 | (window as any).clipboardData.setData('text', text); 73 | success = true; 74 | } catch (err) { 75 | debug && console.error('unable to copy using clipboardData: ', err); 76 | } 77 | } finally { 78 | if (fakeElem) { 79 | document.body.removeChild(fakeElem); 80 | } 81 | } 82 | return success; 83 | } 84 | -------------------------------------------------------------------------------- /lib/copy.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.copy = void 0; 4 | function select(element) { 5 | var selectedText; 6 | if (element.nodeName === 'SELECT') { 7 | element.focus(); 8 | selectedText = element.value; 9 | } 10 | else if (element.nodeName === 'INPUT' || element.nodeName === 'TEXTAREA') { 11 | var isReadOnly = element.hasAttribute('readonly'); 12 | if (!isReadOnly) { 13 | element.setAttribute('readonly', ''); 14 | } 15 | element.select(); 16 | element.setSelectionRange(0, element.value.length); 17 | if (!isReadOnly) { 18 | element.removeAttribute('readonly'); 19 | } 20 | selectedText = element.value; 21 | } 22 | else { 23 | if (element.hasAttribute('contenteditable')) { 24 | element.focus(); 25 | } 26 | var selection = window.getSelection(); 27 | var range = document.createRange(); 28 | range.selectNodeContents(element); 29 | selection.removeAllRanges(); 30 | selection.addRange(range); 31 | selectedText = selection.toString(); 32 | } 33 | return selectedText; 34 | } 35 | function copy(text, options) { 36 | var debug, fakeElem, success = false; 37 | options = options || {}; 38 | debug = options.debug || false; 39 | try { 40 | var isRTL = document.documentElement.getAttribute('dir') == 'rtl'; 41 | fakeElem = document.createElement('textarea'); 42 | // Prevent zooming on iOS 43 | fakeElem.style.fontSize = '12pt'; 44 | // Reset box model 45 | fakeElem.style.border = '0'; 46 | fakeElem.style.padding = '0'; 47 | fakeElem.style.margin = '0'; 48 | // Move element out of screen horizontally 49 | fakeElem.style.position = 'absolute'; 50 | fakeElem.style[isRTL ? 'right' : 'left'] = '-9999px'; 51 | // Move element to the same position vertically 52 | var yPosition = window.pageYOffset || document.documentElement.scrollTop; 53 | fakeElem.style.top = yPosition + "px"; 54 | fakeElem.setAttribute('readonly', ''); 55 | fakeElem.value = text; 56 | document.body.appendChild(fakeElem); 57 | select(fakeElem); 58 | var successful = document.execCommand('copy'); 59 | if (!successful) { 60 | throw new Error('copy command was unsuccessful'); 61 | } 62 | success = true; 63 | } 64 | catch (err) { 65 | debug && console.error('unable to copy using execCommand: ', err); 66 | debug && console.warn('trying IE specific stuff'); 67 | try { 68 | window.clipboardData.setData('text', text); 69 | success = true; 70 | } 71 | catch (err) { 72 | debug && console.error('unable to copy using clipboardData: ', err); 73 | } 74 | } 75 | finally { 76 | if (fakeElem) { 77 | document.body.removeChild(fakeElem); 78 | } 79 | } 80 | return success; 81 | } 82 | exports.copy = copy; 83 | -------------------------------------------------------------------------------- /lib/utils.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.deepMerge = exports.iframeCall = exports.locationCall = exports.supportLink = exports.isAndroidWithLocationCallSupport = exports.isIOSWithLocationCallSupport = exports.enableApplink = exports.enableULink = exports.inBaidu = exports.inWeibo = exports.inQQ = exports.inWeixin = exports.isAndroid = exports.isIos = void 0; 4 | var detector_1 = require("./detector"); 5 | exports.isIos = detector_1.detector.os.name === 'ios'; 6 | exports.isAndroid = detector_1.detector.os.name === 'android'; 7 | exports.inWeixin = detector_1.detector.browser.name === 'micromessenger'; 8 | exports.inQQ = detector_1.detector.browser.name === 'qq'; 9 | exports.inWeibo = detector_1.detector.browser.name === 'weibo'; 10 | exports.inBaidu = detector_1.detector.browser.name === 'baidu'; 11 | exports.enableULink = exports.isIos && detector_1.detector.os.version >= 9; 12 | exports.enableApplink = exports.isAndroid && detector_1.detector.os.version >= 6; 13 | exports.isIOSWithLocationCallSupport = exports.isIos && detector_1.detector.browser.name == 'safari' && detector_1.detector.os.version >= 9; 14 | var isChromeWithLocationCallSupport = detector_1.detector.browser.name == 'chrome' && detector_1.detector.browser.version > 55; 15 | var isSamsungWithLocationCallSupport = detector_1.detector.browser.name == 'samsung'; 16 | exports.isAndroidWithLocationCallSupport = exports.isAndroid && (isChromeWithLocationCallSupport || isSamsungWithLocationCallSupport); 17 | /** 18 | * detect support link 19 | */ 20 | exports.supportLink = function () { 21 | var supportLink = false; 22 | if (exports.enableApplink) { 23 | switch (detector_1.detector.browser.name) { 24 | case 'chrome': 25 | case 'samsung': 26 | case 'zhousi': 27 | supportLink = true; 28 | break; 29 | default: 30 | supportLink = false; 31 | break; 32 | } 33 | } 34 | if (exports.enableULink) { 35 | switch (detector_1.detector.browser.name) { 36 | case 'uc': 37 | case 'qq': 38 | supportLink = false; 39 | break; 40 | default: 41 | supportLink = true; 42 | break; 43 | } 44 | } 45 | return supportLink; 46 | }; 47 | /** 48 | * location call 49 | * @param url 50 | */ 51 | exports.locationCall = function (url) { 52 | (top.location || location).href = url; 53 | }; 54 | /** 55 | * iframe call 56 | * @param url 57 | */ 58 | exports.iframeCall = function (url) { 59 | var iframe = document.createElement('iframe'); 60 | iframe.setAttribute('src', url); 61 | iframe.setAttribute('style', 'display:none'); 62 | document.body.appendChild(iframe); 63 | setTimeout(function () { 64 | document.body.removeChild(iframe); 65 | }, 200); 66 | }; 67 | /** 68 | * merge object 69 | */ 70 | exports.deepMerge = function (firstObj, secondObj) { 71 | for (var key in secondObj) { 72 | firstObj[key] = firstObj[key] && firstObj[key].toString() === "[object Object]" ? 73 | exports.deepMerge(firstObj[key], secondObj[key]) : firstObj[key] = secondObj[key]; 74 | } 75 | return firstObj; 76 | }; 77 | -------------------------------------------------------------------------------- /lib/index.d.ts: -------------------------------------------------------------------------------- 1 | import { copy } from './copy'; 2 | import { ua, detector } from './detector'; 3 | import { isAndroid, isIos, inWeibo, inWeixin, inQQ, inBaidu, enableApplink, enableULink, supportLink, locationCall, iframeCall } from './utils'; 4 | export { copy, ua, detector }; 5 | export { isAndroid, isIos, inWeibo, inWeixin, inQQ, inBaidu, enableApplink, enableULink, supportLink, locationCall, iframeCall, }; 6 | export declare class LaunchApp { 7 | static defaultConfig: any; 8 | static openChannel: { 9 | scheme: { 10 | preOpen(opt: any): any; 11 | open: (url: string) => void; 12 | }; 13 | link: { 14 | preOpen: (opt: any) => any; 15 | open: (url: string) => void; 16 | }; 17 | guide: { 18 | open: () => void; 19 | }; 20 | store: { 21 | open: (noTimeout: any) => void; 22 | }; 23 | unknown: { 24 | open: () => void; 25 | }; 26 | }; 27 | static openStatus: { 28 | FAILED: number; 29 | SUCCESS: number; 30 | UNKNOWN: number; 31 | }; 32 | static callbackResult: { 33 | DO_NOTING: number; 34 | OPEN_LAND_PAGE: number; 35 | OPEN_APP_STORE: number; 36 | }; 37 | private readonly configs; 38 | private readonly openMethod; 39 | private timer; 40 | private options; 41 | private timeoutDownload; 42 | private callback; 43 | private openUrl; 44 | private callbackId; 45 | constructor(opt?: any); 46 | /** 47 | * select open method according to the environment and config 48 | */ 49 | _getOpenMethod(): { 50 | preOpen(opt: any): any; 51 | open: (url: string) => void; 52 | } | { 53 | open: () => void; 54 | }; 55 | /** 56 | * launch app 57 | * @param {*} opt 58 | * page:'index', 59 | * param:{}, 60 | * paramMap:{} 61 | * scheme:'', for scheme 62 | * url:'', for link 63 | * launchType:{ 64 | * ios:link/scheme/store 65 | * android:link/scheme/store 66 | * } 67 | * autodemotion 68 | * guideMethod 69 | * useYingyongbao 70 | * updateTipMethod 71 | * clipboardTxt 72 | * pkgs:{android:'',ios:'',yyb:'',store:{...}} 73 | * timeout 是否走超时逻辑,<0表示不走 74 | * landPage 兜底页 75 | * callback 端回调方法 76 | * @param {*} callback: callbackResult 77 | */ 78 | open(opt?: any, callback?: (status: number, detector: any, scheme?: string) => number): void; 79 | /** 80 | * download package 81 | * opt: {android:'',ios:'',yyk:'',landPage} 82 | */ 83 | download(opt?: any): void; 84 | /** 85 | * 检验版本 86 | * @param pageConf {version:''} 87 | */ 88 | _checkVersion(pageConf: any): boolean; 89 | /** 90 | * map param (for different platform) 91 | * @param {*} param 92 | * @param {*} paramMap 93 | */ 94 | _paramMapProcess(param: any, paramMap: any): any; 95 | /** 96 | * generating URL parameters 97 | * @param {*} obj 98 | */ 99 | _stringtifyParams(obj: any): string; 100 | /** 101 | * generating URL 102 | * @param {*} conf 103 | * @param type 'scheme link yyb' 104 | */ 105 | _getUrlFromConf(conf: any, type: string): string; 106 | /** 107 | * callback 108 | * @param status 109 | */ 110 | _callend(status: number): void; 111 | /** 112 | * determine whether or not open successfully 113 | */ 114 | _setTimeEvent(): void; 115 | } 116 | -------------------------------------------------------------------------------- /examples/index.ts: -------------------------------------------------------------------------------- 1 | import { LaunchApp, detector } from '../src/index' 2 | import { isAndroid, inWeixin, inWeibo, supportLink } from '../src/utils'; 3 | import './index.less'; 4 | console.log('detector,', detector); 5 | 6 | function addHandler(element, type, handler) { 7 | if (!element) return; 8 | if (element.addEventListener) { 9 | element.addEventListener(type, handler, false); 10 | } else if (element.attachEvent) { 11 | element.attachEvent('on' + type, handler); 12 | } else { 13 | element['on' + type] = handler; 14 | } 15 | } 16 | 17 | const linkOpen = document.getElementsByClassName('j_open')[0]; 18 | const linkDown = document.getElementsByClassName('j_down')[0]; 19 | 20 | // config 21 | let schemeConfig = { 22 | protocol: 'baiduhaokan', 23 | index: { path: 'home/index' }, 24 | video: { path: 'video/details/' }, 25 | author: { path: 'author/details/', version: '4.7' }, 26 | }; 27 | const haokanConfig = { 28 | inApp: false, // TODO 29 | appVersion: '4.19.5.10', 30 | pkgName: 'com.baidu.haokan', 31 | deeplink: { 32 | scheme: { 33 | android: schemeConfig, 34 | ios: schemeConfig 35 | }, 36 | link: { 37 | index: { url: 'http://hku.baidu.com/h5/share/homeindex' }, 38 | video: { url: 'http://hku.baidu.com/h5/share/detail' }, 39 | author: { url: 'http://hku.baidu.com/h5/share/detailauthor' } 40 | } 41 | }, 42 | pkgs: { 43 | android: 'https://downpack.baidu.com/baidutieba_AndroidPhone_v8.8.8.6(8.8.8.6)_1020584c.apk', 44 | ios: 'https://itunes.apple.com/cn/app/id1322948417?mt=8', 45 | yyb: 'http://a.app.qq.com/o/simple.jsp?pkgname=com.baidu.haokan&ckey=CK1374101624513', 46 | store: { 47 | // other: { 48 | // reg: '', 49 | // scheme: '', 50 | // id: '' 51 | // }, 52 | } 53 | }, 54 | useUniversalLink: true, 55 | useAppLink: supportLink(), 56 | autodemotion: true, 57 | useYingyongbao: inWeixin && isAndroid, 58 | useGuideMethod: inWeibo && isAndroid, 59 | // guideMethod: () => { 60 | // alert('右上角->在浏览器中打开'); 61 | // }, 62 | timeout: 2000, 63 | landPage: 'https://haokan.baidu.com/download' 64 | }; 65 | const lanchHaokan = new LaunchApp(haokanConfig); 66 | 67 | addHandler(linkOpen, 'click', function () { 68 | lanchHaokan.open({ 69 | // useGuideMethod: true, 70 | useYingyongbao: true,//inWeixin && isAndroid, 71 | launchType: { 72 | ios: 'scheme', 73 | android: inWeixin ? 'store' : 'scheme' 74 | }, 75 | page: 'author', 76 | param: { 77 | url_key: '4215764431860909454', 78 | target: 'https%3A%2F%2Fbaijiahao.baidu.com%2Fu%3Fapp_id%3D1611116910625404%26fr%3Dbjhvideo', 79 | }, 80 | // scheme: 'baiduhaokan://my/history?a=b', 81 | // url: 'https://hku.baidu.com/h5/share/s/my/settings', 82 | // scheme:'baiduhaokan://my/setting', 83 | // url:'http://hku.baidu.com/h5/share/detailauthor?url_key=1611116910625404&target=https%3A%2F%2Fbaijiahao.baidu.com%2Fu%3Fapp_id%3D1611116910625404%26fr%3Dbjhvideo', 84 | // guideMethod: () => { 85 | // alert('出去玩opt'); 86 | // }, 87 | timeout: 2000, 88 | // clipboardTxt: '#baiduhaokan://webview/?url_key=https%3a%2f%2feopa.baidu.com%2fpage%2fauthorizeIndex-AcHzJLpa%3fproductid%3d1%26gtype%3d1%26idfrom%3dinside-baiduappbanner&pd=yq&tab=guide&tag=guide&source=yq-0-yq#', 89 | // pkgs: { 90 | // android: 'https://sv.bdstatic.com/static/haokanapk/apk/baiduhaokan1021176d.apk', 91 | // ios: 'https://itunes.apple.com/cn/app/id1322948417?mt=8', 92 | // // yyb: 'http://a.app.qq.com/o/simple.jsp?pkgname=com.baidu.tieba&ckey=CK1374101624513' 93 | // } 94 | }); 95 | // , (s, d, url) => { 96 | // console.log('callbackout', s, d, url); 97 | // s != 1 && copy(url); 98 | // return 2; 99 | // } 100 | 101 | }); 102 | 103 | addHandler(linkDown, 'click', function () { 104 | // lanchHaokan.download(); 105 | lanchHaokan.download({ 106 | android: 'https://downpack.baidu.com/baidutieba_AndroidPhone_v8.8.8.6(8.8.8.6)_1020584c.apk', 107 | ios: 'https://itunes.apple.com/cn/app/id1322948417?mt=8', 108 | yyb: 'http://a.app.qq.com/o/simple.jsp?pkgname=com.baidu.haokan&ckey=CK1374101624513&a=3', 109 | landPage: 'https://haokan.baidu.com/download' 110 | }); 111 | }) 112 | -------------------------------------------------------------------------------- /src/detector.ts: -------------------------------------------------------------------------------- 1 | function typeOf(type: string) { 2 | return function (object: any) { 3 | return Object.prototype.toString.call(object) === "[object " + type + "]"; 4 | }; 5 | } 6 | function each(object: any, factory: any) { 7 | for (let i = 0, l = object.length; i < l; i++) { 8 | if (factory.call(object, object[i], i) === false) { 9 | break; 10 | } 11 | } 12 | } 13 | 14 | export class Detector { 15 | _rules: { os: any[], browser: any[] } 16 | constructor(rules: any) { 17 | this._rules = rules; 18 | } 19 | 20 | _detect(name: string, expression: any, ua: string) { 21 | const expr = typeOf("Function")(expression) ? expression.call(null, ua) : expression; 22 | if (!expr) { return null; } 23 | const info = { 24 | name: name, 25 | version: "0", 26 | codename: "", 27 | }; 28 | if (expr === true) { 29 | return info; 30 | } else if (typeOf("String")(expr)) { 31 | if (ua.indexOf(expr) !== -1) { 32 | return info; 33 | } 34 | } else if (typeOf("Object")(expr)) { 35 | if (expr.hasOwnProperty("version")) { 36 | info.version = expr.version; 37 | } 38 | return info; 39 | } else if (typeOf("RegExp")(expr)) { 40 | const m = expr.exec(ua); 41 | if (m) { 42 | if (m.length >= 2 && m[1]) { 43 | info.version = m[1].replace(/_/g, "."); 44 | } 45 | return info; 46 | } 47 | } 48 | } 49 | 50 | _parseItem(ua: string, patterns: any[], factory: any, detector: any) { 51 | let self = this; 52 | let detected = { 53 | name: "na", 54 | version: "0", 55 | };; 56 | each(patterns, function (pattern: any) { 57 | const d = self._detect(pattern[0], pattern[1], ua); 58 | if (d) { 59 | detected = d; 60 | return false; 61 | } 62 | }); 63 | factory.call(detector, detected.name, detected.version); 64 | } 65 | 66 | /** 67 | * parse ua 68 | * @param ua 69 | */ 70 | parse(ua: string) { 71 | ua = (ua || "").toLowerCase(); 72 | const d: any = {}; 73 | 74 | this._parseItem(ua, this._rules.os, function (name: string, version: string) { 75 | const v = parseFloat(version); 76 | d.os = { 77 | name: name, 78 | version: v, 79 | fullVersion: version, 80 | }; 81 | d.os[name] = v; 82 | }, d); 83 | 84 | this._parseItem(ua, this._rules.browser, function (name: string, version: string) { 85 | let mode = version; 86 | const v = parseFloat(version); 87 | d.browser = { 88 | name: name, 89 | version: v, 90 | fullVersion: version, 91 | mode: parseFloat(mode), 92 | fullMode: mode, 93 | }; 94 | d.browser[name] = v; 95 | }, d); 96 | return d; 97 | } 98 | } 99 | 100 | const OS = [ 101 | ["ios", function (ua: string) { 102 | if (/\bcpu(?: iphone)? os /.test(ua)) { 103 | return /\bcpu(?: iphone)? os ([0-9._]+)/; 104 | } else if (ua.indexOf("iph os ") !== -1) { 105 | return /\biph os ([0-9_]+)/; 106 | } else { 107 | return /\bios\b/; 108 | } 109 | }], 110 | ["android", function (ua: string) { 111 | if (ua.indexOf("android") >= 0) { 112 | return /\bandroid[ \/-]?([0-9.x]+)?/; 113 | } else if (ua.indexOf("adr") >= 0) { 114 | if (ua.indexOf("mqqbrowser") >= 0) { 115 | return /\badr[ ]\(linux; u; ([0-9.]+)?/; 116 | } else { 117 | return /\badr(?:[ ]([0-9.]+))?/; 118 | } 119 | } 120 | return "android"; 121 | //return /\b(?:android|\badr)(?:[\/\- ](?:\(linux; u; )?)?([0-9.x]+)?/; 122 | }], 123 | ["wp", function (ua: string) { 124 | if (ua.indexOf("windows phone ") !== -1) { 125 | return /\bwindows phone (?:os )?([0-9.]+)/; 126 | } else if (ua.indexOf("xblwp") !== -1) { 127 | return /\bxblwp([0-9.]+)/; 128 | } else if (ua.indexOf("zunewp") !== -1) { 129 | return /\bzunewp([0-9.]+)/; 130 | } 131 | return "windows phone"; 132 | }], 133 | ["symbian", /\bsymbian(?:os)?\/([0-9.]+)/], 134 | ["chromeos", /\bcros i686 ([0-9.]+)/], 135 | ["linux", "linux"], 136 | ["windowsce", /\bwindows ce(?: ([0-9.]+))?/] 137 | ]; 138 | const BROWSER = [ 139 | // app 140 | ["micromessenger", /\bmicromessenger\/([\d.]+)/], 141 | ["qq", /\bqq/i], 142 | ["qzone", /qzone\/.*_qz_([\d.]+)/i], 143 | ["qqbrowser", /\bm?qqbrowser\/([0-9.]+)/], 144 | ["tt", /\btencenttraveler ([0-9.]+)/], 145 | ["weibo", /weibo__([0-9.]+)/], 146 | ["uc", function (ua: string) { 147 | if (ua.indexOf("ucbrowser/") >= 0) { 148 | return /\bucbrowser\/([0-9.]+)/; 149 | } else if (ua.indexOf("ubrowser/") >= 0) { 150 | return /\bubrowser\/([0-9.]+)/; 151 | } else if (/\buc\/[0-9]/.test(ua)) { 152 | return /\buc\/([0-9.]+)/; 153 | } else if (ua.indexOf("ucweb") >= 0) { 154 | // `ucweb/2.0` is compony info. 155 | // `UCWEB8.7.2.214/145/800` is browser info. 156 | return /\bucweb([0-9.]+)?/; 157 | } else { 158 | return /\b(?:ucbrowser|uc)\b/; 159 | } 160 | }], 161 | ["360", function (ua: string) { 162 | if (ua.indexOf("360 aphone browser") !== -1) { 163 | return /\b360 aphone browser \(([^\)]+)\)/; 164 | } 165 | return /\b360(?:se|ee|chrome|browser)\b/; 166 | }], 167 | ["baidu", 168 | function (ua) { 169 | let back = 0; 170 | let a; 171 | if (/ baiduboxapp\//i.test(ua)) { 172 | if (a = /([\d+.]+)_(?:diordna|enohpi)_/.exec(ua)) { 173 | a = a[1].split("."); 174 | back = a.reverse().join("."); 175 | } else if ((a = /baiduboxapp\/([\d+.]+)/.exec(ua))) { 176 | back = a[1]; 177 | } 178 | return { 179 | version: back, 180 | }; 181 | } 182 | return false; 183 | }, 184 | ], 185 | ["baidubrowser", /\b(?:ba?idubrowser|baiduhd)[ \/]([0-9.x]+)/], 186 | ["bdminivideo", /bdminivideo\/([0-9.]+)/], 187 | ["sogou", function (ua: string) { 188 | if (ua.indexOf("sogoumobilebrowser") >= 0) { 189 | return /sogoumobilebrowser\/([0-9.]+)/; 190 | } else if (ua.indexOf("sogoumse") >= 0) { 191 | return true; 192 | } 193 | return / se ([0-9.x]+)/; 194 | }], 195 | ["ali-ap", function (ua: string) { 196 | if (ua.indexOf("aliapp") > 0) { 197 | return /\baliapp\(ap\/([0-9.]+)\)/; 198 | } else { 199 | return /\balipayclient\/([0-9.]+)\b/; 200 | } 201 | }], 202 | ["ali-tb", /\baliapp\(tb\/([0-9.]+)\)/], 203 | ["ali-tm", /\baliapp\(tm\/([0-9.]+)\)/], 204 | ["tao", /\btaobrowser\/([0-9.]+)/], 205 | // 厂商 206 | ["mi", /\bmiuibrowser\/([0-9.]+)/], 207 | ["oppo", /\boppobrowser\/([0-9.]+)/], 208 | ["vivo", /\bvivobrowser\/([0-9.]+)/], 209 | ["meizu", /\bmzbrowser\/([0-9.]+)/], 210 | ["nokia", /\bnokiabrowser\/([0-9.]+)/], 211 | // ["huawei", /\bhuaweibrowser\/([0-9.]+)/], 212 | ["samsung", /\bsamsungbrowser\/([0-9.]+)/], 213 | // browser 214 | ["maxthon", /\b(?:maxthon|mxbrowser)(?:[ \/]([0-9.]+))?/], 215 | // Opera 15 之后开始使用 Chromniun 内核,需要放在 Chrome 的规则之前。 216 | ["opera", function (ua: string) { 217 | const re_opera_old = /\bopera.+version\/([0-9.ab]+)/; 218 | const re_opera_new = /\bopr\/([0-9.]+)/; 219 | return re_opera_old.test(ua) ? re_opera_old : re_opera_new; 220 | }], 221 | ["edge", /edge\/([0-9.]+)/], 222 | ["firefox", /\bfirefox\/([0-9.ab]+)/], 223 | ["chrome", / (?:chrome|crios|crmo)\/([0-9.]+)/], 224 | // Android 默认浏览器。该规则需要在 safari 之前。 225 | ["android", function (ua: string) { 226 | if (ua.indexOf("android") === -1) { return; } 227 | return /\bversion\/([0-9.]+(?: beta)?)/; 228 | }], 229 | ["safari", /\bversion\/([0-9.]+(?: beta)?)(?: mobile(?:\/[a-z0-9]+)?)? safari\//], 230 | // 如果不能识别为浏览器则为webview。 231 | ["webview", /\bcpu(?: iphone)? os (?:[0-9._]+).+\bapplewebkit\b/], 232 | ]; 233 | 234 | const detector = new Detector({ 235 | os: OS, 236 | browser: BROWSER 237 | }); 238 | const ua = navigator.userAgent + " " + navigator.appVersion + " " + navigator.vendor; 239 | const d = detector.parse(ua); 240 | export { d as detector, ua } 241 | -------------------------------------------------------------------------------- /lib/detector.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.ua = exports.detector = exports.Detector = void 0; 4 | function typeOf(type) { 5 | return function (object) { 6 | return Object.prototype.toString.call(object) === "[object " + type + "]"; 7 | }; 8 | } 9 | function each(object, factory) { 10 | for (var i = 0, l = object.length; i < l; i++) { 11 | if (factory.call(object, object[i], i) === false) { 12 | break; 13 | } 14 | } 15 | } 16 | var Detector = /** @class */ (function () { 17 | function Detector(rules) { 18 | this._rules = rules; 19 | } 20 | Detector.prototype._detect = function (name, expression, ua) { 21 | var expr = typeOf("Function")(expression) ? expression.call(null, ua) : expression; 22 | if (!expr) { 23 | return null; 24 | } 25 | var info = { 26 | name: name, 27 | version: "0", 28 | codename: "", 29 | }; 30 | if (expr === true) { 31 | return info; 32 | } 33 | else if (typeOf("String")(expr)) { 34 | if (ua.indexOf(expr) !== -1) { 35 | return info; 36 | } 37 | } 38 | else if (typeOf("Object")(expr)) { 39 | if (expr.hasOwnProperty("version")) { 40 | info.version = expr.version; 41 | } 42 | return info; 43 | } 44 | else if (typeOf("RegExp")(expr)) { 45 | var m = expr.exec(ua); 46 | if (m) { 47 | if (m.length >= 2 && m[1]) { 48 | info.version = m[1].replace(/_/g, "."); 49 | } 50 | return info; 51 | } 52 | } 53 | }; 54 | Detector.prototype._parseItem = function (ua, patterns, factory, detector) { 55 | var self = this; 56 | var detected = { 57 | name: "na", 58 | version: "0", 59 | }; 60 | ; 61 | each(patterns, function (pattern) { 62 | var d = self._detect(pattern[0], pattern[1], ua); 63 | if (d) { 64 | detected = d; 65 | return false; 66 | } 67 | }); 68 | factory.call(detector, detected.name, detected.version); 69 | }; 70 | /** 71 | * parse ua 72 | * @param ua 73 | */ 74 | Detector.prototype.parse = function (ua) { 75 | ua = (ua || "").toLowerCase(); 76 | var d = {}; 77 | this._parseItem(ua, this._rules.os, function (name, version) { 78 | var v = parseFloat(version); 79 | d.os = { 80 | name: name, 81 | version: v, 82 | fullVersion: version, 83 | }; 84 | d.os[name] = v; 85 | }, d); 86 | this._parseItem(ua, this._rules.browser, function (name, version) { 87 | var mode = version; 88 | var v = parseFloat(version); 89 | d.browser = { 90 | name: name, 91 | version: v, 92 | fullVersion: version, 93 | mode: parseFloat(mode), 94 | fullMode: mode, 95 | }; 96 | d.browser[name] = v; 97 | }, d); 98 | return d; 99 | }; 100 | return Detector; 101 | }()); 102 | exports.Detector = Detector; 103 | var OS = [ 104 | ["ios", function (ua) { 105 | if (/\bcpu(?: iphone)? os /.test(ua)) { 106 | return /\bcpu(?: iphone)? os ([0-9._]+)/; 107 | } 108 | else if (ua.indexOf("iph os ") !== -1) { 109 | return /\biph os ([0-9_]+)/; 110 | } 111 | else { 112 | return /\bios\b/; 113 | } 114 | }], 115 | ["android", function (ua) { 116 | if (ua.indexOf("android") >= 0) { 117 | return /\bandroid[ \/-]?([0-9.x]+)?/; 118 | } 119 | else if (ua.indexOf("adr") >= 0) { 120 | if (ua.indexOf("mqqbrowser") >= 0) { 121 | return /\badr[ ]\(linux; u; ([0-9.]+)?/; 122 | } 123 | else { 124 | return /\badr(?:[ ]([0-9.]+))?/; 125 | } 126 | } 127 | return "android"; 128 | //return /\b(?:android|\badr)(?:[\/\- ](?:\(linux; u; )?)?([0-9.x]+)?/; 129 | }], 130 | ["wp", function (ua) { 131 | if (ua.indexOf("windows phone ") !== -1) { 132 | return /\bwindows phone (?:os )?([0-9.]+)/; 133 | } 134 | else if (ua.indexOf("xblwp") !== -1) { 135 | return /\bxblwp([0-9.]+)/; 136 | } 137 | else if (ua.indexOf("zunewp") !== -1) { 138 | return /\bzunewp([0-9.]+)/; 139 | } 140 | return "windows phone"; 141 | }], 142 | ["symbian", /\bsymbian(?:os)?\/([0-9.]+)/], 143 | ["chromeos", /\bcros i686 ([0-9.]+)/], 144 | ["linux", "linux"], 145 | ["windowsce", /\bwindows ce(?: ([0-9.]+))?/] 146 | ]; 147 | var BROWSER = [ 148 | // app 149 | ["micromessenger", /\bmicromessenger\/([\d.]+)/], 150 | ["qq", /\bqq/i], 151 | ["qzone", /qzone\/.*_qz_([\d.]+)/i], 152 | ["qqbrowser", /\bm?qqbrowser\/([0-9.]+)/], 153 | ["tt", /\btencenttraveler ([0-9.]+)/], 154 | ["weibo", /weibo__([0-9.]+)/], 155 | ["uc", function (ua) { 156 | if (ua.indexOf("ucbrowser/") >= 0) { 157 | return /\bucbrowser\/([0-9.]+)/; 158 | } 159 | else if (ua.indexOf("ubrowser/") >= 0) { 160 | return /\bubrowser\/([0-9.]+)/; 161 | } 162 | else if (/\buc\/[0-9]/.test(ua)) { 163 | return /\buc\/([0-9.]+)/; 164 | } 165 | else if (ua.indexOf("ucweb") >= 0) { 166 | // `ucweb/2.0` is compony info. 167 | // `UCWEB8.7.2.214/145/800` is browser info. 168 | return /\bucweb([0-9.]+)?/; 169 | } 170 | else { 171 | return /\b(?:ucbrowser|uc)\b/; 172 | } 173 | }], 174 | ["360", function (ua) { 175 | if (ua.indexOf("360 aphone browser") !== -1) { 176 | return /\b360 aphone browser \(([^\)]+)\)/; 177 | } 178 | return /\b360(?:se|ee|chrome|browser)\b/; 179 | }], 180 | ["baidu", function (ua) { 181 | var back = 0; 182 | var a; 183 | if (/ baiduboxapp\//i.test(ua)) { 184 | if (a = /([\d+.]+)_(?:diordna|enohpi)_/.exec(ua)) { 185 | a = a[1].split("."); 186 | back = a.reverse().join("."); 187 | } 188 | else if ((a = /baiduboxapp\/([\d+.]+)/.exec(ua))) { 189 | back = a[1]; 190 | } 191 | return { 192 | version: back, 193 | }; 194 | } 195 | return false; 196 | }, 197 | ], 198 | ["baidubrowser", /\b(?:ba?idubrowser|baiduhd)[ \/]([0-9.x]+)/], 199 | ["bdminivideo", /bdminivideo\/([0-9.]+)/], 200 | ["sogou", function (ua) { 201 | if (ua.indexOf("sogoumobilebrowser") >= 0) { 202 | return /sogoumobilebrowser\/([0-9.]+)/; 203 | } 204 | else if (ua.indexOf("sogoumse") >= 0) { 205 | return true; 206 | } 207 | return / se ([0-9.x]+)/; 208 | }], 209 | ["ali-ap", function (ua) { 210 | if (ua.indexOf("aliapp") > 0) { 211 | return /\baliapp\(ap\/([0-9.]+)\)/; 212 | } 213 | else { 214 | return /\balipayclient\/([0-9.]+)\b/; 215 | } 216 | }], 217 | ["ali-tb", /\baliapp\(tb\/([0-9.]+)\)/], 218 | ["ali-tm", /\baliapp\(tm\/([0-9.]+)\)/], 219 | ["tao", /\btaobrowser\/([0-9.]+)/], 220 | // 厂商 221 | ["mi", /\bmiuibrowser\/([0-9.]+)/], 222 | ["oppo", /\boppobrowser\/([0-9.]+)/], 223 | ["vivo", /\bvivobrowser\/([0-9.]+)/], 224 | ["meizu", /\bmzbrowser\/([0-9.]+)/], 225 | ["nokia", /\bnokiabrowser\/([0-9.]+)/], 226 | // ["huawei", /\bhuaweibrowser\/([0-9.]+)/], 227 | ["samsung", /\bsamsungbrowser\/([0-9.]+)/], 228 | // browser 229 | ["maxthon", /\b(?:maxthon|mxbrowser)(?:[ \/]([0-9.]+))?/], 230 | // Opera 15 之后开始使用 Chromniun 内核,需要放在 Chrome 的规则之前。 231 | ["opera", function (ua) { 232 | var re_opera_old = /\bopera.+version\/([0-9.ab]+)/; 233 | var re_opera_new = /\bopr\/([0-9.]+)/; 234 | return re_opera_old.test(ua) ? re_opera_old : re_opera_new; 235 | }], 236 | ["edge", /edge\/([0-9.]+)/], 237 | ["firefox", /\bfirefox\/([0-9.ab]+)/], 238 | ["chrome", / (?:chrome|crios|crmo)\/([0-9.]+)/], 239 | // Android 默认浏览器。该规则需要在 safari 之前。 240 | ["android", function (ua) { 241 | if (ua.indexOf("android") === -1) { 242 | return; 243 | } 244 | return /\bversion\/([0-9.]+(?: beta)?)/; 245 | }], 246 | ["safari", /\bversion\/([0-9.]+(?: beta)?)(?: mobile(?:\/[a-z0-9]+)?)? safari\//], 247 | // 如果不能识别为浏览器则为webview。 248 | ["webview", /\bcpu(?: iphone)? os (?:[0-9._]+).+\bapplewebkit\b/], 249 | ]; 250 | var detector = new Detector({ 251 | os: OS, 252 | browser: BROWSER 253 | }); 254 | var ua = navigator.userAgent + " " + navigator.appVersion + " " + navigator.vendor; 255 | exports.ua = ua; 256 | var d = detector.parse(ua); 257 | exports.detector = d; 258 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # web-launch-app 2 | 3 | ## Intro 4 | - 唤起App到指定页、通过Scheme调用端能力、下载安装包、到应用商店,同时提供了相关的detector、copy、util等功能 5 | 6 | ## Install 7 | - npm install web-launch-app --save 8 | - https://unpkg.com/web-launch-app@version/lib/wla.min.js (window.WLA) 9 | 10 | ## Usage 11 | 12 | ```javascript 13 | import { 14 | LaunchApp, detector, ua, copy, supportLink, 15 | isAndroid, isIos, inWeixin, inQQ, inWeibo, inBaidu 16 | } from 'web-launch-app'; 17 | 18 | const lanchApp = new LaunchApp(config); 19 | // 简单唤起 20 | lanchApp.open({ 21 | page: 'pagename/action', 22 | param:{ 23 | k: 'v' 24 | } 25 | }); 26 | 27 | // 复杂唤起 28 | lanchApp.open({ 29 | useYingyongbao: inWeixin && isAndroid, 30 | launchType: { 31 | ios: inWeixin ? 'store' : 'link', 32 | android: inWeixin ? 'store' : 'scheme', 33 | }, 34 | autodemotion: false, 35 | scheme: 'app://path?k=v', 36 | url: 'https://link.domain.com/path?k=v', 37 | param:{ 38 | k2: 'v2' 39 | }, 40 | timeout: 2000, 41 | pkgs:{ 42 | android: 'https://cdn.app.com/package/app20190501.apk', 43 | ios: 'https://itunes.apple.com/cn/app/appid123?mt=8', 44 | yyb: 'http://a.app.qq.com/o/simple.jsp?pkgname=com.app.www&ckey=CK123' 45 | } 46 | }, (s, d, url) => { 47 | console.log('callbackout', s, d, url); 48 | s != 1 && copy(url); 49 | return 2; 50 | }); 51 | 52 | // 下载 53 | lanchApp.download(); 54 | lanchApp.download({ 55 | android: 'https://cdn.app.com/package/app20190501.apk', 56 | ios: 'https://itunes.apple.com/cn/app/appid123?mt=8', 57 | yyb: 'http://a.app.qq.com/o/simple.jsp?pkgname=com.app.www&ckey=CK123', 58 | landPage: 'https://haokan.baidu.com/download' 59 | }); 60 | ``` 61 | 62 | ## API 63 | #### export 64 | - LaunchApp:唤起类,核心逻辑所在,提供了open()及download()方法通过不同方案实现唤起App及下载 65 | - copy:复制方法(浏览器安全限制,必须由用户行为触发) 66 | - detector:宿主环境对象(含os及browser信息) 67 | - ua:= navigator.userAgent + " " + navigator.appVersion + " " + navigator.vendor 68 | - isAndroid、isIos、inWeixin、inQQ、inWeibo、inBaidu:字面含义,Boolea值 69 | - supportLink:当前环境是否支持universal link或applink 70 | 71 | #### open(options, callback) 72 | |Param | |Notes| 73 | |------|--------|-----| 74 | |options|useGuideMethod| 是否使用引导提示,优先级高于launchType指定的方案(适用于微信、微博等受限环境),默认false | 75 | | |guideMethod| 引导提示方法,默认蒙层文案提示 | 76 | | |updateTipMethod| 版本升级提示方法,scheme配置指定版本时使用,默认alert提示 | 77 | | |useYingyongbao| launchType为store方案时(应用宝归为应用商店),控制微信中是否走应用宝,默认false | 78 | | |launchType| 【1】link:iOS9+使用universal link,Android6+使用applink,可配置指定link无法使用时自动降级为scheme。【2】scheme:scheme协议,通过唤起超时逻辑进行未唤起处理,同时适用于app内打开页面调用native功能。【3】store:系统应用商店(去应用宝需要指定useYingyongbao为true) | 79 | | |autodemotion| 是否支持link方案不可用时自动降级为scheme方案,(注意参数配置:使用page时要有同page下的link和scheme配置,或同时指定url及scheme参数),默认false | 80 | | |scheme| 指定scheme | 81 | | |callback| scheme的回调方法 | 82 | | |url| 指定link url(iOS的universal link值或Android的applink值) | 83 | | |page| 在config中配置的页面名称或端能力名称(替代scheme和url参数方便维护)| 84 | | |param| scheme或link的参数 | 85 | | |paramMap| 参数映射(适用于iOS与Android同scheme功能但参数名不同的情况,真实世界就是有这么多坑orz)| 86 | | |clipboardTxt| 复制到剪贴板内容(针对未安装或环境受限等唤起中断情况使用,在打开app或下载app后可以通过剪贴板内容进行交互衔接或统计),浏览器安全限制需要用户动作触发才能生效| 87 | | |timeout| scheme/store方案中超时时间,默认2000毫秒,<0表示不走超时逻辑 | 88 | | |landPage| 落地页面(异常或未知情况均会进入此页面) | 89 | | |pkgs| {android:'',ios:'',yyb:'',store:{...}},指定子项会覆盖基础配置 | 90 | |callback|| (s, d, url) => { return 0;} ,launchType为scheme或store方案时默认有超时逻辑,可通过设置tmieout为负值取消或根据callback中的返回值进行超时处理。s表示唤起结果(0失败,1成功,2未知), d为detector,url为最终的scheme或link值。无返回值默认下载apk包或去appstore,1不处理,2落地页,3应用市场(百度春晚活动时引导去应用市场下载分流减压),详见`LaunchApp.callbackResult`。 91 | 92 | 93 | #### download(options) 94 | |Param | |Notes| 95 | |------|--------|-----| 96 | |options ||未指定项使用实例配置中的默认值| 97 | ||yyb| 应用宝地址,在微信中使用 | 98 | ||android| android apk包下载地址 | 99 | ||ios| appstore地址 | 100 | ||landPage| 落地页地址,非iOS/Android平台使用 | 101 | 102 | 103 | ## Config 104 | ```javascript 105 | // 针对各种环境及方案参数有点多,需要使用者了解scheme及link本身的区别 106 | // 虽然config中很多参数可以在使用api时指定,还是建议在实例时全局配置,减少使用api时传参 107 | { 108 | inApp: false, // 是否是app内(在app内使用了指定version的scheme会进行版本检测) 109 | appVersion: '', // 对具体scheme链接进行版本检测时使用 110 | pkgName:'', // 应用商店使用 111 | deeplink:{ 112 | // 配置scheme方案具体页面及参数,生成请求格式为"protocol://path?param¶m" 113 | scheme: { 114 | android: { 115 | // 指定android的scheme协议头 116 | protocol: 'appname', 117 | index: { // 页面名或端能力名(默认请设置为:index) 118 | protocol: 'appname', // 可选,如无会读取上一级protocol,一般不需要配置 119 | path: 'path', 120 | param: {}, // 生成scheme或linkurl时的固定参数 121 | paramMap: {},// 参数映射,解决不同平台参数名不一至情况 122 | version: '4.9.6' // 版本要求 123 | }, 124 | share:{...}, 125 | ... 126 | }, 127 | ios: { 128 | ... 129 | } 130 | }, 131 | // 配置univerlink或applink方案中具体页面url及参数 132 | link: { 133 | pagename: { 134 | url: 'https://link.app.com/p/{forumName}', // 支持占位符 135 | param: { 136 | }, 137 | paramMap: { 138 | }, 139 | version: 0 140 | }, 141 | ... 142 | } 143 | }, 144 | // 下载包配置 145 | pkgs: { 146 | yyb: 'http://a.app.qq.com/o/simple.jsp?pkgname=com.baidu.haokan&ckey=', 147 | android: 'https://cdn.app.com/package/app-default.apk', 148 | ios: 'https://itunes.apple.com/cn/app/id1092031003?mt=8', 149 | store: { // android手机商店匹配,一般不需要配置 150 | android: { 151 | reg: /\(.*Android.*\)/, 152 | scheme: 'market://details?id=packagename' 153 | }, 154 | samsung: { 155 | reg: /\(.*Android.*(SAMSUNG|SM-|GT-).*\)/, 156 | scheme: 'samsungapps://ProductDetail/{id}' 157 | }, 158 | ... 159 | } 160 | }, 161 | useUniversalLink: supportLink(), // 默认根据环境判断 162 | useAppLink: supportLink(), // 默认根据环境判断 163 | autodemotion: false, // 不支持link方案时自动降级为scheme方案,默认false 164 | useYingyongbao: false, // 在微信中store方案时是否走应用宝,默认false 165 | useGuideMethod: false, // 使用guide方案 166 | guideMethod: ()=>{}, // 引导方法,默认蒙层文案提示 167 | updateTipMethod: ()=>{}, // scheme版本检测时升级提示 168 | searchPrefix: '?', // scheme或univerlink生成请求中参数前缀,默认为"?" 169 | timeout: 2000, // scheme/store方案中超时时间,默认2000毫秒,<0表示不走超时逻辑 170 | landPage: '', // 兜底页 171 | } 172 | ``` 173 | 174 | ## Demo 175 | ```javascript 176 | // ------------------------------------------------------------------- 177 | // launch-app.ts(基础文件,通过默认配置减少业务代码开发量,多模块使用建议提npm包) 178 | // ------------------------------------------------------------------- 179 | import { 180 | LaunchApp, copy, detector, ua, supportLink, 181 | isAndroid, isIos, inWeixin, inWeibo 182 | } from 'web-launch-app'; 183 | const inApp = /appname(.*)/.test(ua); 184 | const appVersion = inApp ? /appname\/(\d+(\.\d+)*)/.exec(ua)[1] : ''; 185 | const lanchIns = new LaunchApp({ 186 | inApp: inApp, 187 | appVersion: appVersion, 188 | pkgName: 'com.app.www', 189 | deeplink: { 190 | scheme: { 191 | android: { 192 | protocol: 'app', 193 | index: { 194 | path: '/', 195 | }, 196 | frs: { 197 | protocol: 'app', 198 | path: 'forum/detail', 199 | param: {from:'h5'}, 200 | paramMap: { 201 | forumName: 'kw' 202 | } 203 | } 204 | }, 205 | ios: { 206 | protocol: 'app', 207 | index: { 208 | path: '/', 209 | }, 210 | frs: { 211 | path: 'forum/detail' 212 | } 213 | } 214 | }, 215 | link: { 216 | index: {url: 'https://link.app.com'}, 217 | frs: {url: 'https://link.app.com/p/{forumName}'} 218 | }, 219 | }, 220 | pkgs: { 221 | android: 'https://cdn.app.com/package/app-defult.apk', 222 | ios: 'https://itunes.apple.com/app/apple-store/appid123?pt=328057&ct=MobileQQ_LXY&mt=8', 223 | yyb: 'http://a.app.qq.com/o/simple.jsp?pkgname=com.app.www&ckey=123', 224 | }, 225 | useUniversalLink: supportLink(), 226 | useAppLink: supportLink(), 227 | autodemotion: true, 228 | useYingyongbao: inWeixin && isAndroid, // 2019.7.16发布iOS微信7.0.5支持ulink 229 | useGuideMethod: inWeibo && isAndroid, // 受限情况下使用引导方案 230 | timeout: 2500, 231 | landPage: 'http://www.app.com/download' 232 | }); 233 | 234 | /** 235 | * 唤起app到指定页面 236 | * @param options 237 | * @param callback 238 | */ 239 | export function launch(options?: any, callback?: (status, detector, scheme) => number) { 240 | // pkgs处理 241 | options.pkgs = options.pkgs || {}; 242 | if(options.param && options.param.pkg){ 243 | options.pkgs.android = options.pkgs.android || `https://cdn.app.com/download/app-${pkg}.apk`; 244 | } 245 | if(options.param && options.param.ckey){ 246 | options.pkgs.android = options.pkgs.android || `http://a.app.qq.com/o/simple.jsp?pkgname=com.app.www&ckey=${ckey}`; 247 | } 248 | 249 | // 针对scheme方案处理剪贴板口令 250 | if (options.clipboardTxt === undefined) { 251 | let paramStr = options.param ? stringtifyParams(options.param) : ''; 252 | if (options.scheme) { 253 | options.clipboardTxt = '#' + options.scheme + (paramStr ? ((options.scheme.indexOf('?') > 0 ? '&' : '?') + paramStr) : '') + '#'; 254 | } else if (options.page) { 255 | // schemeConfig为实例化时参数中scheme配置 256 | options.clipboardTxt = '#' + schemeConfig['protocol'] + '://' + schemeConfig[options.page].path + (paramStr ? '?' + paramStr : '') + '#'; 257 | } 258 | } 259 | // TODO 处理唤起&新增统计归因、qq浏览器写入剪贴板延迟等通用性功能... 260 | lanchIns.open(options, callback); 261 | } 262 | 263 | /** 264 | * 唤起app到指定页面(尝试唤起场景,使用link方案,适用于不阻断用户继续去h5页体验场景) 265 | * @param options 266 | * @param callback 267 | */ 268 | export function tryLaunch(options?: any={}, callback?: (status, detector, scheme) => number) { 269 | options.launchType = { 270 | ios: 'link', 271 | android: 'link' 272 | }; 273 | options.autodemotion = false; 274 | launch(options); 275 | } 276 | 277 | /** 278 | * 唤起app到指定页面(强制唤起场景,使用scheme方案) 279 | */ 280 | export function forceLaunch(options?: any={}, callback?: (status, detector, scheme) => number) { 281 | options.launchType = { 282 | ios: 'scheme', 283 | android: 'scheme' 284 | }; 285 | launch(options); 286 | } 287 | 288 | /** 289 | * 唤起app到指定页面(常见场景方案,ios走link,android优先走link不支持走scheme,android微信中走应用宝) 290 | * @param options 291 | * @param callback 292 | */ 293 | export function hotLaunch(options?: any, callback?: (status, detector, scheme) => number) { 294 | options.useGuideMethod = isAndroid && inWeibo; 295 | options.launchType = { 296 | ios: 'link', 297 | android: inWeixin ? 'store' : (supportLink ? 'link' : 'scheme') 298 | }; 299 | options.useYingyongbao = isAndroid && inWeixin; 300 | options.autodemotion = true; 301 | 302 | launch(options, callback); 303 | } 304 | 305 | /** 306 | * 端内H5页面调用端能力 307 | */ 308 | export function invoke(options: any) { 309 | options.launchType = { 310 | ios: 'scheme', 311 | android: 'scheme' 312 | }; 313 | options.timeout = -1; 314 | lanchIns.open(options); 315 | } 316 | 317 | /** 318 | * 下载安装包 319 | * @param opt 320 | */ 321 | export function download(opt) { 322 | // TODO 参数处理... 323 | lanchIns.download(opt); 324 | } 325 | 326 | // ------------------------------------------------------------------- 327 | // demopage.ts(业务代码部分) 328 | // ------------------------------------------------------------------- 329 | import {launch, tryLaunch, forceLaunch, hotLaunch, invoke, download} from 'launch-app' 330 | // 唤起(微博出引导提示,ios微信去appstore,android微信去应用宝,同时指定超时处理及下载包) 331 | launch({ 332 | useGuideMethod: inWeibo, 333 | useYingyongbao: inWeixin && isAndroid, 334 | launchType: { 335 | ios: inWeixin ? 'store' : 'link', 336 | android: inWeixin ? 'store' : 'scheme' 337 | }, 338 | page: 'frs', 339 | param: { 340 | k: 'v', 341 | ckey: '123', 342 | pkg: '20190502', 343 | target: 'https://www.app.com/download', 344 | }, 345 | // scheme:'', 346 | // url:'https://link.app.com/path', 347 | // guideMethod: () => { 348 | // alert('请点击右上角,选择浏览器打开~'); 349 | // }, 350 | timeout: 2000, 351 | // clipboardTxt: '#key#', // launch-app中自动生成 352 | // pkgs: { 353 | // yyb: 'http://a.app.qq.com/o/simple.jsp?pkgname=com.app.www&ckey=123', // 通过ckey参数处理,不传使用默认值 354 | // android: 'https://cdn.app.com/package/app-20190502.apk', // 通过pkg参数处理 355 | // ios: 'https://itunes.apple.com/cn/app/appid123?mt=8' // 不传使用默认值 356 | // } 357 | }, (status, detector, url) => { 358 | console.log('callback', status, detector, url); 359 | // s != 1 && copy(url); 360 | return 0; 361 | }); 362 | 363 | // 尽量使用封装的tryLaunch、forceLaunch、hotLaunch、invoke方法,减少直接使用launch方法,便于扩展 364 | tryLaunch({ 365 | url: 'https://link.domain.com/path?k=v', 366 | param:{ 367 | k2: 'v2' 368 | } 369 | }) 370 | invoke({ 371 | page:'share', 372 | // scheme:'app://share', 373 | param:{ 374 | k:'v' 375 | } 376 | }); 377 | 378 | // 下载 379 | download(); 380 | download({ 381 | pkgs:{ 382 | ios: '', 383 | android: '', 384 | yyb: '', 385 | landPage:'' 386 | } 387 | }); 388 | ``` 389 | 390 | ## Who use? 391 | 好看视频、百度贴吧、伙拍小视频... 392 | - [好看视频的免费增长技术体系建设](https://juejin.im/post/5e778f8b518825494f7e1eac) 393 | -------------------------------------------------------------------------------- /lib/wla.min.js: -------------------------------------------------------------------------------- 1 | window.WLA=function(e){var o={};function n(t){if(o[t])return o[t].exports;var i=o[t]={i:t,l:!1,exports:{}};return e[t].call(i.exports,i,i.exports,n),i.l=!0,i.exports}return n.m=e,n.c=o,n.d=function(e,o,t){n.o(e,o)||Object.defineProperty(e,o,{enumerable:!0,get:t})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,o){if(1&o&&(e=n(e)),8&o)return e;if(4&o&&"object"==typeof e&&e&&e.__esModule)return e;var t=Object.create(null);if(n.r(t),Object.defineProperty(t,"default",{enumerable:!0,value:e}),2&o&&"string"!=typeof e)for(var i in e)n.d(t,i,function(o){return e[o]}.bind(null,i));return t},n.n=function(e){var o=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(o,"a",o),o},n.o=function(e,o){return Object.prototype.hasOwnProperty.call(e,o)},n.p="",n(n.s=8)}([function(e,o,n){"use strict";Object.defineProperty(o,"__esModule",{value:!0});var t=n(1);o.isIos="ios"===t.detector.os.name,o.isAndroid="android"===t.detector.os.name,o.inWeixin="micromessenger"===t.detector.browser.name,o.inQQ="qq"===t.detector.browser.name,o.inWeibo="weibo"===t.detector.browser.name,o.inBaidu="baidu"===t.detector.browser.name,o.enableULink=o.isIos&&t.detector.os.version>=9,o.enableApplink=o.isAndroid&&t.detector.os.version>=6,o.isIOSWithLocationCallSupport=o.isIos&&"safari"==t.detector.browser.name&&t.detector.os.version>=9;var i="chrome"==t.detector.browser.name&&t.detector.browser.version>55,r="samsung"==t.detector.browser.name;o.isAndroidWithLocationCallSupport=o.isAndroid&&(i||r),o.supportLink=function(){var e=!1;if(o.enableApplink)switch(t.detector.browser.name){case"chrome":case"samsung":case"zhousi":e=!0;break;default:e=!1}if(o.enableULink)switch(t.detector.browser.name){case"uc":case"qq":e=!1;break;default:e=!0}return e},o.locationCall=function(e){(top.location||location).href=e},o.iframeCall=function(e){var o=document.createElement("iframe");o.setAttribute("src",e),o.setAttribute("style","display:none"),document.body.appendChild(o),setTimeout((function(){document.body.removeChild(o)}),200)},o.deepMerge=function(e,n){for(var t in n)e[t]=e[t]&&"[object Object]"===e[t].toString()?o.deepMerge(e[t],n[t]):e[t]=n[t];return e}},function(e,o,n){"use strict";function t(e){return function(o){return Object.prototype.toString.call(o)==="[object "+e+"]"}}Object.defineProperty(o,"__esModule",{value:!0});var i=function(){function e(e){this._rules=e}return e.prototype._detect=function(e,o,n){var i=t("Function")(o)?o.call(null,n):o;if(!i)return null;var r={name:e,version:"0",codename:""};if(!0===i)return r;if(t("String")(i)){if(-1!==n.indexOf(i))return r}else{if(t("Object")(i))return i.hasOwnProperty("version")&&(r.version=i.version),r;if(t("RegExp")(i)){var a=i.exec(n);if(a)return a.length>=2&&a[1]&&(r.version=a[1].replace(/_/g,".")),r}}},e.prototype._parseItem=function(e,o,n,t){var i=this,r={name:"na",version:"0"};!function(e,o){for(var n=0,t=e.length;n=0?/\bandroid[ \/-]?([0-9.x]+)?/:e.indexOf("adr")>=0?e.indexOf("mqqbrowser")>=0?/\badr[ ]\(linux; u; ([0-9.]+)?/:/\badr(?:[ ]([0-9.]+))?/:"android"}],["wp",function(e){return-1!==e.indexOf("windows phone ")?/\bwindows phone (?:os )?([0-9.]+)/:-1!==e.indexOf("xblwp")?/\bxblwp([0-9.]+)/:-1!==e.indexOf("zunewp")?/\bzunewp([0-9.]+)/:"windows phone"}],["symbian",/\bsymbian(?:os)?\/([0-9.]+)/],["chromeos",/\bcros i686 ([0-9.]+)/],["linux","linux"],["windowsce",/\bwindows ce(?: ([0-9.]+))?/]],browser:[["micromessenger",/\bmicromessenger\/([\d.]+)/],["qq",/\bqq/i],["qzone",/qzone\/.*_qz_([\d.]+)/i],["qqbrowser",/\bm?qqbrowser\/([0-9.]+)/],["tt",/\btencenttraveler ([0-9.]+)/],["weibo",/weibo__([0-9.]+)/],["uc",function(e){return e.indexOf("ucbrowser/")>=0?/\bucbrowser\/([0-9.]+)/:e.indexOf("ubrowser/")>=0?/\bubrowser\/([0-9.]+)/:/\buc\/[0-9]/.test(e)?/\buc\/([0-9.]+)/:e.indexOf("ucweb")>=0?/\bucweb([0-9.]+)?/:/\b(?:ucbrowser|uc)\b/}],["360",function(e){return-1!==e.indexOf("360 aphone browser")?/\b360 aphone browser \(([^\)]+)\)/:/\b360(?:se|ee|chrome|browser)\b/}],["baidu",function(e){var o,n=0;return!!/ baiduboxapp\//i.test(e)&&((o=/([\d+.]+)_(?:diordna|enohpi)_/.exec(e))?n=(o=o[1].split(".")).reverse().join("."):(o=/baiduboxapp\/([\d+.]+)/.exec(e))&&(n=o[1]),{version:n})}],["baidubrowser",/\b(?:ba?idubrowser|baiduhd)[ \/]([0-9.x]+)/],["bdminivideo",/bdminivideo\/([0-9.]+)/],["sogou",function(e){return e.indexOf("sogoumobilebrowser")>=0?/sogoumobilebrowser\/([0-9.]+)/:e.indexOf("sogoumse")>=0||/ se ([0-9.x]+)/}],["ali-ap",function(e){return e.indexOf("aliapp")>0?/\baliapp\(ap\/([0-9.]+)\)/:/\balipayclient\/([0-9.]+)\b/}],["ali-tb",/\baliapp\(tb\/([0-9.]+)\)/],["ali-tm",/\baliapp\(tm\/([0-9.]+)\)/],["tao",/\btaobrowser\/([0-9.]+)/],["mi",/\bmiuibrowser\/([0-9.]+)/],["oppo",/\boppobrowser\/([0-9.]+)/],["vivo",/\bvivobrowser\/([0-9.]+)/],["meizu",/\bmzbrowser\/([0-9.]+)/],["nokia",/\bnokiabrowser\/([0-9.]+)/],["samsung",/\bsamsungbrowser\/([0-9.]+)/],["maxthon",/\b(?:maxthon|mxbrowser)(?:[ \/]([0-9.]+))?/],["opera",function(e){var o=/\bopera.+version\/([0-9.ab]+)/;return o.test(e)?o:/\bopr\/([0-9.]+)/}],["edge",/edge\/([0-9.]+)/],["firefox",/\bfirefox\/([0-9.ab]+)/],["chrome",/ (?:chrome|crios|crmo)\/([0-9.]+)/],["android",function(e){if(-1!==e.indexOf("android"))return/\bversion\/([0-9.]+(?: beta)?)/}],["safari",/\bversion\/([0-9.]+(?: beta)?)(?: mobile(?:\/[a-z0-9]+)?)? safari\//],["webview",/\bcpu(?: iphone)? os (?:[0-9._]+).+\bapplewebkit\b/]]}),a=navigator.userAgent+" "+navigator.appVersion+" "+navigator.vendor;o.ua=a;var s=r.parse(a);o.detector=s},function(e,o,n){"use strict";function t(e){return(t="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e})(e)}Object.defineProperty(o,"__esModule",{value:!0});var i=n(3);o.copy=i.copy;var r=n(1);o.ua=r.ua,o.detector=r.detector;var a=n(0);o.isAndroid=a.isAndroid,o.isIos=a.isIos,o.inWeibo=a.inWeibo,o.inWeixin=a.inWeixin,o.inQQ=a.inQQ,o.inBaidu=a.inBaidu,o.enableApplink=a.enableApplink,o.enableULink=a.enableULink,o.supportLink=a.supportLink,o.locationCall=a.locationCall,o.iframeCall=a.iframeCall;var s=function(){function e(o){this.callbackId=0;var n=a.deepMerge({},e.defaultConfig);this.configs=a.deepMerge(n,o),this.openMethod=this._getOpenMethod()}return e.prototype._getOpenMethod=function(){var o=e.openChannel,n=o.guide,t=o.link,i=o.scheme,r=o.unknown,s=this.configs,c=s.useGuideMethod,l=s.useUniversalLink,d=s.useAppLink,p=s.autodemotion;return c?n:l||d?p&&(a.isIos&&!a.enableULink||a.isAndroid&&!a.enableApplink)?i:t:a.isIos||a.isAndroid?i:r},e.prototype.open=function(o,n){try{this.options=o,this.callback=n,this.timeoutDownload=o.timeout>=0||this.configs.timeout>=0&&null==o.timeout;var t=e.openChannel,s=t.scheme,c=t.link,l=t.guide,d=t.store,p=t.unknown,u=null,b=!0;if(o.useGuideMethod||null==this.options.useGuideMethod&&this.configs.useGuideMethod)u=l,b=!1;else if(o.launchType){switch(o.launchType[r.detector.os.name]){case"link":u=c,o.autodemotion&&(a.isIos&&!a.enableULink||a.isAndroid&&!a.enableApplink)&&(u=s);break;case"scheme":u=s;break;case"store":u=d,b=!1;break;default:u=p,b=!1}}if(u=u||this.openMethod,"function"==typeof o.callback){o.param=o.param||{};var f="_wla_func_"+ ++this.callbackId;window[f]=function(){o.callback.apply(window,[].slice.call(arguments,0))},o.param.callback=f}else o.callback&&(o.param.callback=n);o.clipboardTxt&&i.copy(o.clipboardTxt),b?(this.openUrl=u.preOpen&&u.preOpen.call(this,o||{}),u.open.call(this,this.openUrl)):u.open.call(this)}catch(e){console.log("launch error:",e),a.locationCall(this.options.landPage||this.configs.landPage)}},e.prototype.download=function(e){var o=a.deepMerge(this.configs.pkgs,e);a.inWeixin?a.locationCall(o.yyb):a.isAndroid?a.locationCall(o.android):a.isIos?a.locationCall(o.ios):a.locationCall(e.landPage||this.configs.landPage)},e.prototype._checkVersion=function(e){for(var o=e.version.trim().split("."),n=this.configs.appVersion.trim().split("."),t=Math.max(o.length,n.length),i=!1,r=0;rs){i=!1;break}if(a0?"&":"?")+n:"");break;case"scheme":if(this.options.scheme)t=this.options.scheme+(n?(this.options.scheme.indexOf("?")>0?"&":this.configs.searchPrefix(r.detector))+n:"");else t=(e.protocol||(a.isIos?this.configs.deeplink.scheme.ios.protocol:this.configs.deeplink.scheme.android.protocol))+"://"+e.path+(n?this.configs.searchPrefix(r.detector)+n:"");break;case"store":t=e.scheme.replace("{id}",e.pkgName||this.configs.pkgName)}return t},e.prototype._callend=function(o){clearTimeout(this.timer);var n=this.callback&&this.callback(o,r.detector,this.openUrl);if(o!=e.openStatus.SUCCESS)switch(n){case e.callbackResult.DO_NOTING:break;case e.callbackResult.OPEN_LAND_PAGE:a.locationCall(this.options.landPage||this.configs.landPage);break;case e.callbackResult.OPEN_APP_STORE:e.openChannel.store.open.call(this,!0);break;default:this.download(this.options.pkgs)}},e.prototype._setTimeEvent=function(){var o=this,n=!1,t="hidden",i="visibilitychange";void 0!==document.hidden?(t="hidden",i="visibilitychange"):void 0!==document.msHidden?(t="msHidden",i="msvisibilitychange"):void 0!==document.webkitHidden&&(t="webkitHidden",i="webkitvisibilitychange");var r=function r(a){n=!0,document[t]||a.hidden||"hidden"==document.visibilityState?o._callend(e.openStatus.SUCCESS):o._callend(e.openStatus.UNKNOWN),document.removeEventListener(i,r),document.removeEventListener("baiduboxappvisibilitychange",r)};document.addEventListener(i,r,!1),document.addEventListener("baiduboxappvisibilitychange",r,!1),this.timer=setTimeout((function(){n||(document.removeEventListener(i,r),document.removeEventListener("baiduboxappvisibilitychange",r),document.hidden||n?o._callend(e.openStatus.UNKNOWN):o._callend(e.openStatus.FAILED),n=!0)}),this.options.timeout||this.configs.timeout)},e.defaultConfig={inApp:!1,appVersion:"",pkgName:"",deeplink:{scheme:{android:{protocol:"protocol",index:{path:"path",param:{},paramMap:{},version:""}},ios:{protocol:"protocol",index:{path:"path",param:{},paramMap:{},version:""}}},link:{index:{url:"",param:{},paramMap:{},version:0}}},pkgs:{yyb:"http://a.app.qq.com/o/simple.jsp?pkgname=com.baidu.haokan&ckey=",ios:"https://itunes.apple.com/cn/app/id1092031003?mt=8",android:"",store:{samsung:{reg:/\(.*Android.*(SAMSUNG|SM-|GT-).*\)/,scheme:"samsungapps://ProductDetail/{id}"},android:{reg:/\(.*Android.*\)/,scheme:"market://details?id={id}"}}},useUniversalLink:a.supportLink(),useAppLink:a.supportLink(),autodemotion:!1,useYingyongbao:!1,useGuideMethod:a.isAndroid&&a.inWeibo,guideMethod:function(){var e=document.createElement("div");e.className="wx-guide-div",e.innerText='点击右上角->选择"在浏览器中打开"',e.style.position="fixed",e.style.top="0",e.style.left="0",e.style.zIndex="1111",e.style.width="100vw",e.style.height="100vh",e.style.textAlign="center",e.style.lineHeight="200px",e.style.color="#fff",e.style.fontSize="20px",e.style.backgroundColor="#000",e.style.opacity="0.7",document.body.appendChild(e),e.onclick=function(){e.remove()}},updateTipMethod:function(){alert("升级App后才能使用此功能!")},searchPrefix:function(e){return"?"},timeout:2e3,landPage:"https://github.com/jawidx/web-launch-app"},e.openChannel={scheme:{preOpen:function(e){var o={};a.isAndroid?o=this.configs.deeplink.scheme.android:a.isIos&&(o=this.configs.deeplink.scheme.ios);var n=o[e.page]||o.index;return n=a.deepMerge(n,e),this.configs.inApp&&n.version&&!this._checkVersion(n)?"":(n.paramMap&&(n.param=this._paramMapProcess(n.param,n.paramMap)),this._getUrlFromConf(n,"scheme"))},open:function(e){e&&(this.timeoutDownload&&this._setTimeEvent(),a.isIOSWithLocationCallSupport||a.isAndroidWithLocationCallSupport?a.locationCall(e):a.iframeCall(e))}},link:{preOpen:function(e){var o=this.configs.deeplink.link,n=o[e.page]||o.index;return(n=a.deepMerge(n,e)).paramMap&&(n.param=this._paramMapProcess(n.param,n.paramMap)),this._getUrlFromConf(n,"link")},open:function(e){a.locationCall(e)}},guide:{open:function(){var e=this.options.guideMethod||this.configs.guideMethod;e&&e(r.detector)}},store:{open:function(e){a.inWeixin||e||!this.timeoutDownload||this._setTimeEvent();var o=a.deepMerge(this.configs.pkgs,this.options.pkgs);if(a.inWeixin&&(this.options.useYingyongbao||null==this.options.useYingyongbao&&this.configs.useYingyongbao))a.locationCall(o.yyb);else if(a.isIos)a.locationCall(o.ios);else if(a.isAndroid){var n=this.configs.pkgs.store,t=void 0,i=void 0;for(var s in n)if((t=n[s])&&t.reg.test(r.ua)){i=this._getUrlFromConf(t,"store"),a.iframeCall(i);break}e&&!i&&a.locationCall(this.options.landPage||this.configs.landPage)}}},unknown:{open:function(){a.locationCall(this.options.landPage||this.configs.landPage)}}},e.openStatus={FAILED:0,SUCCESS:1,UNKNOWN:2},e.callbackResult={DO_NOTING:1,OPEN_LAND_PAGE:2,OPEN_APP_STORE:3},e}();o.LaunchApp=s},function(e,o,n){"use strict";Object.defineProperty(o,"__esModule",{value:!0}),o.copy=function(e,o){var n,t,i=!1;n=(o=o||{}).debug||!1;try{var r="rtl"==document.documentElement.getAttribute("dir");(t=document.createElement("textarea")).style.fontSize="12pt",t.style.border="0",t.style.padding="0",t.style.margin="0",t.style.position="absolute",t.style[r?"right":"left"]="-9999px";var a=window.pageYOffset||document.documentElement.scrollTop;if(t.style.top=a+"px",t.setAttribute("readonly",""),t.value=e,document.body.appendChild(t),function(e){var o;if("SELECT"===e.nodeName)e.focus(),o=e.value;else if("INPUT"===e.nodeName||"TEXTAREA"===e.nodeName){var n=e.hasAttribute("readonly");n||e.setAttribute("readonly",""),e.select(),e.setSelectionRange(0,e.value.length),n||e.removeAttribute("readonly"),o=e.value}else{e.hasAttribute("contenteditable")&&e.focus();var t=window.getSelection(),i=document.createRange();i.selectNodeContents(e),t.removeAllRanges(),t.addRange(i),o=t.toString()}}(t),!document.execCommand("copy"))throw new Error("copy command was unsuccessful");i=!0}catch(o){n&&console.error("unable to copy using execCommand: ",o),n&&console.warn("trying IE specific stuff");try{window.clipboardData.setData("text",e),i=!0}catch(e){n&&console.error("unable to copy using clipboardData: ",e)}}finally{t&&document.body.removeChild(t)}return i}},,,,,function(e,o,n){e.exports=n(2)}]); -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { copy } from './copy' 2 | import { ua, detector } from './detector'; 3 | import { 4 | isAndroid, isIos, 5 | inWeibo, inWeixin, inQQ, inBaidu, 6 | enableApplink, enableULink, supportLink, 7 | locationCall, iframeCall, 8 | isAndroidWithLocationCallSupport, isIOSWithLocationCallSupport, 9 | deepMerge, 10 | } from './utils' 11 | 12 | export { copy, ua, detector } 13 | export { 14 | isAndroid, isIos, 15 | inWeibo, inWeixin, inQQ, inBaidu, 16 | enableApplink, enableULink, supportLink, 17 | locationCall, iframeCall, 18 | } 19 | 20 | export class LaunchApp { 21 | static defaultConfig: any = { 22 | inApp: false, 23 | appVersion: '', 24 | // 应用商店包名 25 | pkgName: '', 26 | deeplink: { 27 | scheme: { 28 | android: { 29 | protocol: 'protocol', 30 | index: { 31 | path: 'path', 32 | param: {}, 33 | paramMap: { 34 | }, 35 | version: '' 36 | } 37 | }, 38 | ios: { 39 | protocol: 'protocol', 40 | index: { 41 | path: 'path', 42 | param: {}, 43 | paramMap: { 44 | }, 45 | version: '' 46 | } 47 | } 48 | }, 49 | link: { 50 | index: { 51 | url: '', 52 | param: { 53 | }, 54 | paramMap: { 55 | }, 56 | version: 0 57 | } 58 | } 59 | }, 60 | pkgs: { 61 | yyb: 'http://a.app.qq.com/o/simple.jsp?pkgname=com.baidu.haokan&ckey=', 62 | ios: 'https://itunes.apple.com/cn/app/id1092031003?mt=8', 63 | android: '', 64 | store: { 65 | samsung: { 66 | reg: /\(.*Android.*(SAMSUNG|SM-|GT-).*\)/, 67 | scheme: 'samsungapps://ProductDetail/{id}' 68 | }, 69 | android: { 70 | reg: /\(.*Android.*\)/, 71 | scheme: 'market://details?id={id}' 72 | } 73 | } 74 | }, 75 | useUniversalLink: supportLink(), 76 | useAppLink: supportLink(), 77 | // 不支持link方案时自动降级为scheme方案 78 | autodemotion: false, 79 | useYingyongbao: false, 80 | // 受限引导 81 | useGuideMethod: isAndroid && inWeibo, 82 | guideMethod: () => { 83 | const div = document.createElement('div'); 84 | div.className = 'wx-guide-div' 85 | div.innerText = '点击右上角->选择"在浏览器中打开"'; 86 | div.style.position = 'fixed' 87 | div.style.top = '0'; 88 | div.style.left = '0'; 89 | div.style.zIndex = '1111'; 90 | div.style.width = '100vw'; 91 | div.style.height = '100vh'; 92 | div.style.textAlign = 'center'; 93 | div.style.lineHeight = '200px' 94 | div.style.color = '#fff'; 95 | div.style.fontSize = '20px' 96 | div.style.backgroundColor = '#000'; 97 | div.style.opacity = '0.7'; 98 | document.body.appendChild(div); 99 | div.onclick = function () { 100 | div.remove(); 101 | } 102 | }, 103 | // 升级提示 104 | updateTipMethod: () => { 105 | alert('升级App后才能使用此功能!'); 106 | }, 107 | // 参数前缀 108 | searchPrefix: (detector: any) => { return '?' }, 109 | // 超时下载, <0表示不使用超时下载 110 | timeout: 2000, 111 | // 兜底页面 112 | landPage: 'https://github.com/jawidx/web-launch-app' 113 | }; 114 | static openChannel = { 115 | scheme: { 116 | preOpen(opt: any) { 117 | let pageMap: any = {}; 118 | if (isAndroid) { 119 | pageMap = this.configs.deeplink.scheme.android; 120 | } else if (isIos) { 121 | pageMap = this.configs.deeplink.scheme.ios; 122 | } 123 | let pageConf = pageMap[opt.page] || pageMap['index']; 124 | pageConf = deepMerge(pageConf, opt); 125 | // 版本检测 126 | if (this.configs.inApp && pageConf.version && !this._checkVersion(pageConf)) { 127 | return ''; 128 | } 129 | if (pageConf.paramMap) { 130 | pageConf.param = this._paramMapProcess(pageConf.param, pageConf.paramMap); 131 | } 132 | return this._getUrlFromConf(pageConf, 'scheme'); 133 | }, 134 | open: function (url: string) { 135 | if (!url) { 136 | return; 137 | } 138 | if (this.timeoutDownload) { 139 | this._setTimeEvent(); 140 | } 141 | if (isIOSWithLocationCallSupport || isAndroidWithLocationCallSupport) { 142 | locationCall(url); 143 | } else { 144 | iframeCall(url); 145 | } 146 | } 147 | }, 148 | link: { 149 | preOpen: function (opt: any) { 150 | const pageMap = this.configs.deeplink.link; 151 | let pageConf = pageMap[opt.page] || pageMap['index']; 152 | pageConf = deepMerge(pageConf, opt); 153 | if (pageConf.paramMap) { 154 | pageConf.param = this._paramMapProcess(pageConf.param, pageConf.paramMap); 155 | } 156 | return this._getUrlFromConf(pageConf, 'link'); 157 | }, 158 | open: function (url: string) { 159 | locationCall(url); 160 | } 161 | }, 162 | guide: { 163 | open: function () { 164 | let func = this.options.guideMethod || this.configs.guideMethod; 165 | func && func(detector); 166 | } 167 | }, 168 | store: { 169 | open: function (noTimeout) { 170 | // 超时处理 171 | if (!inWeixin && !noTimeout && this.timeoutDownload) { 172 | this._setTimeEvent(); 173 | } 174 | let pkgs = deepMerge(this.configs.pkgs, this.options.pkgs); 175 | if (inWeixin && (this.options.useYingyongbao || (this.options.useYingyongbao == undefined && this.configs.useYingyongbao))) { 176 | locationCall(pkgs.yyb); 177 | } else if (isIos) { 178 | locationCall(pkgs.ios); 179 | } else if (isAndroid) { 180 | let store = this.configs.pkgs.store, brand, url; 181 | for (let key in store) { 182 | brand = store[key]; 183 | if (brand && brand.reg.test(ua)) { 184 | url = this._getUrlFromConf(brand, 'store'); 185 | iframeCall(url); 186 | break; 187 | } 188 | } 189 | if (noTimeout && !url) { 190 | locationCall(this.options.landPage || this.configs.landPage); 191 | } 192 | } 193 | // 未匹配到商店会走超时逻辑走兜底 194 | } 195 | }, 196 | unknown: { 197 | open: function () { 198 | locationCall(this.options.landPage || this.configs.landPage); 199 | } 200 | } 201 | }; 202 | 203 | static openStatus = { 204 | FAILED: 0, 205 | SUCCESS: 1, 206 | UNKNOWN: 2 207 | }; 208 | 209 | static callbackResult = { 210 | DO_NOTING: 1, 211 | OPEN_LAND_PAGE: 2, 212 | OPEN_APP_STORE: 3, 213 | } 214 | 215 | // config 216 | private readonly configs: any; 217 | private readonly openMethod: any; 218 | private timer: any; 219 | // param 220 | private options: any; 221 | private timeoutDownload: boolean; 222 | private callback: (status: number, detector: any, scheme: string) => number; 223 | // other 224 | private openUrl: string 225 | private callbackId = 0; 226 | constructor(opt?: any) { 227 | let tmpConfig = deepMerge({}, LaunchApp.defaultConfig); 228 | this.configs = deepMerge(tmpConfig, opt); 229 | this.openMethod = this._getOpenMethod(); 230 | } 231 | 232 | /** 233 | * select open method according to the environment and config 234 | */ 235 | _getOpenMethod() { 236 | let { guide, link, scheme, unknown } = LaunchApp.openChannel; 237 | let { useGuideMethod, useUniversalLink, useAppLink, autodemotion } = this.configs; 238 | 239 | if (useGuideMethod) { 240 | return guide; 241 | } 242 | if (useUniversalLink || useAppLink) { 243 | if (autodemotion && ((isIos && !enableULink) || (isAndroid && !enableApplink))) { 244 | return scheme; 245 | } 246 | return link; 247 | } 248 | if (isIos || isAndroid) { 249 | return scheme; 250 | } 251 | return unknown; 252 | } 253 | 254 | /** 255 | * launch app 256 | * @param {*} opt 257 | * page:'index', 258 | * param:{}, 259 | * paramMap:{} 260 | * scheme:'', for scheme 261 | * url:'', for link 262 | * launchType:{ 263 | * ios:link/scheme/store 264 | * android:link/scheme/store 265 | * } 266 | * autodemotion 267 | * guideMethod 268 | * useYingyongbao 269 | * updateTipMethod 270 | * clipboardTxt 271 | * pkgs:{android:'',ios:'',yyb:'',store:{...}} 272 | * timeout 是否走超时逻辑,<0表示不走 273 | * landPage 兜底页 274 | * callback 端回调方法 275 | * @param {*} callback: callbackResult 276 | */ 277 | open(opt?: any, callback?: (status: number, detector: any, scheme?: string) => number) { 278 | try { 279 | this.options = opt; 280 | this.callback = callback; 281 | this.timeoutDownload = opt.timeout >= 0 || (this.configs.timeout >= 0 && opt.timeout == undefined); 282 | let { scheme, link, guide, store, unknown } = LaunchApp.openChannel; 283 | let tmpOpenMethod = null, needPro = true; 284 | 285 | // 指定调起方案 286 | if (opt.useGuideMethod || (this.options.useGuideMethod == undefined && this.configs.useGuideMethod)) { 287 | tmpOpenMethod = guide; 288 | needPro = false; 289 | } else if (opt.launchType) { 290 | let type = opt.launchType[detector.os.name]; 291 | switch (type) { 292 | case 'link': 293 | tmpOpenMethod = link; 294 | if (opt.autodemotion && ((isIos && !enableULink) || (isAndroid && !enableApplink))) { 295 | tmpOpenMethod = scheme; 296 | } 297 | break; 298 | case 'scheme': 299 | tmpOpenMethod = scheme; 300 | break; 301 | case 'store': 302 | tmpOpenMethod = store; 303 | needPro = false; 304 | break; 305 | default: 306 | tmpOpenMethod = unknown; 307 | needPro = false; 308 | break; 309 | } 310 | } 311 | tmpOpenMethod = tmpOpenMethod || this.openMethod; 312 | 313 | if (typeof opt.callback === 'function') { 314 | opt.param = opt.param || {}; 315 | let funcName = '_wla_func_' + (++this.callbackId); 316 | window[funcName] = function () { 317 | opt.callback.apply(window, ([]).slice.call(arguments, 0)); 318 | }; 319 | opt.param['callback'] = funcName; 320 | } else { 321 | if (opt.callback) { 322 | opt.param['callback'] = callback; 323 | } 324 | } 325 | 326 | opt.clipboardTxt && copy(opt.clipboardTxt); 327 | if (needPro) { 328 | this.openUrl = tmpOpenMethod.preOpen && tmpOpenMethod.preOpen.call(this, opt || {}); 329 | tmpOpenMethod.open.call(this, this.openUrl); 330 | } else { 331 | tmpOpenMethod.open.call(this); 332 | } 333 | } catch (e) { 334 | console.log('launch error:', e); 335 | locationCall(this.options.landPage || this.configs.landPage); 336 | } 337 | } 338 | 339 | /** 340 | * download package 341 | * opt: {android:'',ios:'',yyk:'',landPage} 342 | */ 343 | download(opt?: any) { 344 | let pkgs = deepMerge(this.configs.pkgs, opt); 345 | 346 | if (inWeixin) { 347 | locationCall(pkgs.yyb); 348 | } else if (isAndroid) { 349 | locationCall(pkgs.android); 350 | } else if (isIos) { 351 | locationCall(pkgs.ios); 352 | } else { 353 | locationCall(opt.landPage || this.configs.landPage); 354 | } 355 | } 356 | 357 | /** 358 | * 检验版本 359 | * @param pageConf {version:''} 360 | */ 361 | _checkVersion(pageConf) { 362 | const nums1 = pageConf.version.trim().split('.'); 363 | const nums2 = this.configs.appVersion.trim().split('.'); 364 | const len = Math.max(nums1.length, nums2.length); 365 | let result = false; 366 | for (let i = 0; i < len; i++) { 367 | const n1 = parseInt(nums1[i] || 0); 368 | const n2 = parseInt(nums2[i] || 0); 369 | if (n1 > n2) { 370 | result = false; 371 | break; 372 | } else if (n1 < n2) { 373 | result = true; 374 | break; 375 | } else { 376 | continue; 377 | } 378 | } 379 | 380 | if (!result) { 381 | let func = this.options.updateTipMethod || this.configs.updateTipMethod; 382 | func && func(); 383 | } 384 | return result; 385 | } 386 | 387 | /** 388 | * map param (for different platform) 389 | * @param {*} param 390 | * @param {*} paramMap 391 | */ 392 | _paramMapProcess(param: any, paramMap: any) { 393 | if (!paramMap) { 394 | return param; 395 | } 396 | 397 | let newParam: any = {}; 398 | for (let k in param) { 399 | if (paramMap[k]) { 400 | newParam[paramMap[k]] = param[k]; 401 | } else { 402 | newParam[k] = param[k]; 403 | } 404 | } 405 | 406 | return newParam; 407 | } 408 | 409 | /** 410 | * generating URL parameters 411 | * @param {*} obj 412 | */ 413 | _stringtifyParams(obj: any) { 414 | if (!obj) { 415 | return ''; 416 | } 417 | let str = ''; 418 | for (let k in obj) { 419 | if (!obj.hasOwnProperty(k)) { 420 | continue; 421 | } 422 | if (typeof obj[k] == 'object') { 423 | str += k + '=' + encodeURIComponent(JSON.stringify(obj[k])) + '&'; 424 | } else { 425 | str += k + '=' + encodeURIComponent(obj[k]) + '&'; 426 | } 427 | }; 428 | return str ? str.substr(0, str.length - 1) : str; 429 | } 430 | 431 | /** 432 | * generating URL 433 | * @param {*} conf 434 | * @param type 'scheme link yyb' 435 | */ 436 | _getUrlFromConf(conf: any, type: string) { 437 | let paramStr = conf.param && this._stringtifyParams(conf.param); 438 | let strUrl = ''; 439 | switch (type) { 440 | case 'link': 441 | // 对url进行参数处理 'tieba.baidu.com/p/{pid}' 442 | let url = conf.url; 443 | const placeholders = url.match(/\{.*?\}/g); 444 | placeholders && placeholders.forEach((ph: string, i: number) => { 445 | const key = ph.substring(1, ph.length - 1); 446 | url = url.replace(ph, conf.param[key]); 447 | delete conf.param[key]; 448 | }); 449 | 450 | strUrl = url + (paramStr ? ((url.indexOf('?') > 0 ? '&' : '?') + paramStr) : ''); 451 | break; 452 | case 'scheme': 453 | if (this.options.scheme) { 454 | strUrl = this.options.scheme + (paramStr ? ((this.options.scheme.indexOf('?') > 0 ? '&' : this.configs.searchPrefix(detector)) + paramStr) : ''); 455 | } else { 456 | let protocol = conf.protocol || (isIos ? this.configs.deeplink.scheme.ios.protocol : this.configs.deeplink.scheme.android.protocol); 457 | strUrl = protocol + '://' + conf.path + 458 | (paramStr ? this.configs.searchPrefix(detector) + paramStr : ''); 459 | } 460 | break; 461 | case 'store': 462 | strUrl = conf.scheme.replace('{id}', conf.pkgName || this.configs.pkgName); 463 | break; 464 | } 465 | return strUrl; 466 | } 467 | 468 | /** 469 | * callback 470 | * @param status 471 | */ 472 | _callend(status: number) { 473 | clearTimeout(this.timer); 474 | const backResult = this.callback && this.callback(status, detector, this.openUrl); 475 | if (status != LaunchApp.openStatus.SUCCESS) { 476 | switch (backResult) { 477 | case LaunchApp.callbackResult.DO_NOTING: 478 | break; 479 | case LaunchApp.callbackResult.OPEN_LAND_PAGE: 480 | locationCall(this.options.landPage || this.configs.landPage); 481 | break; 482 | case LaunchApp.callbackResult.OPEN_APP_STORE: 483 | LaunchApp.openChannel.store.open.call(this, true); 484 | break; 485 | default: 486 | this.download(this.options.pkgs); 487 | break; 488 | } 489 | } 490 | } 491 | 492 | /** 493 | * determine whether or not open successfully 494 | */ 495 | _setTimeEvent() { 496 | const self = this; 497 | let haveChange = false; 498 | 499 | let property = 'hidden', eventName = 'visibilitychange'; 500 | if (typeof document.hidden !== 'undefined') { // Opera 12.10 and Firefox 18 and later support 501 | property = 'hidden'; 502 | eventName = 'visibilitychange'; 503 | } else if (typeof (document).msHidden !== 'undefined') { 504 | property = 'msHidden'; 505 | eventName = 'msvisibilitychange'; 506 | } else if (typeof (document).webkitHidden !== 'undefined') { 507 | property = 'webkitHidden'; 508 | eventName = 'webkitvisibilitychange'; 509 | } 510 | 511 | const pageChange = function (e) { 512 | haveChange = true; 513 | if (document[property] || e.hidden || document.visibilityState == 'hidden') { 514 | self._callend(LaunchApp.openStatus.SUCCESS); 515 | } else { 516 | self._callend(LaunchApp.openStatus.UNKNOWN); 517 | } 518 | // document.removeEventListener('pagehide', pageChange); 519 | document.removeEventListener(eventName, pageChange); 520 | document.removeEventListener('baiduboxappvisibilitychange', pageChange); 521 | }; 522 | // window.addEventListener('pagehide', pageChange, false); 523 | document.addEventListener(eventName, pageChange, false); 524 | document.addEventListener('baiduboxappvisibilitychange', pageChange, false); 525 | 526 | 527 | this.timer = setTimeout(function () { 528 | if (haveChange) { 529 | return; 530 | } 531 | // document.removeEventListener('pagehide', pageChange); 532 | document.removeEventListener(eventName, pageChange); 533 | document.removeEventListener('baiduboxappvisibilitychange', pageChange); 534 | 535 | // document.removeEventListener 536 | if (!document.hidden && !haveChange) { 537 | self._callend(LaunchApp.openStatus.FAILED); 538 | } else { 539 | self._callend(LaunchApp.openStatus.UNKNOWN); 540 | } 541 | haveChange = true; 542 | }, this.options.timeout || this.configs.timeout); 543 | } 544 | } 545 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.LaunchApp = exports.iframeCall = exports.locationCall = exports.supportLink = exports.enableULink = exports.enableApplink = exports.inBaidu = exports.inQQ = exports.inWeixin = exports.inWeibo = exports.isIos = exports.isAndroid = exports.detector = exports.ua = exports.copy = void 0; 4 | var copy_1 = require("./copy"); 5 | Object.defineProperty(exports, "copy", { enumerable: true, get: function () { return copy_1.copy; } }); 6 | var detector_1 = require("./detector"); 7 | Object.defineProperty(exports, "ua", { enumerable: true, get: function () { return detector_1.ua; } }); 8 | Object.defineProperty(exports, "detector", { enumerable: true, get: function () { return detector_1.detector; } }); 9 | var utils_1 = require("./utils"); 10 | Object.defineProperty(exports, "isAndroid", { enumerable: true, get: function () { return utils_1.isAndroid; } }); 11 | Object.defineProperty(exports, "isIos", { enumerable: true, get: function () { return utils_1.isIos; } }); 12 | Object.defineProperty(exports, "inWeibo", { enumerable: true, get: function () { return utils_1.inWeibo; } }); 13 | Object.defineProperty(exports, "inWeixin", { enumerable: true, get: function () { return utils_1.inWeixin; } }); 14 | Object.defineProperty(exports, "inQQ", { enumerable: true, get: function () { return utils_1.inQQ; } }); 15 | Object.defineProperty(exports, "inBaidu", { enumerable: true, get: function () { return utils_1.inBaidu; } }); 16 | Object.defineProperty(exports, "enableApplink", { enumerable: true, get: function () { return utils_1.enableApplink; } }); 17 | Object.defineProperty(exports, "enableULink", { enumerable: true, get: function () { return utils_1.enableULink; } }); 18 | Object.defineProperty(exports, "supportLink", { enumerable: true, get: function () { return utils_1.supportLink; } }); 19 | Object.defineProperty(exports, "locationCall", { enumerable: true, get: function () { return utils_1.locationCall; } }); 20 | Object.defineProperty(exports, "iframeCall", { enumerable: true, get: function () { return utils_1.iframeCall; } }); 21 | var LaunchApp = /** @class */ (function () { 22 | function LaunchApp(opt) { 23 | this.callbackId = 0; 24 | var tmpConfig = utils_1.deepMerge({}, LaunchApp.defaultConfig); 25 | this.configs = utils_1.deepMerge(tmpConfig, opt); 26 | this.openMethod = this._getOpenMethod(); 27 | } 28 | /** 29 | * select open method according to the environment and config 30 | */ 31 | LaunchApp.prototype._getOpenMethod = function () { 32 | var _a = LaunchApp.openChannel, guide = _a.guide, link = _a.link, scheme = _a.scheme, unknown = _a.unknown; 33 | var _b = this.configs, useGuideMethod = _b.useGuideMethod, useUniversalLink = _b.useUniversalLink, useAppLink = _b.useAppLink, autodemotion = _b.autodemotion; 34 | if (useGuideMethod) { 35 | return guide; 36 | } 37 | if (useUniversalLink || useAppLink) { 38 | if (autodemotion && ((utils_1.isIos && !utils_1.enableULink) || (utils_1.isAndroid && !utils_1.enableApplink))) { 39 | return scheme; 40 | } 41 | return link; 42 | } 43 | if (utils_1.isIos || utils_1.isAndroid) { 44 | return scheme; 45 | } 46 | return unknown; 47 | }; 48 | /** 49 | * launch app 50 | * @param {*} opt 51 | * page:'index', 52 | * param:{}, 53 | * paramMap:{} 54 | * scheme:'', for scheme 55 | * url:'', for link 56 | * launchType:{ 57 | * ios:link/scheme/store 58 | * android:link/scheme/store 59 | * } 60 | * autodemotion 61 | * guideMethod 62 | * useYingyongbao 63 | * updateTipMethod 64 | * clipboardTxt 65 | * pkgs:{android:'',ios:'',yyb:'',store:{...}} 66 | * timeout 是否走超时逻辑,<0表示不走 67 | * landPage 兜底页 68 | * callback 端回调方法 69 | * @param {*} callback: callbackResult 70 | */ 71 | LaunchApp.prototype.open = function (opt, callback) { 72 | try { 73 | this.options = opt; 74 | this.callback = callback; 75 | this.timeoutDownload = opt.timeout >= 0 || (this.configs.timeout >= 0 && opt.timeout == undefined); 76 | var _a = LaunchApp.openChannel, scheme = _a.scheme, link = _a.link, guide = _a.guide, store = _a.store, unknown = _a.unknown; 77 | var tmpOpenMethod = null, needPro = true; 78 | // 指定调起方案 79 | if (opt.useGuideMethod || (this.options.useGuideMethod == undefined && this.configs.useGuideMethod)) { 80 | tmpOpenMethod = guide; 81 | needPro = false; 82 | } 83 | else if (opt.launchType) { 84 | var type = opt.launchType[detector_1.detector.os.name]; 85 | switch (type) { 86 | case 'link': 87 | tmpOpenMethod = link; 88 | if (opt.autodemotion && ((utils_1.isIos && !utils_1.enableULink) || (utils_1.isAndroid && !utils_1.enableApplink))) { 89 | tmpOpenMethod = scheme; 90 | } 91 | break; 92 | case 'scheme': 93 | tmpOpenMethod = scheme; 94 | break; 95 | case 'store': 96 | tmpOpenMethod = store; 97 | needPro = false; 98 | break; 99 | default: 100 | tmpOpenMethod = unknown; 101 | needPro = false; 102 | break; 103 | } 104 | } 105 | tmpOpenMethod = tmpOpenMethod || this.openMethod; 106 | if (typeof opt.callback === 'function') { 107 | opt.param = opt.param || {}; 108 | var funcName = '_wla_func_' + (++this.callbackId); 109 | window[funcName] = function () { 110 | opt.callback.apply(window, ([]).slice.call(arguments, 0)); 111 | }; 112 | opt.param['callback'] = funcName; 113 | } 114 | else { 115 | if (opt.callback) { 116 | opt.param['callback'] = callback; 117 | } 118 | } 119 | opt.clipboardTxt && copy_1.copy(opt.clipboardTxt); 120 | if (needPro) { 121 | this.openUrl = tmpOpenMethod.preOpen && tmpOpenMethod.preOpen.call(this, opt || {}); 122 | tmpOpenMethod.open.call(this, this.openUrl); 123 | } 124 | else { 125 | tmpOpenMethod.open.call(this); 126 | } 127 | } 128 | catch (e) { 129 | console.log('launch error:', e); 130 | utils_1.locationCall(this.options.landPage || this.configs.landPage); 131 | } 132 | }; 133 | /** 134 | * download package 135 | * opt: {android:'',ios:'',yyk:'',landPage} 136 | */ 137 | LaunchApp.prototype.download = function (opt) { 138 | var pkgs = utils_1.deepMerge(this.configs.pkgs, opt); 139 | if (utils_1.inWeixin) { 140 | utils_1.locationCall(pkgs.yyb); 141 | } 142 | else if (utils_1.isAndroid) { 143 | utils_1.locationCall(pkgs.android); 144 | } 145 | else if (utils_1.isIos) { 146 | utils_1.locationCall(pkgs.ios); 147 | } 148 | else { 149 | utils_1.locationCall(opt.landPage || this.configs.landPage); 150 | } 151 | }; 152 | /** 153 | * 检验版本 154 | * @param pageConf {version:''} 155 | */ 156 | LaunchApp.prototype._checkVersion = function (pageConf) { 157 | var nums1 = pageConf.version.trim().split('.'); 158 | var nums2 = this.configs.appVersion.trim().split('.'); 159 | var len = Math.max(nums1.length, nums2.length); 160 | var result = false; 161 | for (var i = 0; i < len; i++) { 162 | var n1 = parseInt(nums1[i] || 0); 163 | var n2 = parseInt(nums2[i] || 0); 164 | if (n1 > n2) { 165 | result = false; 166 | break; 167 | } 168 | else if (n1 < n2) { 169 | result = true; 170 | break; 171 | } 172 | else { 173 | continue; 174 | } 175 | } 176 | if (!result) { 177 | var func = this.options.updateTipMethod || this.configs.updateTipMethod; 178 | func && func(); 179 | } 180 | return result; 181 | }; 182 | /** 183 | * map param (for different platform) 184 | * @param {*} param 185 | * @param {*} paramMap 186 | */ 187 | LaunchApp.prototype._paramMapProcess = function (param, paramMap) { 188 | if (!paramMap) { 189 | return param; 190 | } 191 | var newParam = {}; 192 | for (var k in param) { 193 | if (paramMap[k]) { 194 | newParam[paramMap[k]] = param[k]; 195 | } 196 | else { 197 | newParam[k] = param[k]; 198 | } 199 | } 200 | return newParam; 201 | }; 202 | /** 203 | * generating URL parameters 204 | * @param {*} obj 205 | */ 206 | LaunchApp.prototype._stringtifyParams = function (obj) { 207 | if (!obj) { 208 | return ''; 209 | } 210 | var str = ''; 211 | for (var k in obj) { 212 | if (!obj.hasOwnProperty(k)) { 213 | continue; 214 | } 215 | if (typeof obj[k] == 'object') { 216 | str += k + '=' + encodeURIComponent(JSON.stringify(obj[k])) + '&'; 217 | } 218 | else { 219 | str += k + '=' + encodeURIComponent(obj[k]) + '&'; 220 | } 221 | } 222 | ; 223 | return str ? str.substr(0, str.length - 1) : str; 224 | }; 225 | /** 226 | * generating URL 227 | * @param {*} conf 228 | * @param type 'scheme link yyb' 229 | */ 230 | LaunchApp.prototype._getUrlFromConf = function (conf, type) { 231 | var paramStr = conf.param && this._stringtifyParams(conf.param); 232 | var strUrl = ''; 233 | switch (type) { 234 | case 'link': 235 | // 对url进行参数处理 'tieba.baidu.com/p/{pid}' 236 | var url_1 = conf.url; 237 | var placeholders = url_1.match(/\{.*?\}/g); 238 | placeholders && placeholders.forEach(function (ph, i) { 239 | var key = ph.substring(1, ph.length - 1); 240 | url_1 = url_1.replace(ph, conf.param[key]); 241 | delete conf.param[key]; 242 | }); 243 | strUrl = url_1 + (paramStr ? ((url_1.indexOf('?') > 0 ? '&' : '?') + paramStr) : ''); 244 | break; 245 | case 'scheme': 246 | if (this.options.scheme) { 247 | strUrl = this.options.scheme + (paramStr ? ((this.options.scheme.indexOf('?') > 0 ? '&' : this.configs.searchPrefix(detector_1.detector)) + paramStr) : ''); 248 | } 249 | else { 250 | var protocol = conf.protocol || (utils_1.isIos ? this.configs.deeplink.scheme.ios.protocol : this.configs.deeplink.scheme.android.protocol); 251 | strUrl = protocol + '://' + conf.path + 252 | (paramStr ? this.configs.searchPrefix(detector_1.detector) + paramStr : ''); 253 | } 254 | break; 255 | case 'store': 256 | strUrl = conf.scheme.replace('{id}', conf.pkgName || this.configs.pkgName); 257 | break; 258 | } 259 | return strUrl; 260 | }; 261 | /** 262 | * callback 263 | * @param status 264 | */ 265 | LaunchApp.prototype._callend = function (status) { 266 | clearTimeout(this.timer); 267 | var backResult = this.callback && this.callback(status, detector_1.detector, this.openUrl); 268 | if (status != LaunchApp.openStatus.SUCCESS) { 269 | switch (backResult) { 270 | case LaunchApp.callbackResult.DO_NOTING: 271 | break; 272 | case LaunchApp.callbackResult.OPEN_LAND_PAGE: 273 | utils_1.locationCall(this.options.landPage || this.configs.landPage); 274 | break; 275 | case LaunchApp.callbackResult.OPEN_APP_STORE: 276 | LaunchApp.openChannel.store.open.call(this, true); 277 | break; 278 | default: 279 | this.download(this.options.pkgs); 280 | break; 281 | } 282 | } 283 | }; 284 | /** 285 | * determine whether or not open successfully 286 | */ 287 | LaunchApp.prototype._setTimeEvent = function () { 288 | var self = this; 289 | var haveChange = false; 290 | var property = 'hidden', eventName = 'visibilitychange'; 291 | if (typeof document.hidden !== 'undefined') { // Opera 12.10 and Firefox 18 and later support 292 | property = 'hidden'; 293 | eventName = 'visibilitychange'; 294 | } 295 | else if (typeof document.msHidden !== 'undefined') { 296 | property = 'msHidden'; 297 | eventName = 'msvisibilitychange'; 298 | } 299 | else if (typeof document.webkitHidden !== 'undefined') { 300 | property = 'webkitHidden'; 301 | eventName = 'webkitvisibilitychange'; 302 | } 303 | var pageChange = function (e) { 304 | haveChange = true; 305 | if (document[property] || e.hidden || document.visibilityState == 'hidden') { 306 | self._callend(LaunchApp.openStatus.SUCCESS); 307 | } 308 | else { 309 | self._callend(LaunchApp.openStatus.UNKNOWN); 310 | } 311 | // document.removeEventListener('pagehide', pageChange); 312 | document.removeEventListener(eventName, pageChange); 313 | document.removeEventListener('baiduboxappvisibilitychange', pageChange); 314 | }; 315 | // window.addEventListener('pagehide', pageChange, false); 316 | document.addEventListener(eventName, pageChange, false); 317 | document.addEventListener('baiduboxappvisibilitychange', pageChange, false); 318 | this.timer = setTimeout(function () { 319 | if (haveChange) { 320 | return; 321 | } 322 | // document.removeEventListener('pagehide', pageChange); 323 | document.removeEventListener(eventName, pageChange); 324 | document.removeEventListener('baiduboxappvisibilitychange', pageChange); 325 | // document.removeEventListener 326 | if (!document.hidden && !haveChange) { 327 | self._callend(LaunchApp.openStatus.FAILED); 328 | } 329 | else { 330 | self._callend(LaunchApp.openStatus.UNKNOWN); 331 | } 332 | haveChange = true; 333 | }, this.options.timeout || this.configs.timeout); 334 | }; 335 | LaunchApp.defaultConfig = { 336 | inApp: false, 337 | appVersion: '', 338 | // 应用商店包名 339 | pkgName: '', 340 | deeplink: { 341 | scheme: { 342 | android: { 343 | protocol: 'protocol', 344 | index: { 345 | path: 'path', 346 | param: {}, 347 | paramMap: {}, 348 | version: '' 349 | } 350 | }, 351 | ios: { 352 | protocol: 'protocol', 353 | index: { 354 | path: 'path', 355 | param: {}, 356 | paramMap: {}, 357 | version: '' 358 | } 359 | } 360 | }, 361 | link: { 362 | index: { 363 | url: '', 364 | param: {}, 365 | paramMap: {}, 366 | version: 0 367 | } 368 | } 369 | }, 370 | pkgs: { 371 | yyb: 'http://a.app.qq.com/o/simple.jsp?pkgname=com.baidu.haokan&ckey=', 372 | ios: 'https://itunes.apple.com/cn/app/id1092031003?mt=8', 373 | android: '', 374 | store: { 375 | samsung: { 376 | reg: /\(.*Android.*(SAMSUNG|SM-|GT-).*\)/, 377 | scheme: 'samsungapps://ProductDetail/{id}' 378 | }, 379 | android: { 380 | reg: /\(.*Android.*\)/, 381 | scheme: 'market://details?id={id}' 382 | } 383 | } 384 | }, 385 | useUniversalLink: utils_1.supportLink(), 386 | useAppLink: utils_1.supportLink(), 387 | // 不支持link方案时自动降级为scheme方案 388 | autodemotion: false, 389 | useYingyongbao: false, 390 | // 受限引导 391 | useGuideMethod: utils_1.isAndroid && utils_1.inWeibo, 392 | guideMethod: function () { 393 | var div = document.createElement('div'); 394 | div.className = 'wx-guide-div'; 395 | div.innerText = '点击右上角->选择"在浏览器中打开"'; 396 | div.style.position = 'fixed'; 397 | div.style.top = '0'; 398 | div.style.left = '0'; 399 | div.style.zIndex = '1111'; 400 | div.style.width = '100vw'; 401 | div.style.height = '100vh'; 402 | div.style.textAlign = 'center'; 403 | div.style.lineHeight = '200px'; 404 | div.style.color = '#fff'; 405 | div.style.fontSize = '20px'; 406 | div.style.backgroundColor = '#000'; 407 | div.style.opacity = '0.7'; 408 | document.body.appendChild(div); 409 | div.onclick = function () { 410 | div.remove(); 411 | }; 412 | }, 413 | // 升级提示 414 | updateTipMethod: function () { 415 | alert('升级App后才能使用此功能!'); 416 | }, 417 | // 参数前缀 418 | searchPrefix: function (detector) { return '?'; }, 419 | // 超时下载, <0表示不使用超时下载 420 | timeout: 2000, 421 | // 兜底页面 422 | landPage: 'https://github.com/jawidx/web-launch-app' 423 | }; 424 | LaunchApp.openChannel = { 425 | scheme: { 426 | preOpen: function (opt) { 427 | var pageMap = {}; 428 | if (utils_1.isAndroid) { 429 | pageMap = this.configs.deeplink.scheme.android; 430 | } 431 | else if (utils_1.isIos) { 432 | pageMap = this.configs.deeplink.scheme.ios; 433 | } 434 | var pageConf = pageMap[opt.page] || pageMap['index']; 435 | pageConf = utils_1.deepMerge(pageConf, opt); 436 | // 版本检测 437 | if (this.configs.inApp && pageConf.version && !this._checkVersion(pageConf)) { 438 | return ''; 439 | } 440 | if (pageConf.paramMap) { 441 | pageConf.param = this._paramMapProcess(pageConf.param, pageConf.paramMap); 442 | } 443 | return this._getUrlFromConf(pageConf, 'scheme'); 444 | }, 445 | open: function (url) { 446 | if (!url) { 447 | return; 448 | } 449 | if (this.timeoutDownload) { 450 | this._setTimeEvent(); 451 | } 452 | if (utils_1.isIOSWithLocationCallSupport || utils_1.isAndroidWithLocationCallSupport) { 453 | utils_1.locationCall(url); 454 | } 455 | else { 456 | utils_1.iframeCall(url); 457 | } 458 | } 459 | }, 460 | link: { 461 | preOpen: function (opt) { 462 | var pageMap = this.configs.deeplink.link; 463 | var pageConf = pageMap[opt.page] || pageMap['index']; 464 | pageConf = utils_1.deepMerge(pageConf, opt); 465 | if (pageConf.paramMap) { 466 | pageConf.param = this._paramMapProcess(pageConf.param, pageConf.paramMap); 467 | } 468 | return this._getUrlFromConf(pageConf, 'link'); 469 | }, 470 | open: function (url) { 471 | utils_1.locationCall(url); 472 | } 473 | }, 474 | guide: { 475 | open: function () { 476 | var func = this.options.guideMethod || this.configs.guideMethod; 477 | func && func(detector_1.detector); 478 | } 479 | }, 480 | store: { 481 | open: function (noTimeout) { 482 | // 超时处理 483 | if (!utils_1.inWeixin && !noTimeout && this.timeoutDownload) { 484 | this._setTimeEvent(); 485 | } 486 | var pkgs = utils_1.deepMerge(this.configs.pkgs, this.options.pkgs); 487 | if (utils_1.inWeixin && (this.options.useYingyongbao || (this.options.useYingyongbao == undefined && this.configs.useYingyongbao))) { 488 | utils_1.locationCall(pkgs.yyb); 489 | } 490 | else if (utils_1.isIos) { 491 | utils_1.locationCall(pkgs.ios); 492 | } 493 | else if (utils_1.isAndroid) { 494 | var store = this.configs.pkgs.store, brand = void 0, url = void 0; 495 | for (var key in store) { 496 | brand = store[key]; 497 | if (brand && brand.reg.test(detector_1.ua)) { 498 | url = this._getUrlFromConf(brand, 'store'); 499 | utils_1.iframeCall(url); 500 | break; 501 | } 502 | } 503 | if (noTimeout && !url) { 504 | utils_1.locationCall(this.options.landPage || this.configs.landPage); 505 | } 506 | } 507 | // 未匹配到商店会走超时逻辑走兜底 508 | } 509 | }, 510 | unknown: { 511 | open: function () { 512 | utils_1.locationCall(this.options.landPage || this.configs.landPage); 513 | } 514 | } 515 | }; 516 | LaunchApp.openStatus = { 517 | FAILED: 0, 518 | SUCCESS: 1, 519 | UNKNOWN: 2 520 | }; 521 | LaunchApp.callbackResult = { 522 | DO_NOTING: 1, 523 | OPEN_LAND_PAGE: 2, 524 | OPEN_APP_STORE: 3, 525 | }; 526 | return LaunchApp; 527 | }()); 528 | exports.LaunchApp = LaunchApp; 529 | --------------------------------------------------------------------------------