准备网页以提供应用程序
为应用服务设置网页¶
应用用户按照以下步骤安装和运行应用:
- 点击应用程序服务网页上的应用启动按钮,下载并安装Crossplay Launcher。
- 运行Crossplay Launcher。
- Crossplay Launcher会自动下载并安装应用程序。
- Crossplay Launcher会自动更新并运行应用程序。
以下是使用Crossplay Launcher从网页服务应用的整个过程。
服务应用程序的网页可以通过以下两种方式提供:
- 游戏 (https://withhive.com/)
- 由开发该应用程序的公司构建的网页。
本指南提供了示例和指导,说明在使用跨平台启动器从网页服务应用时,网页上需要实现的内容,由应用开发者构建。
检查应用服务条件¶
当用户访问网页时,检查用户的PC环境,例如OS版本是否受支持。
- 支持的OS:Windows 10或更高版本
检查条件后,如果有任何问题,应向用户显示错误弹出窗口。
实现应用启动按钮和跨平台启动器下载弹窗¶
实现一个弹出窗口,用于下载Crossplay Launcher安装文件。弹出窗口必须包含安装Crossplay Launcher的指南。
在网页上点击应用启动按钮会显示弹出窗口。弹出窗口应有一个按钮来下载Crossplay Launcher安装文件。
检查 Crossplay Launcher 安装情况¶
当用户点击网页上的应用启动按钮时,有必要检查用户的 PC 上是否安装了 Crossplay Launcher。在网页上执行以下 JavaScript 代码以检查用户的 PC 上是否安装了 Crossplay Launcher。
JavaScript 代码
!function (b, a) {
"object" == typeof exports && "object" == typeof module
? module.exports = a()
: "function" == typeof define && define.amd
? define("customProtocolCheck", [], a)
: "object" == typeof exports
? exports.customProtocolCheck = a()
: b.customProtocolCheck = a()
}(window, function () {
return function (b) {
var c = {};
function a(d) {
if (c[d])
return c[d].exports;
var e = c[d] = {
i: d,
l: !1,
exports: {}
};
return b[d].call(e.exports, e, e.exports, a),
e.l = !0,
e.exports
}
return a.m = b,
a.c = c,
a.d = function (b, c, d) {
a.o(b, c) || Object.defineProperty(b, c, {
enumerable: !0,
get: d
})
},
a.r = function (a) {
"undefined" != typeof Symbol && Symbol.toStringTag && Object.defineProperty(
a,
Symbol.toStringTag,
{value: "Module"}
),
Object.defineProperty(a, "__esModule", {
value: !0
})
},
a.t = function (b, c) {
if (
1 & c && (b = a(b)),
8 & c || 4 & c && "object" == typeof b && b && b.__esModule
)
return b;
var d = Object.create(null);
if (a.r(d), Object.defineProperty(d, "default", {
enumerable: !0,
value: b
}), 2 & c && "string" != typeof b)
for (var e in b)
a.d(d, e, (function (a) {
return b[a]
}).bind(null, e));
return d
},
a.n = function (c) {
var b = c && c.__esModule
? function () {
return c.default
}
: function () {
return c
};
return a.d(b, "a", b),
b
},
a.o = function (a, b) {
return Object
.prototype
.hasOwnProperty
.call(a, b)
},
a.p = "",
a(a.s = 0)
}({
"./index.js": function (module, exports) {
eval(
'var browser = {\n getUserAgent: function getUserAgent() {\n return window.' +
'navigator.userAgent;\n },\n userAgentContains: function userAgentContains(br' +
'owserName) {\n browserName = browserName.toLowerCase();\n return this.ge' +
'tUserAgent().toLowerCase().indexOf(browserName) > -1;\n },\n isOSX: function' +
' isOSX() {\n return this.userAgentContains("Macintosh");\n },\n isFirefox' +
': function isFirefox() {\n return this.userAgentContains("firefox");\n },' +
'\n isInternetExplorer: function isInternetExplorer() {\n return this.userA' +
'gentContains("trident");\n },\n\n /**\r\n * 检测 IE 11 及更早版本\r\n ' +
'* @return {Boolean} 当 IE 11 及更早版本时返回 true\r\n */\n isIE: functi' +
'on isIE() {\n var ua = this.getUserAgent().toLowerCase(); // 测试值。\n' +
' // 取消注释以检查结果\n // IE 10\n // ua = \'Mozilla/5.0 (com' +
'patible; MSIE 10.0; Windows NT 6.2; Trident/6.0)\';\n // IE 11\n // ua =' +
' \'Mozilla/5.0 (Windows NT 6.3; Trident/7.0; rv:11.0) like Gecko/20100101 Fire' +
'fox/12.0\';\n\n var msie = ua.indexOf("msie");\n\n if (msie > 0) {\n ' +
' // IE 10 或更早版本\n return true;\n }\n\n var trident = ua.indexOf' +
'("trident/");\n\n if (trident > 0) {\n // IE 11\n return true;\n ' +
' } // 其他浏览器\n\n\n return false;\n },\n isEdge: function isEdge(' +
') {\n var ua = this.getUserAgent().toLowerCase(); // 测试值。\n // U' +
'ncomment to check result\n // Edge\n // ua = \'Mozilla/5.0 (Windows NT 1' +
'0.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.135 S' +
'afari/537.36 Edge/12.10240\';\n\n var edge = ua.indexOf("edge");\n\n if ' +
'(edge > 0) {\n return true;\n }\n\n return false;\n },\n isChrome' +
': function isChrome() {\n // IE11 对 window.chrome 返回 undefined\n ' +
'// 而新 Opera 30 对 window.chrome 输出 true\n // 但需要检查 window.opr 是否未定义\n // 新 IE Edge 对 window.chrome 输出 true\n // 如果不是 iOS Chrome 检查\n var isChromium = window.ch' +
'rome;\n var winNav = window.navigator;\n var vendorName = winNav.vendor;' +
'\n var isOpera = typeof window.opr !== "undefined";\n var isIEedge = win' +
'Nav.userAgent.indexOf("Edge") > -1;\n var isIOSChrome = winNav.userAgent.ma' +
'tch("CriOS");\n return isChromium !== null && typeof isChromium !== "undefi' +
'ned" && vendorName === "Google Inc." && isOpera === false && isIEedge === fals' +
'e || isIOSChrome;\n },\n isWhale: function isWhale() {\n // IE11 returns ' +
'undefined for window.chrome\n // and new Opera 30 outputs true for window.c' +
'hrome\n // but needs to check if window.opr is not undefined\n // and ne' +
'w IE Edge outputs to true for window.chrome\n // and if not iOS Chrome chec' +
'k\n var isChromium = window.chrome;\n var winNav = window.navigator;\n ' +
' var vendorName = winNav.vendor;\n var isOpera = typeof window.opr !== "un' +
'defined";\n var isIEedge = winNav.userAgent.indexOf("Edge") > -1;\n var ' +
'isIOSChrome = winNav.userAgent.match("CriOS");\n return isChromium !== null' +
' && typeof isChromium !== "undefined" && vendorName === "NAVER Corp." && isOpe' +
'ra === false && isIEedge === false || isIOSChrome;\n },\n isOpera: function ' +
'isOpera() {\n return this.userAgentContains(" OPR/");\n }\n};\nvar DEFAULT' +
'_CUSTOM_PROTOCOL_FAIL_CALLBACK_TIMEOUT;\n\nvar registerEvent = function regist' +
'erEvent(target, eventType, cb) {\n if (target.addEventListener) {\n target' +
'.addEventListener(eventType, cb);\n return {\n remove: function remove' +
'() {\n target.removeEventListener(eventType, cb);\n }\n };\n }' +
' else {\n target.attachEvent(eventType, cb);\n return {\n remove: f' +
'unction remove() {\n target.detachEvent(eventType, cb);\n }\n }' +
';\n }\n};\n\nvar createHiddenIframe = function createHiddenIframe(target, uri' +
') {\n var iframe = document.createElement("iframe");\n iframe.src = uri;\n ' +
'iframe.id = "hiddenIframe";\n iframe.style.display = "none";\n target.append' +
'Child(iframe);\n return iframe;\n};\n\nvar openUriWithHiddenFrame = function ' +
'openUriWithHiddenFrame(uri, failCb, successCb) {\n var timeout = setTimeout(f' +
'unction () {\n failCb();\n handler.remove();\n }, DEFAULT_CUSTOM_PROTOC' +
'OL_FAIL_CALLBACK_TIMEOUT);\n var iframe = document.querySelector("#hiddenIfra' +
'me");\n\n if (!iframe) {\n iframe = createHiddenIframe(document.body, "abo' +
'ut:blank");\n }\n\n onBlur = function onBlur() {\n clearTimeout(timeout);' +
'\n handler.remove();\n successCb();\n };\n\n var handler = registerEve' +
'nt(window, "blur", onBlur);\n iframe.contentWindow.location.href = uri;\n};\n' +
'\nvar openUriWithTimeoutHack = function openUriWithTimeoutHack(uri, failCb, su' +
'ccessCb) {\n var timeout = setTimeout(function () {\n failCb();\n handl' +
'er.remove();\n }, DEFAULT_CUSTOM_PROTOCOL_FAIL_CALLBACK_TIMEOUT); //handle pa' +
'ge running in an iframe (blur must be registered with top level window)\n\n v' +
'ar target = window;\n\n while (target.parent && target != target.parent) {\n ' +
' target = target.parent;\n }\n\n onBlur = function onBlur() {\n clearTi' +
'meout(timeout);\n handler.remove();\n successCb();\n };\n\n var handle' +
'r = registerEvent(target, "blur", onBlur);\n window.location = uri;\n};\n\nva' +
'r openUriUsingFirefox = function openUriUsingFirefox(uri, failCb, successCb) {' +
'\n var iframe = document.querySelector("#hiddenIframe");\n\n if (!iframe) {' +
'\n iframe = createHiddenIframe(document.body, "about:blank");\n }\n\n try' +
' {\n iframe.contentWindow.location.href = uri;\n successCb();\n } catch' +
' (e) {\n if (e.name == "NS_ERROR_UNKNOWN_PROTOCOL") {\n failCb();\n ' +
' }\n }\n};\n\nvar openUriWithMsLaunchUri = function openUriWithMsLaunchUri(ur' +
'i, failCb, successCb) {\n navigator.msLaunchUri(uri, successCb, failCb);\n};' +
'\n\nvar getBrowserVersion = function getBrowserVersion() {\n var ua = window.' +
'navigator.userAgent;\n var tem,\n M = ua.match(/(opera|chrome|safari|fir' +
'efox|msie|trident(?=\\/))\\/?\\s*(\\d+)/i) || [];\n\n if (/trident/i.test(M[1' +
'])) {\n tem = /\\brv[ :]+(\\d+)/g.exec(ua) || [];\n return parseFloat(te' +
'm[1]) || "";\n }\n\n if (M[1] === "Chrome") {\n tem = ua.match(/\\b(OPR|E' +
'dge)\\/(\\d+)/);\n\n if (tem != null) {\n return parseFloat(tem[2]);\n' +
' }\n }\n\n M = M[2] ? [M[1], M[2]] : [window.navigator.appName, window.na' +
'vigator.appVersion, "-?"];\n if ((tem = ua.match(/version\\/(\\d+)/i)) != nul' +
'l) M.splice(1, 1, tem[1]);\n return parseFloat(M[1]);\n};\n\nvar protocolChec' +
'k = function protocolCheck(uri, failCb, successCb) {\n var timeout = argument' +
's.length > 3 && arguments[3] !== undefined ? arguments[3] : 2000;\n var unsup' +
'portedCb = arguments.length > 4 ? arguments[4] : undefined;\n\n var failCallb' +
'ack = function failCallback() {\n failCb && failCb();\n };\n\n var succes' +
'sCallback = function successCallback() {\n successCb && successCb();\n };' +
'\n\n var unsupportedCallback = function unsupportedCallback() {\n unsuppor' +
'tedCb && unsupportedCb();\n };\n\n var openUri = function openUri() {\n i' +
'f (browser.isFirefox()) {\n var browserVersion = getBrowserVersion();\n\n' +
' if (browserVersion >= 64) {\n openUriWithHiddenFrame(uri, failCal' +
'llback, successCallback);\n } else {\n openUriUsingFirefox(uri, fai' +
'lCallback, successCallback);\n }\n } else if (browser.isWhale()) {\n ' +
' openUriWithTimeoutHack(uri, failCallback, successCallback);\n } else if' +
' (browser.isChrome()) {\n openUriWithTimeoutHack(uri, failCallback, succe' +
'ssCallback);\n } else if (browser.isOSX()) {\n openUriWithHiddenFrame(' +
'uri, failCallback, successCallback);\n } else {\n //not supported, imp' +
'lement please\n unsupportedCallback();\n }\n };\n\n if (timeout) {\n' +
' DEFAULT_CUSTOM_PROTOCOL_FAIL_CALLBACK_TIMEOUT = timeout;\n }\n\n if (bro' +
'wser.isEdge() || browser.isIE()) {\n //for IE and Edge in Win 8 and Win 10' +
'\n openUriWithMsLaunchUri(uri, failCb, successCb);\n } else {\n if (doc' +
'ument.hasFocus()) {\n openUri();\n } else {\n var focusHandler = ' +
'registerEvent(window, "focus", function () {\n focusHandler.remove();\n' +
' openUri();\n });\n }\n }\n};\n\nmodule.exports = protocolChec' +
'k;\n\n//# sourceURL=webpack://customProtocolCheck/./index.js?'
)
},
0: function (module, exports, __webpack_require__) {
eval(
'module.exports = __webpack_require__(/*! /Users/shahv/Viresh/work/rnd/custom-p' +
'rotocol-check/index.js */"./index.js");\n\n\n//# sourceURL=webpack://customPro' +
'tocolCheck/multi_./index.js?'
)
}
})
})
如果用户的PC上未安装Crossplay Launcher,请实现同时安装Crossplay Launcher和应用程序的功能。如果Crossplay Launcher已经安装,请实现运行Crossplay Launcher的功能。
同时安装 Crossplay 启动器和应用¶
在 Crossplay 启动器 的安装过程中,应用也可以自动安装并运行。
实现下载 Crossplay Launcher 安装文件的事件:下载安装文件¶
在实现上述指南以创建应用启动按钮和弹出窗口后,实现用户在弹出窗口上点击下载按钮时下载Crossplay Launcher安装文件的功能,hivecrossplay-fn.qpyou.cn/hivecrossplay/p/w/Installer.exe。
实现下载 Crossplay Launcher 安装文件的事件:自动应用安装¶
当用户点击下载按钮时,实现将 自动安装 URI 复制到用户的 PC 剪贴板的功能。在安装 Crossplay Launcher 后,Crossplay Launcher 会自动在剪贴板中搜索游戏安装 URI。如果找到,它将安装与 URI 对应的应用。如果在剪贴板中未找到游戏安装 URI,则只会在用户的 PC 上安装 Crossplay Launcher。要安装应用,用户需要返回网页并再次点击应用启动按钮。有关更多详细信息,请参阅 运行 Crossplay Launcher。
获取自动安装 URI¶
从Hive 控制台 (沙盒或商业) > 跨平台启动器 > 应用管理 > 下载设置 > 启动器安装/启动 URI获取自动安装URI。
# Example
hivelauncher:?app_id=com.com2us.hivesdk.windows.microsoftstore.global.normal&start_point=9 f387268b8ea6c3e0016c8fd41562ed53d4c38338
将URI复制到剪贴板¶
实现将获得的自动安装URI复制到用户的PC剪贴板空间。
运行跨平台启动器¶
一旦用户的 PC 上安装了 跨平台启动器,当用户在网页上点击应用启动按钮时,执行以下 JavaScript 代码。该 JavaScript 代码使用启动 URI 启动 跨平台启动器,并且 跨平台启动器 会自动安装并运行应用。
获取启动 URI¶
从URI中获取启动Hive 控制台 (沙盒或实时) > 跨平台启动器 > 应用管理 > 下载设置 > 启动器安装/启动 URI。
# Example
hivelauncher:?app_id=com.com2us.hivesdk.windows.microsoftstore.global.normal&start_point=9
实现并运行Javascript代码¶
当用户的PC上安装了Crossplay Launcher时,实现用户在网页上点击应用启动按钮时执行JavaScript代码。
event.preventDefault
? event.preventDefault()
: (event.returnValue = false);
window.customProtocolCheck({
# Implement executing the launch URI.
# The launch URI launches the Crossplay Launcher. The Crossplay Launcher automatically installs and runs the app.
}, function (e) {
console.log("FAIL");
{
# Handling Crossplay Launcher Execution Failure
}
}, function (e) {
console.log("SUCCESS");
}, 2500, function (e) {
console.log("Not Supported");
{
# Handling No Response After 2500ms from Crossplay Launcher Execution Request
}
});
以下是按下“在PC上播放”按钮时的结果示例,这会触发JavaScript代码执行。当Crossplay Launcher运行时,浏览器通知提示是否执行Crossplay Updater,该更新程序会更新Crossplay Launcher本身。
Note
Due to the difference between JAVA's URLEncoder.encode()
and JavaScript's encodeURIComponent()
, when implementing the execution of the app in JAVA, the replaceAll("\+", "%20")
operation is required.