跳转至

准备网页以提供应用程序

为应用服务设置网页

应用用户按照以下步骤安装和运行应用:

 

  1. 点击应用程序服务网页上的应用启动按钮,下载并安装Crossplay Launcher
  2. 运行Crossplay Launcher
  3. Crossplay Launcher会自动下载并安装应用程序。
  4. Crossplay Launcher会自动更新并运行应用程序。

 

以下是使用Crossplay Launcher从网页服务应用的整个过程。

 

 

服务应用程序的网页可以通过以下两种方式提供:

  1. 游戏 (https://withhive.com/)
  2. 由开发该应用程序的公司构建的网页。

本指南提供了示例和指导,说明在使用跨平台启动器从网页服务应用时,网页上需要实现的内容,由应用开发者构建

检查应用服务条件

当用户访问网页时,检查用户的PC环境,例如OS版本是否受支持。

  • 支持的OSWindows 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.