准备网页以提供应用
設定應用服務的網頁¶
應用程式用戶需要遵循以下步驟來安裝和運行應用程式:
- 在提供應用程式的網頁上點擊應用程式啟動按鈕,下載並安裝Crossplay Launcher。
- 運行Crossplay Launcher。
- Crossplay Launcher會自動下載並安裝應用程式。
- Crossplay Launcher會自動更新並運行應用程式。
以下是使用Crossplay Launcher從網頁服務應用程序的整個過程。
應用程式的服務網頁必須由貴公司獨立建置。當建置網頁時,使用 Crossplay Launcher 服務應用程式,請遵循以下指示。以下提供開發人員必須在其網頁上包含的基本實作範例。
檢查應用程式服務條件¶
當用戶訪問網頁時,檢查用戶的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 * Detects IE 11 and older\r\n ' +
'* @return {Boolean} Returns true when IE 11 and older\r\n */\n isIE: functi' +
'on isIE() {\n var ua = this.getUserAgent().toLowerCase(); // Test values.\n' +
' // Uncomment to check result\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 or older\n return true;\n }\n\n var trident = ua.indexOf' +
'("trident/");\n\n if (trident > 0) {\n // IE 11\n return true;\n ' +
' } // other browser\n\n\n return false;\n },\n isEdge: function isEdge(' +
') {\n var ua = this.getUserAgent().toLowerCase(); // Test values.\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 returns undefined for window.chrome\n ' +
'// and new Opera 30 outputs true for window.chrome\n // but needs to check ' +
'if window.opr is not undefined\n // and new IE Edge outputs to true for win' +
'dow.chrome\n // and if not iOS Chrome check\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 ' +
'未定義的 window.chrome\n // 和新的 Opera 30 對 window.c' +
'hrome 輸出 true\n // 但需要檢查 window.opr 是否未定義\n // 和新' +
'的 IE Edge 對 window.chrome 輸出 true\n // 如果不是 iOS Chrome 檢查' +
'\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); //處理在 iframe 中運行的頁面 (blur 必須註冊到頂層窗口)\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' +
'lback, 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?'
)
}
})
})
如果用戶的Crossplay Launcher尚未安裝在PC上,請實現功能以同時安裝Crossplay Launcher和應用程式。如果Crossplay Launcher已經安裝,請實現功能以運行Crossplay Launcher。
同時安裝 Crossplay Launcher 和應用程式¶
在安裝Crossplay Launcher的過程中,應用程式也可以自動安裝並運行。
實作下載 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
(可選) 在執行 URI 中使用自定義參數¶
如果開發者願意,他們可以將自定義參數添加到從 Hive 控制台獲得的遊戲啟動 URI中。以下是添加了自定義參數的啟動 URI 的示例。
# Example
hivelauncher:?app_id=com.com2us.hivesdk.windows.microsoftstore.global.normal&start_point=9&custom_param1=value1&custom_param2=value2
遊戲自動安裝 URI 原本可以從 Hive 控制台獲得。然而,如果您已經在遊戲執行 URI 中添加了自定義參數,您將需要自己創建並使用 自動安裝 URI。下面,使用 SHA1 對執行 URI 進行哈希,然後將此哈希值附加到執行 URI 本身。 之後,使用包含哈希值的整個字符串作為自動安裝 URI。
# Hash the execution URI with custom parameters.
hivelauncher:?app_id=com.com2us.hivesdk.windows.microsoftstore.global.normal&start_point=9&custom_param1=value1&custom_param2=value2
# The hashed value is as follows:
CFF1F0C2FC15E4CF3C06A81A41E1E079B2BD7A9D
# Append the hash value to the execution URI as shown below. Use the entire string as the auto-installation URI.
hivelauncher:?app_id=com.com2us.hivesdk.windows.microsoftstore.global.normal&start_point=9&custom_param1=value1&custom_param2=value2 CFF1F0C2FC15E4CF3C06A81A41E1E079B2BD7A9D
Note
您可以在遊戲客戶端中檢查執行 URI 中輸入的自定義參數值。
實作並執行Javascript代碼¶
當Crossplay Launcher安裝在用戶的PC上時,實現當用戶在網頁上點擊應用啟動按鈕時執行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.