Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions androidApp/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,8 @@ android {
applicationId = "com.smjcco.wxpusher"
minSdk = libs.versions.android.minSdk.get().toInt()
targetSdk = libs.versions.android.targetSdk.get().toInt()
versionCode = 10610
versionName = "1.6.10"
versionCode = 10700
versionName = "1.7.0"
//指定产物名称
setProperty("archivesBaseName", "wxpusher-app-v$versionName")

Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
package com.smjcco.wxpusher.common

import com.smjcco.wxpusher.WxpConfig

object WxpConstants {
//协议目录
const val PrivacyUrl: String =
"https://wxpusher.zjiecode.com/admin/agreement/index-argeement.html"
var PrivacyUrl: String = WxpConfig.appFeUrl + "/admin/agreement/index-argeement.html"

//隐私协议入口
const val PrivacyPolicyUrl =
"https://wxpusher.zjiecode.com/admin/agreement/privacy-agreement.html"
var PrivacyPolicyUrl = WxpConfig.appFeUrl + "/admin/agreement/privacy-agreement.html"
}
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ import com.smjcco.wxpusher.page.web.bridge.BridgeContext
import com.smjcco.wxpusher.page.web.bridge.WxpWebBridgeManager
import com.smjcco.wxpusher.utils.DeviceUtils
import com.smjcco.wxpusher.web.AppFeVersionManager
import com.smjcco.wxpusher.web.WxpWebHostPolicy
import com.smjcco.wxpusher.wxapi.WxpWeixinOpenManager
import com.tencent.mm.opensdk.modelmsg.SendMessageToWX

Expand All @@ -54,13 +55,15 @@ open class WxpWebViewFragment : WxpBaseFragment() {
private const val DEVICE_TOKEN_KEY = "deviceToken"
private const val DEVICE_PLATFORM_KEY = "platform"
private const val DEVICE_VERSION_NAME_KEY = "versionName"

// 白名单域名列表
private val WHITELIST_HOSTS = setOf(
"wxpusher.zjiecode.com",
"wxpusher.test.zjiecode.com",
"10.0.0.11",
"127.0.0.1"
const val OPTION_MENU_COPY_LINK = "copy_link"
const val OPTION_MENU_WEIXIN_SHARE = "weixin_share"
const val OPTION_MENU_SHARE = "share"
const val OPTION_MENU_OPEN_BROWSER = "open_browser"
val SUPPORTED_OPTION_MENU_KEYS = setOf(
OPTION_MENU_COPY_LINK,
OPTION_MENU_WEIXIN_SHARE,
OPTION_MENU_SHARE,
OPTION_MENU_OPEN_BROWSER
)

fun newInstance(url: String): WxpWebViewFragment {
Expand Down Expand Up @@ -89,6 +92,9 @@ open class WxpWebViewFragment : WxpBaseFragment() {
private var showThirdPartyBanner = true
private var lastLoadRequest: String? = null
private var webDescription: String? = null
private var optionMenuVisibleOverride: Boolean? = null
private var optionMenuItemsOverride: Set<String>? = null
private var bottomBarVisibleOverride: Boolean? = null
private lateinit var bridgeContext: BridgeContext
private lateinit var webBridgeManager: WxpWebBridgeManager

Expand Down Expand Up @@ -418,7 +424,7 @@ open class WxpWebViewFragment : WxpBaseFragment() {
}

private fun isHostInWhitelist(host: String?): Boolean {
return host != null && WHITELIST_HOSTS.contains(host)
return WxpWebHostPolicy.isHostInWhitelist(host)
}

private fun createRequestWithTokenIfNeeded(url: String): Map<String, String> {
Expand Down Expand Up @@ -461,22 +467,78 @@ open class WxpWebViewFragment : WxpBaseFragment() {
}

private fun updateMenuVisibility(url: String?) {
if (url == null) return
applyWebChromeVisibility(url)
}

private fun applyWebChromeVisibility(url: String?) {
val resolvedUrl = url ?: (webView.url ?: targetUrl)
activity?.invalidateOptionsMenu()
webOptBannerView.visibility = if (resolveBottomBarVisible(resolvedUrl)) View.VISIBLE else View.GONE
}

private fun resolveOptionMenuVisible(url: String?): Boolean {
optionMenuVisibleOverride?.let {
return it
}
if (url.isNullOrBlank()) {
return true
}
val uri = url.toUri()
//浏览器左上角的按钮
if (isHostInWhitelist(uri.host) && uri.path?.contains("wxuser") == true) {
// 订阅管理页面,隐藏菜单
activity?.invalidateOptionsMenu()
return !(isHostInWhitelist(uri.host) && uri.path?.contains("wxuser") == true)
}

private fun resolveBottomBarVisible(url: String?): Boolean {
bottomBarVisibleOverride?.let {
return it
}
if (url.isNullOrBlank()) {
return true
}
val uri = url.toUri()
return !(isHostInWhitelist(uri.host) && uri.path?.contains("app") == true)
}

//下面的导航按钮
if (isHostInWhitelist(uri.host) && uri.path?.contains("app") == true) {
webOptBannerView.visibility = View.GONE
private fun resolveEnabledOptionMenuItems(): Set<String> {
val overrideItems = optionMenuItemsOverride
return if (overrideItems == null) {
SUPPORTED_OPTION_MENU_KEYS
} else {
webOptBannerView.visibility = View.VISIBLE
overrideItems.intersect(SUPPORTED_OPTION_MENU_KEYS)
}
}

private fun optionMenuKeyToItemId(optionKey: String): Int? {
return when (optionKey) {
OPTION_MENU_COPY_LINK -> R.id.action_copy_link
OPTION_MENU_WEIXIN_SHARE -> R.id.action_weixin_share
OPTION_MENU_SHARE -> R.id.action_share
OPTION_MENU_OPEN_BROWSER -> R.id.action_open_browser
else -> null
}
}

fun setOptionMenuVisibleOverride(visible: Boolean?) {
activity?.runOnUiThread {
optionMenuVisibleOverride = visible
val currentUrl = if (::webView.isInitialized) webView.url ?: targetUrl else targetUrl
applyWebChromeVisibility(currentUrl)
}
}

fun setOptionMenuItemsOverride(options: Set<String>?) {
activity?.runOnUiThread {
optionMenuItemsOverride = options?.intersect(SUPPORTED_OPTION_MENU_KEYS)
val currentUrl = if (::webView.isInitialized) webView.url ?: targetUrl else targetUrl
applyWebChromeVisibility(currentUrl)
}
}

fun setBottomBarVisibleOverride(visible: Boolean?) {
activity?.runOnUiThread {
bottomBarVisibleOverride = visible
val currentUrl = if (::webView.isInitialized) webView.url ?: targetUrl else targetUrl
applyWebChromeVisibility(currentUrl)
}
}

private fun updateWebOptionBtnStatus() {
Expand Down Expand Up @@ -523,14 +585,22 @@ open class WxpWebViewFragment : WxpBaseFragment() {
*/
fun onActivityCreateOptionsMenu(menu: Menu?, menuInflater: MenuInflater): Boolean {
val url = webView.url ?: targetUrl
val uri = url.toUri()

// 如果是订阅管理页面,不显示菜单
if (isHostInWhitelist(uri.host) && uri.path?.contains("wxuser") == true) {
if (!resolveOptionMenuVisible(url)) {
return false
}
val enabledOptionKeys = resolveEnabledOptionMenuItems()
if (enabledOptionKeys.isEmpty()) {
return false
}

menuInflater.inflate(R.menu.webview_menu, menu)
val enabledItemIds = enabledOptionKeys.mapNotNull { optionMenuKeyToItemId(it) }.toSet()
val allItemIds = SUPPORTED_OPTION_MENU_KEYS.mapNotNull { optionMenuKeyToItemId(it) }
allItemIds.forEach { itemId ->
if (!enabledItemIds.contains(itemId)) {
menu?.removeItem(itemId)
}
}
return true
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,16 @@ import com.smjcco.wxpusher.base.common.WxpLogUtils
import com.smjcco.wxpusher.page.web.bridge.handlers.GetLoginInfoBridgeHandler
import com.smjcco.wxpusher.page.web.bridge.handlers.OpenUrlBridgeHandler
import com.smjcco.wxpusher.page.web.bridge.handlers.PayRequestBridgeHandler
import com.smjcco.wxpusher.page.web.bridge.handlers.SetWebBottomBarBridgeHandler
import com.smjcco.wxpusher.page.web.bridge.handlers.SetWebOptionMenuBridgeHandler
import com.smjcco.wxpusher.page.web.bridge.handlers.ShowToastBridgeHandler
import com.smjcco.wxpusher.page.web.bridge.handlers.WxpGetEnvBaseUrlBridgeHandler
import com.smjcco.wxpusher.web.WxpWebHostPolicy

class WxpWebBridgeManager(
private val context: BridgeContext,
private val whitelistHosts: Set<String> = DEFAULT_WHITELIST_HOSTS,
private val parser: WxpBridgeMessageParser = WxpBridgeMessageParser()
) {
companion object {
val DEFAULT_WHITELIST_HOSTS = setOf(
"wxpusher.zjiecode.com",
"wxpusher.test.zjiecode.com",
"10.0.0.11",
"127.0.0.1"
)
}

private val emitter = WxpBridgeEmitter(context)
private val handlers = mutableMapOf<String, BridgeHandler>()

Expand All @@ -37,6 +32,10 @@ class WxpWebBridgeManager(
registerHandler("payRequest", requiresWhitelist = true, handler = PayRequestBridgeHandler)
registerHandler("openUrl", requiresWhitelist = false, handler = OpenUrlBridgeHandler)
registerHandler("getLoginInfo", requiresWhitelist = true, handler = GetLoginInfoBridgeHandler)
registerHandler("getEnvBaseUrl", requiresWhitelist = true, handler = WxpGetEnvBaseUrlBridgeHandler)
registerHandler("showToast", requiresWhitelist = true, handler = ShowToastBridgeHandler)
registerHandler("setWebOptionMenu", requiresWhitelist = true, handler = SetWebOptionMenuBridgeHandler)
registerHandler("setWebBottomBar", requiresWhitelist = true, handler = SetWebBottomBarBridgeHandler)
}

fun onMessage(messageJson: String) {
Expand Down Expand Up @@ -77,6 +76,6 @@ class WxpWebBridgeManager(
}

private fun isHostInWhitelist(host: String?): Boolean {
return host != null && whitelistHosts.contains(host)
return WxpWebHostPolicy.isHostInWhitelist(host)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package com.smjcco.wxpusher.page.web.bridge.handlers

import com.smjcco.wxpusher.page.web.WxpWebViewFragment
import com.smjcco.wxpusher.page.web.bridge.BridgeActionHandler
import com.smjcco.wxpusher.page.web.bridge.BridgeContext
import com.smjcco.wxpusher.page.web.bridge.BridgeRequest
import com.smjcco.wxpusher.page.web.bridge.WxpBridgeEmitter

object SetWebBottomBarBridgeHandler : BridgeActionHandler {
override fun handle(request: BridgeRequest, context: BridgeContext, emitter: WxpBridgeEmitter) {
val webFragment = context.fragment as? WxpWebViewFragment
if (webFragment == null) {
emitter.sendBridgeCallback(
callbackId = request.callbackId,
success = false,
error = "web fragment not found"
)
return
}
if (!request.data.containsKey("visible")) {
webFragment.setBottomBarVisibleOverride(null)
emitter.sendBridgeCallback(callbackId = request.callbackId, success = true)
return
}
val visible = parseBooleanValue(request.data["visible"])
if (visible == null) {
emitter.sendBridgeCallback(
callbackId = request.callbackId,
success = false,
error = "visible must be boolean"
)
return
}
webFragment.setBottomBarVisibleOverride(visible)
emitter.sendBridgeCallback(
callbackId = request.callbackId,
success = true
)
}

private fun parseBooleanValue(value: Any?): Boolean? {
return when (value) {
is Boolean -> value
is Number -> value.toInt() != 0
is String -> value.toBooleanStrictOrNull()
else -> null
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package com.smjcco.wxpusher.page.web.bridge.handlers

import com.smjcco.wxpusher.page.web.WxpWebViewFragment
import com.smjcco.wxpusher.page.web.bridge.BridgeActionHandler
import com.smjcco.wxpusher.page.web.bridge.BridgeContext
import com.smjcco.wxpusher.page.web.bridge.BridgeRequest
import com.smjcco.wxpusher.page.web.bridge.WxpBridgeEmitter

object SetWebOptionMenuBridgeHandler : BridgeActionHandler {
override fun handle(request: BridgeRequest, context: BridgeContext, emitter: WxpBridgeEmitter) {
val webFragment = context.fragment as? WxpWebViewFragment
if (webFragment == null) {
emitter.sendBridgeCallback(
callbackId = request.callbackId,
success = false,
error = "web fragment not found"
)
return
}
val hasVisibleField = request.data.containsKey("visible")
val hasOptionsField = request.data.containsKey("options")
if (!hasVisibleField && !hasOptionsField) {
webFragment.setOptionMenuVisibleOverride(null)
webFragment.setOptionMenuItemsOverride(null)
emitter.sendBridgeCallback(callbackId = request.callbackId, success = true)
return
}
if (hasVisibleField) {
val visible = parseBooleanValue(request.data["visible"])
if (visible == null) {
emitter.sendBridgeCallback(
callbackId = request.callbackId,
success = false,
error = "visible must be boolean"
)
return
}
webFragment.setOptionMenuVisibleOverride(visible)
}
if (hasOptionsField) {
val parsedOptions = parseOptionKeys(request.data["options"])
if (parsedOptions == null) {
emitter.sendBridgeCallback(
callbackId = request.callbackId,
success = false,
error = "options must be string array"
)
return
}
val unsupportedOptions = parsedOptions.filterNot { WxpWebViewFragment.SUPPORTED_OPTION_MENU_KEYS.contains(it) }
if (unsupportedOptions.isNotEmpty()) {
emitter.sendBridgeCallback(
callbackId = request.callbackId,
success = false,
error = "unsupported options: ${unsupportedOptions.joinToString(",")}"
)
return
}
webFragment.setOptionMenuItemsOverride(parsedOptions.toSet())
}
emitter.sendBridgeCallback(
callbackId = request.callbackId,
success = true
)
}

private fun parseOptionKeys(value: Any?): List<String>? {
if (value == null) {
return null
}
val listValue = value as? List<*> ?: return null
val parsed = mutableListOf<String>()
for (item in listValue) {
val text = item as? String ?: return null
parsed.add(text)
}
return parsed
}

private fun parseBooleanValue(value: Any?): Boolean? {
return when (value) {
is Boolean -> value
is Number -> value.toInt() != 0
is String -> value.toBooleanStrictOrNull()
else -> null
}
}
}
Loading
Loading