处理上架的问题

1:处理了openurl 拉起问题
2:去掉了http
3 隐私等等
This commit is contained in:
2026-03-05 14:30:07 +08:00
parent 8cc484edcb
commit d8a84dc478
34 changed files with 506 additions and 511 deletions

View File

@@ -11,7 +11,7 @@
#import "KBChatMessage.h"
#import "KBChatPanelView.h"
#import "KBFullAccessManager.h"
#import "KBHostAppLauncher.h"
#import "../Utils/KBExtensionAppLauncher.h"
#import "KBInputBufferManager.h"
#import "KBNetworkManager.h"
#import "KBVM.h"
@@ -118,7 +118,9 @@ static const NSUInteger kKBChatMessageLimit = 6;
if (text.length == 0) {
return;
}
NSLog(@"[KB] 发送消息: %@", text);
#if DEBUG
NSLog(@"[KB] 发送消息 len=%lu", (unsigned long)text.length);
#endif
KBChatMessage *outgoing = [KBChatMessage userMessageWithText:text];
outgoing.avatarURL = [self kb_sharedUserAvatarURL];
@@ -702,11 +704,21 @@ static const NSUInteger kKBChatMessageLimit = 6;
[NSString stringWithFormat:@"%@://recharge?src=keyboard&vipType=svip",
KB_APP_SCHEME];
NSURL *scheme = [NSURL URLWithString:urlString];
BOOL success = [KBHostAppLauncher openHostAppURL:scheme
fromResponder:self.view];
if (!success) {
[KBHUD showInfo:KBLocalized(@"Please open the App to finish purchase")];
}
NSString *ulString = [NSString stringWithFormat:@"%@?src=keyboard&vipType=svip", KB_UL_RECHARGE];
NSURL *ul = [NSURL URLWithString:ulString];
__weak typeof(self) weakSelf = self;
[KBExtensionAppLauncher openPrimaryURL:ul
fallbackURL:scheme
usingInputController:self
source:(self.view ?: (UIResponder *)weakSelf)
completion:^(BOOL success) {
if (success) {
return;
}
dispatch_async(dispatch_get_main_queue(), ^{
[KBHUD showInfo:KBLocalized(@"Please open the App to finish purchase")];
});
}];
}
@end

View File

@@ -12,7 +12,7 @@
#import "KBBackspaceUndoManager.h"
#import "KBFullAccessManager.h"
#import "KBFunctionView.h"
#import "KBHostAppLauncher.h"
#import "../Utils/KBExtensionAppLauncher.h"
#import "KBInputBufferManager.h"
#import "KBKey.h"
#import "KBKeyboardSubscriptionProduct.h"
@@ -766,11 +766,15 @@ static NSString *KBFormatMB(uint64_t bytes) {
// 2) -> App App
//
if (!KBAuthManager.shared.isLoggedIn) {
NSString *schemeStr =
[NSString stringWithFormat:@"%@://login?src=keyboard", KB_APP_SCHEME];
NSURL *scheme = [NSURL URLWithString:schemeStr];
// UIApplication App
BOOL ok = [KBHostAppLauncher openHostAppURL:scheme fromResponder:self.view];
NSURL *ul = [NSURL URLWithString:[NSString stringWithFormat:@"%@?src=keyboard", KB_UL_LOGIN]];
NSURL *scheme =
[NSURL URLWithString:[NSString stringWithFormat:@"%@://login?src=keyboard", KB_APP_SCHEME]];
__weak typeof(self) weakSelf = self;
[KBExtensionAppLauncher openPrimaryURL:ul
fallbackURL:scheme
usingInputController:self
source:(self.view ?: (UIResponder *)weakSelf)
completion:nil];
return;
}
[[KBMaiPointReporter sharedReporter]
@@ -999,11 +1003,15 @@ static NSString *KBFormatMB(uint64_t bytes) {
extra:@{@"action" : @"login_or_recharge"}
completion:nil];
if (!KBAuthManager.shared.isLoggedIn) {
NSString *schemeStr =
[NSString stringWithFormat:@"%@://login?src=keyboard", KB_APP_SCHEME];
NSURL *scheme = [NSURL URLWithString:schemeStr];
// UIApplication App
BOOL ok = [KBHostAppLauncher openHostAppURL:scheme fromResponder:self.view];
NSURL *ul = [NSURL URLWithString:[NSString stringWithFormat:@"%@?src=keyboard", KB_UL_LOGIN]];
NSURL *scheme =
[NSURL URLWithString:[NSString stringWithFormat:@"%@://login?src=keyboard", KB_APP_SCHEME]];
__weak typeof(self) weakSelf = self;
[KBExtensionAppLauncher openPrimaryURL:ul
fallbackURL:scheme
usingInputController:self
source:(self.view ?: (UIResponder *)weakSelf)
completion:nil];
return;
}
NSString *schemeStr =
@@ -1013,13 +1021,20 @@ static NSString *KBFormatMB(uint64_t bytes) {
// if (!ul && !scheme) { return; }
//
// UIApplication App
BOOL ok = [KBHostAppLauncher openHostAppURL:scheme fromResponder:self.view];
if (!ok) {
//
// XXX App /
[KBHUD showInfo:@"请回到桌面手动打开App进行充值"];
}
__weak typeof(self) weakSelf = self;
NSURL *ul = [NSURL URLWithString:[NSString stringWithFormat:@"%@?src=keyboard", KB_UL_RECHARGE]];
[KBExtensionAppLauncher openPrimaryURL:ul
fallbackURL:scheme
usingInputController:self
source:(self.view ?: (UIResponder *)weakSelf)
completion:^(BOOL success) {
if (success) {
return;
}
dispatch_async(dispatch_get_main_queue(), ^{
[KBHUD showInfo:@"当前应用不允许键盘直接唤起App请回到桌面手动打开App进行充值"];
});
}];
}
- (void)functionViewDidRequestSubscription:(KBFunctionView *)functionView {
@@ -1672,11 +1687,20 @@ static NSString *KBFormatMB(uint64_t bytes) {
[NSString stringWithFormat:@"%@://recharge?src=keyboard&vipType=svip",
KB_APP_SCHEME];
NSURL *scheme = [NSURL URLWithString:urlString];
BOOL success = [KBHostAppLauncher openHostAppURL:scheme
fromResponder:self.view];
if (!success) {
[KBHUD showInfo:KBLocalized(@"Please open the App to finish purchase")];
}
NSURL *ul = [NSURL URLWithString:[NSString stringWithFormat:@"%@?src=keyboard&vipType=svip", KB_UL_RECHARGE]];
__weak typeof(self) weakSelf = self;
[KBExtensionAppLauncher openPrimaryURL:ul
fallbackURL:scheme
usingInputController:self
source:(self.view ?: (UIResponder *)weakSelf)
completion:^(BOOL success) {
if (success) {
return;
}
dispatch_async(dispatch_get_main_queue(), ^{
[KBHUD showInfo:KBLocalized(@"Please open the App to finish purchase")];
});
}];
}
#pragma mark - lazy
@@ -1753,11 +1777,21 @@ static NSString *KBFormatMB(uint64_t bytes) {
NSString *urlString = [NSString
stringWithFormat:@"%@://recharge?src=keyboard&%@", KB_APP_SCHEME, query];
NSURL *scheme = [NSURL URLWithString:urlString];
BOOL success = [KBHostAppLauncher openHostAppURL:scheme
fromResponder:self.view];
if (!success) {
[KBHUD showInfo:KBLocalized(@"Please open the App to finish purchase")];
}
NSString *ulString = [NSString stringWithFormat:@"%@?src=keyboard&%@", KB_UL_RECHARGE, query];
NSURL *ul = [NSURL URLWithString:ulString];
__weak typeof(self) weakSelf = self;
[KBExtensionAppLauncher openPrimaryURL:ul
fallbackURL:scheme
usingInputController:self
source:(self.view ?: (UIResponder *)weakSelf)
completion:^(BOOL success) {
if (success) {
return;
}
dispatch_async(dispatch_get_main_queue(), ^{
[KBHUD showInfo:KBLocalized(@"Please open the App to finish purchase")];
});
}];
}
+ (NSString *)kb_urlEncodedString:(NSString *)value {

View File

@@ -13,7 +13,7 @@
#import "KBChatPanelView.h"
#import "KBFunctionView.h"
#import "KBFullAccessManager.h"
#import "KBHostAppLauncher.h"
#import "../Utils/KBExtensionAppLauncher.h"
#import "KBInputBufferManager.h"
#import "KBKey.h"
#import "KBKeyBoardMainView.h"
@@ -55,13 +55,22 @@
#endif
if (mode == KBKeyboardPanelModeFunction && !islogin) {
[KBHUD showInfo:KBLocalized(@"请先登录后使用AI功能")];
NSString *schemeStr =
[NSString stringWithFormat:@"%@://login?src=keyboard", KB_APP_SCHEME];
NSURL *scheme = [NSURL URLWithString:schemeStr];
BOOL ok = [KBHostAppLauncher openHostAppURL:scheme fromResponder:self.view];
if (!ok) {
[KBHUD showInfo:KBLocalized(@"请回到桌面手动打开App登录")];
}
NSURL *ul = [NSURL URLWithString:[NSString stringWithFormat:@"%@?src=keyboard", KB_UL_LOGIN]];
NSURL *scheme =
[NSURL URLWithString:[NSString stringWithFormat:@"%@://login?src=keyboard", KB_APP_SCHEME]];
__weak typeof(self) weakSelf = self;
[KBExtensionAppLauncher openPrimaryURL:ul
fallbackURL:scheme
usingInputController:self
source:(self.view ?: (UIResponder *)weakSelf)
completion:^(BOOL success) {
if (success) {
return;
}
dispatch_async(dispatch_get_main_queue(), ^{
[KBHUD showInfo:KBLocalized(@"请回到桌面手动打开App登录")];
});
}];
return;
}
@@ -617,24 +626,33 @@
extra:@{@"action" : @"login_or_recharge"}
completion:nil];
if (!KBAuthManager.shared.isLoggedIn) {
NSString *schemeStr =
[NSString stringWithFormat:@"%@://login?src=keyboard", KB_APP_SCHEME];
NSURL *scheme = [NSURL URLWithString:schemeStr];
// UIApplication App
BOOL ok = [KBHostAppLauncher openHostAppURL:scheme fromResponder:self.view];
NSURL *ul = [NSURL URLWithString:[NSString stringWithFormat:@"%@?src=keyboard", KB_UL_LOGIN]];
NSURL *scheme =
[NSURL URLWithString:[NSString stringWithFormat:@"%@://login?src=keyboard", KB_APP_SCHEME]];
__weak typeof(self) weakSelf = self;
[KBExtensionAppLauncher openPrimaryURL:ul
fallbackURL:scheme
usingInputController:self
source:(self.view ?: (UIResponder *)weakSelf)
completion:nil];
return;
}
NSString *schemeStr =
[NSString stringWithFormat:@"%@://recharge?src=keyboard", KB_APP_SCHEME];
NSURL *scheme = [NSURL URLWithString:schemeStr];
// UIApplication App
BOOL ok = [KBHostAppLauncher openHostAppURL:scheme fromResponder:self.view];
if (!ok) {
//
// XXX App /
[KBHUD showInfo:@"请回到桌面手动打开App进行充值"];
}
NSURL *ul = [NSURL URLWithString:[NSString stringWithFormat:@"%@?src=keyboard", KB_UL_RECHARGE]];
NSURL *scheme =
[NSURL URLWithString:[NSString stringWithFormat:@"%@://recharge?src=keyboard", KB_APP_SCHEME]];
__weak typeof(self) weakSelf = self;
[KBExtensionAppLauncher openPrimaryURL:ul
fallbackURL:scheme
usingInputController:self
source:(self.view ?: (UIResponder *)weakSelf)
completion:^(BOOL success) {
if (success) {
return;
}
dispatch_async(dispatch_get_main_queue(), ^{
[KBHUD showInfo:@"当前应用不允许键盘直接唤起App请回到桌面手动打开App进行充值"];
});
}];
}
- (void)functionViewDidRequestSubscription:(KBFunctionView *)functionView {

View File

@@ -9,7 +9,7 @@
#import "KBAuthManager.h"
#import "KBFullAccessManager.h"
#import "KBHostAppLauncher.h"
#import "../Utils/KBExtensionAppLauncher.h"
#import "KBKeyboardSubscriptionProduct.h"
#import "KBKeyboardSubscriptionView.h"
@@ -26,11 +26,15 @@
//
// 2) -> App App
if (!KBAuthManager.shared.isLoggedIn) {
NSString *schemeStr =
[NSString stringWithFormat:@"%@://login?src=keyboard", KB_APP_SCHEME];
NSURL *scheme = [NSURL URLWithString:schemeStr];
// UIApplication App
BOOL ok = [KBHostAppLauncher openHostAppURL:scheme fromResponder:self.view];
NSURL *ul = [NSURL URLWithString:[NSString stringWithFormat:@"%@?src=keyboard", KB_UL_LOGIN]];
NSURL *scheme =
[NSURL URLWithString:[NSString stringWithFormat:@"%@://login?src=keyboard", KB_APP_SCHEME]];
__weak typeof(self) weakSelf = self;
[KBExtensionAppLauncher openPrimaryURL:ul
fallbackURL:scheme
usingInputController:self
source:(self.view ?: (UIResponder *)weakSelf)
completion:nil];
return;
}
[self kb_setPanelMode:KBKeyboardPanelModeSubscription animated:YES];
@@ -96,10 +100,21 @@
NSString *urlString = [NSString
stringWithFormat:@"%@://recharge?src=keyboard&%@", KB_APP_SCHEME, query];
NSURL *scheme = [NSURL URLWithString:urlString];
BOOL success = [KBHostAppLauncher openHostAppURL:scheme fromResponder:self.view];
if (!success) {
[KBHUD showInfo:KBLocalized(@"Please open the App to finish purchase")];
}
NSString *ulString = [NSString stringWithFormat:@"%@?src=keyboard&%@", KB_UL_RECHARGE, query];
NSURL *ul = [NSURL URLWithString:ulString];
__weak typeof(self) weakSelf = self;
[KBExtensionAppLauncher openPrimaryURL:ul
fallbackURL:scheme
usingInputController:self
source:(self.view ?: (UIResponder *)weakSelf)
completion:^(BOOL success) {
if (success) {
return;
}
dispatch_async(dispatch_get_main_queue(), ^{
[KBHUD showInfo:KBLocalized(@"Please open the App to finish purchase")];
});
}];
}
+ (NSString *)kb_urlEncodedString:(NSString *)value {

View File

@@ -2,12 +2,11 @@
// KBFullAccessManager.m
//
// 访
// 1) UIInputViewController hasFullAccess API
// 1) 使 UIInputViewController.hasFullAccess API
// 2) Unknown Denied
//
#import "KBFullAccessManager.h"
#import <objc/message.h>
#if __has_include("KBNetworkManager.h")
#import "KBNetworkManager.h"
#endif
@@ -62,7 +61,10 @@ NSNotificationName const KBFullAccessChangedNotification = @"KBFullAccessChanged
Class guideCls = NSClassFromString(@"KBFullAccessGuideView");
if (guideCls && [guideCls respondsToSelector:NSSelectorFromString(@"showInView:")]) {
SEL sel = NSSelectorFromString(@"showInView:");
((void (*)(id, SEL, UIView *))objc_msgSend)(guideCls, sel, parent);
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
[guideCls performSelector:sel withObject:parent];
#pragma clang diagnostic pop
}
#endif
return NO;
@@ -74,13 +76,9 @@ NSNotificationName const KBFullAccessChangedNotification = @"KBFullAccessChanged
- (KBFullAccessState)p_detectFullAccessState {
UIInputViewController *ivc = self.ivc;
if (!ivc) return KBFullAccessStateUnknown;
SEL sel = NSSelectorFromString(@"hasFullAccess");
if ([ivc respondsToSelector:sel]) {
BOOL granted = ((BOOL (*)(id, SEL))objc_msgSend)(ivc, sel);
return granted ? KBFullAccessStateGranted : KBFullAccessStateDenied;
if ([ivc respondsToSelector:@selector(hasFullAccess)]) {
return ivc.hasFullAccess ? KBFullAccessStateGranted : KBFullAccessStateDenied;
}
// Unknown
return KBFullAccessStateUnknown;
}

View File

@@ -225,7 +225,10 @@ static NSString * const kKBStreamSplitToken = @"<SPLIT>";
}
if (payload.length > 0) {
if (self.loggingEnabled) {
NSLog(@"[KBStream] SSE raw payload: %@", payload);
#if DEBUG
NSLog(@"[KBStream] SSE raw payload len=%lu",
(unsigned long)(payload ?: @"").length);
#endif
}
NSString *llmText = nil;
if ([self processLLMChunkPayload:payload output:&llmText]) {
@@ -278,7 +281,10 @@ static NSString * const kKBStreamSplitToken = @"<SPLIT>";
}
if (payload.length > 0) {
if (self.loggingEnabled) {
NSLog(@"[KBStream] SSE raw payload: %@", payload);
#if DEBUG
NSLog(@"[KBStream] SSE raw payload len=%lu",
(unsigned long)(payload ?: @"").length);
#endif
}
NSString *delta = nil;
if ((NSInteger)payload.length >= self.deliveredCharCount) {

View File

@@ -6,6 +6,7 @@
//
#import "NetworkStreamHandler.h"
#import <Security/Security.h>
@interface NetworkStreamHandler ()
@@ -243,8 +244,26 @@ didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
// SSL
if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
NSURLCredential *credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
completionHandler(NSURLSessionAuthChallengeUseCredential, credential);
SecTrustRef trust = challenge.protectionSpace.serverTrust;
if (!trust) {
completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, nil);
return;
}
BOOL ok = NO;
if (@available(iOS 13.0, *)) {
ok = SecTrustEvaluateWithError(trust, nil);
} else {
SecTrustResultType result = kSecTrustResultInvalid;
OSStatus status = SecTrustEvaluate(trust, &result);
ok = (status == errSecSuccess) &&
(result == kSecTrustResultUnspecified || result == kSecTrustResultProceed);
}
if (ok) {
NSURLCredential *credential = [NSURLCredential credentialForTrust:trust];
completionHandler(NSURLSessionAuthChallengeUseCredential, credential);
} else {
completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, nil);
}
} else {
completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, nil);
}

View File

@@ -31,10 +31,18 @@
#define KB_UL_LOGIN KB_UL_BASE @"/login"
#define KB_UL_SETTINGS KB_UL_BASE @"/settings"
// 在扩展内,启用 URL Bridge仅在显式的用户点击动作中使用
// 这样即便宿主 App如备忘录拒绝 extensionContext openURL,仍可通过响应链兜底拉起容器 App。
// 说明:
// - `extensionContext openURL:` 是 Apple 官方推荐方式,但部分宿主 App尤其是“B 类应用”)
// 可能会拦截该调用,导致无法直接唤起容器 App
// - 行业内不少键盘会加“响应链 openURL”兜底来提升唤起成功率但该方式存在上架审核风险。
// 如你要走更稳妥的上架策略:把该宏改为 0仅保留 extensionContext 方案)。
#ifndef KB_URL_BRIDGE_ENABLE
// 上架建议Release 关闭“响应链 openURL”兜底避免审核风险。
#if DEBUG
#define KB_URL_BRIDGE_ENABLE 1
#else
#define KB_URL_BRIDGE_ENABLE 1
#endif
#endif

View File

@@ -2,7 +2,7 @@
// KBExtensionAppLauncher.h
// CustomKeyboard
//
// 封装:在键盘扩展中拉起主 AppScheme / Universal Link + 响应链兜底)。
// 封装:在键盘扩展中拉起主 AppScheme / Universal Link
//
#import <UIKit/UIKit.h>
@@ -12,23 +12,25 @@ NS_ASSUME_NONNULL_BEGIN
@interface KBExtensionAppLauncher : NSObject
/// 通用入口:优先尝试 primaryURL失败后尝试 fallbackURL
/// 两者都失败时再通过响应链openURL:)做兜底
/// 均通过 `extensionContext openURL` 发起跳转(避免使用扩展禁用 API/响应链绕行)
/// 若开启 `KB_URL_BRIDGE_ENABLE=1`,会在两次 `extensionContext openURL` 均失败时,
/// 再尝试一次“响应链 openURL”兜底上架有风险请谨慎开启
/// - Parameters:
/// - primaryURL: 第一优先尝试的 URL可为 Scheme 或 UL
/// - fallbackURL: 失败时的备用 URL可为 nil
/// - ivc: 当前的 UIInputViewController用于 extensionContext openURL
/// - source: 兜底时用作起点的 responder通常传 self 或 self.view
/// - source: 作为响应链兜底的起点(可为 nil
/// - completion: 最终是否“看起来已成功发起”打开动作(不保证一定跳转到 App
+ (void)openPrimaryURL:(NSURL * _Nullable)primaryURL
fallbackURL:(NSURL * _Nullable)fallbackURL
usingInputController:(UIInputViewController *)ivc
source:(UIResponder *)source
source:(UIResponder * _Nullable)source
completion:(void (^ _Nullable)(BOOL success))completion;
/// 简化版:只针对单一 Scheme 做尝试 + 响应链兜底
/// 简化版:只针对单一 Scheme 做尝试。
+ (void)openScheme:(NSURL *)scheme
usingInputController:(UIInputViewController *)ivc
source:(UIResponder *)source
source:(UIResponder * _Nullable)source
completion:(void (^ _Nullable)(BOOL success))completion;
@end

View File

@@ -4,15 +4,86 @@
//
#import "KBExtensionAppLauncher.h"
#if KB_URL_BRIDGE_ENABLE
#import <objc/message.h>
#endif
@implementation KBExtensionAppLauncher
#if KB_URL_BRIDGE_ENABLE
+ (BOOL)kb_openURLViaResponderChain:(NSURL *)url
source:(nullable UIResponder *)source
completion:(void (^ _Nullable)(BOOL success))completion {
if (!url) {
if (completion) { completion(NO); }
return NO;
}
UIResponder *responder = source;
// openURL:options:completionHandler:
// /宿 App
// options options nil
SEL openURLOptionsSel = NSSelectorFromString(@"openURL:options:completionHandler:");
while (responder) {
if ([responder respondsToSelector:openURLOptionsSel]) {
void (*msgSend)(id, SEL, NSURL *, id, void (^)(BOOL)) = (void *)objc_msgSend;
msgSend(responder, openURLOptionsSel, url, nil, ^(BOOL ok) {
if (completion) { completion(ok); }
});
return YES;
}
responder = responder.nextResponder;
}
// openURL:completionHandler:
responder = source;
SEL openURLCompletionSel = NSSelectorFromString(@"openURL:completionHandler:");
while (responder) {
if ([responder respondsToSelector:openURLCompletionSel]) {
void (*msgSend)(id, SEL, NSURL *, void (^)(BOOL)) = (void *)objc_msgSend;
msgSend(responder, openURLCompletionSel, url, ^(BOOL ok) {
if (completion) { completion(ok); }
});
return YES;
}
responder = responder.nextResponder;
}
// openURL:
responder = source;
SEL openURLSel = NSSelectorFromString(@"openURL:");
while (responder) {
if ([responder respondsToSelector:openURLSel]) {
BOOL (*msgSend)(id, SEL, NSURL *) = (void *)objc_msgSend;
BOOL ok = msgSend(responder, openURLSel, url);
if (completion) { completion(ok); }
return YES;
}
responder = responder.nextResponder;
}
if (completion) { completion(NO); }
return NO;
}
#endif
+ (void)openPrimaryURL:(NSURL * _Nullable)primaryURL
fallbackURL:(NSURL * _Nullable)fallbackURL
usingInputController:(UIInputViewController *)ivc
source:(UIResponder *)source
source:(UIResponder * _Nullable)source
completion:(void (^ _Nullable)(BOOL success))completion {
if (![NSThread isMainThread]) {
dispatch_async(dispatch_get_main_queue(), ^{
[self openPrimaryURL:primaryURL
fallbackURL:fallbackURL
usingInputController:ivc
source:source
completion:completion];
});
return;
}
if (!ivc || (!primaryURL && !fallbackURL)) {
if (completion) { completion(NO); }
return;
@@ -48,19 +119,38 @@
finish(YES);
return;
}
BOOL bridged = [self p_bridgeFirst:first second:second from:source];
finish(bridged);
#if KB_URL_BRIDGE_ENABLE
// 宿 App extensionContext openURL
//
UIResponder *start = (source ?: (UIResponder *)ivc.view ?: (UIResponder *)ivc);
[self kb_openURLViaResponderChain:second
source:start
completion:^(BOOL ok3) {
finish(ok3);
}];
#else
finish(NO);
#endif
}];
} else {
BOOL bridged = [self p_bridgeFirst:first second:nil from:source];
finish(bridged);
#if KB_URL_BRIDGE_ENABLE
UIResponder *start = (source ?: (UIResponder *)ivc.view ?: (UIResponder *)ivc);
[self kb_openURLViaResponderChain:first
source:start
completion:^(BOOL ok3) {
finish(ok3);
}];
#else
finish(NO);
#endif
}
}];
}
+ (void)openScheme:(NSURL *)scheme
usingInputController:(UIInputViewController *)ivc
source:(UIResponder *)source
source:(UIResponder * _Nullable)source
completion:(void (^ _Nullable)(BOOL success))completion {
[self openPrimaryURL:scheme
fallbackURL:nil
@@ -69,53 +159,4 @@
completion:completion];
}
#pragma mark - Private
// openURL: KBURLOpenBridge
+ (BOOL)p_openURLViaResponder:(NSURL *)url from:(UIResponder *)start {
#if KB_URL_BRIDGE_ENABLE
if (!url || !start) return NO;
SEL sel = NSSelectorFromString(@"openURL:");
UIResponder *responder = start;
while (responder) {
@try {
if ([responder respondsToSelector:sel]) {
BOOL handled = NO;
BOOL (*funcBool)(id, SEL, NSURL *) = (BOOL (*)(id, SEL, NSURL *))objc_msgSend;
if (funcBool) {
handled = funcBool(responder, sel, url);
} else {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
[responder performSelector:sel withObject:url];
handled = YES;
#pragma clang diagnostic pop
}
return handled;
}
} @catch (__unused NSException *e) {
// ignore and continue
}
responder = responder.nextResponder;
}
return NO;
#else
(void)url; (void)start;
return NO;
#endif
}
+ (BOOL)p_bridgeFirst:(NSURL * _Nullable)first
second:(NSURL * _Nullable)second
from:(UIResponder *)source {
BOOL bridged = NO;
if (first) {
bridged = [self p_openURLViaResponder:first from:source];
}
if (!bridged && second) {
bridged = [self p_openURLViaResponder:second from:source];
}
return bridged;
}
@end

View File

@@ -7,7 +7,7 @@
#import "Masonry.h"
#import "KBResponderUtils.h" // UIInputViewController
#import "KBHUD.h"
#import "KBHostAppLauncher.h"
#import "../Utils/KBExtensionAppLauncher.h"
@interface KBFullAccessGuideView ()
@property (nonatomic, strong) UIControl *backdrop;
@@ -159,18 +159,34 @@
// KBResponderUtils.h
// App访宿 UIApplication + Scheme
- (void)onTapGoEnable {
UIInputViewController *ivc = KBFindInputViewController(self);
// responder
UIResponder *start = ivc.view ?: (UIResponder *)self;
// SchemeAppDelegate kbkeyboardAppExtension://settings
NSURL *scheme = [NSURL URLWithString:[NSString stringWithFormat:@"%@@//settings?src=kb_extension", KB_APP_SCHEME]];
BOOL ok = [KBHostAppLauncher openHostAppURL:scheme fromResponder:start];
if (ok) {
[self dismiss];
} else {
NSString *showInfo = [NSString stringWithFormat:KBLocalized(@"Follow: Settings → General → Keyboard → Keyboards → %@ → Allow Full Access"),AppName];
UIInputViewController *ivc = self.ivc ?: KBFindInputViewController(self);
if (!ivc) {
NSString *showInfo = [NSString stringWithFormat:KBLocalized(@"Follow: Settings → General → Keyboard → Keyboards → %@ → Allow Full Access"), AppName];
[KBHUD showInfo:showInfo];
return;
}
// Universal Link 退 Scheme
NSURL *ul = [NSURL URLWithString:[NSString stringWithFormat:@"%@?src=kb_extension", KB_UL_SETTINGS]];
NSURL *scheme = [NSURL URLWithString:[NSString stringWithFormat:@"%@://settings?src=kb_extension", KB_APP_SCHEME]];
__weak typeof(self) weakSelf = self;
[KBExtensionAppLauncher openPrimaryURL:ul
fallbackURL:scheme
usingInputController:ivc
source:(ivc.view ?: (UIResponder *)weakSelf)
completion:^(BOOL success) {
dispatch_async(dispatch_get_main_queue(), ^{
__strong typeof(weakSelf) self = weakSelf;
if (!self) {
return;
}
if (success) {
[self dismiss];
} else {
NSString *showInfo = [NSString stringWithFormat:KBLocalized(@"Follow: Settings → General → Keyboard → Keyboards → %@ → Allow Full Access"), AppName];
[KBHUD showInfo:showInfo];
}
});
}];
}
@end

View File

@@ -16,7 +16,7 @@
#import "KBFunctionPasteView.h"
#import "KBFunctionTagCell.h"
#import "KBFunctionTagListView.h"
#import "KBHostAppLauncher.h"
#import "KBExtensionAppLauncher.h"
#import "KBInputBufferManager.h"
#import "KBResponderUtils.h" // UIInputViewController
#import "KBSignUtils.h"
@@ -142,76 +142,26 @@
/// #323232 #D0D3DA
+ (UIColor *)kb_backgroundColor {
if (@available(iOS 13.0, *)) {
return [UIColor colorWithDynamicProvider:^UIColor *_Nonnull(
UITraitCollection *_Nonnull traitCollection) {
if (traitCollection.userInterfaceStyle == UIUserInterfaceStyleDark) {
return [UIColor colorWithHex:0x2B2B2B];
} else {
return [UIColor colorWithHex:0xD0D3DA];
}
}];
}
return [UIColor colorWithHex:0xD0D3DA];
}
/// Cell #707070 90%
+ (UIColor *)kb_cellBackgroundColor {
if (@available(iOS 13.0, *)) {
return [UIColor colorWithDynamicProvider:^UIColor *_Nonnull(
UITraitCollection *_Nonnull traitCollection) {
if (traitCollection.userInterfaceStyle == UIUserInterfaceStyleDark) {
return [UIColor colorWithHex:0x707070];
} else {
return [UIColor colorWithWhite:1 alpha:0.9];
}
}];
}
return [UIColor colorWithWhite:1 alpha:0.9];
}
/// Cell #FFFFFF #1B1F1A
+ (UIColor *)kb_cellTextColor {
if (@available(iOS 13.0, *)) {
return [UIColor colorWithDynamicProvider:^UIColor *_Nonnull(
UITraitCollection *_Nonnull traitCollection) {
if (traitCollection.userInterfaceStyle == UIUserInterfaceStyleDark) {
return [UIColor whiteColor];
} else {
return [UIColor colorWithHex:0x1B1F1A];
}
}];
}
return [UIColor colorWithHex:0x1B1F1A];
}
/// Clear
+ (UIColor *)kb_clearButtonTextColor {
if (@available(iOS 13.0, *)) {
return [UIColor colorWithDynamicProvider:^UIColor *_Nonnull(
UITraitCollection *_Nonnull traitCollection) {
if (traitCollection.userInterfaceStyle == UIUserInterfaceStyleDark) {
return [UIColor whiteColor];
} else {
return [UIColor blackColor];
}
}];
}
return [UIColor blackColor];
}
/// #707070 #B9BDC8
+ (UIColor *)kb_deleteButtonBackgroundColor {
if (@available(iOS 13.0, *)) {
return [UIColor colorWithDynamicProvider:^UIColor *_Nonnull(
UITraitCollection *_Nonnull traitCollection) {
if (traitCollection.userInterfaceStyle == UIUserInterfaceStyleDark) {
return [UIColor colorWithHex:0x707070];
} else {
return [UIColor colorWithHex:0xB9BDC8];
}
}];
}
return [UIColor colorWithHex:0xB9BDC8];
}
@@ -505,7 +455,10 @@
if (event.data.length == 0) {
return;
}
NSLog(@"[KBStream] SSE raw payload: %@", event.data);
#if DEBUG
NSLog(@"[KBStream] SSE raw payload len=%lu",
(unsigned long)(event.data ?: @"").length);
#endif
NSData *jsonData = [event.data dataUsingEncoding:NSUTF8StringEncoding];
if (!jsonData) {
return;
@@ -778,12 +731,27 @@
if (!KBAuthManager.shared.isLoggedIn) {
UIInputViewController *ivc = KBFindInputViewController(self);
if (!ivc) {
[KBHUD showInfo:KBLocalized(@"请回到桌面手动打开App登录")];
return;
}
NSString *schemeStr =
[NSString stringWithFormat:@"%@://login?src=keyboard", KB_APP_SCHEME];
NSURL *scheme = [NSURL URLWithString:schemeStr];
// UIApplication App
BOOL ok = [KBHostAppLauncher openHostAppURL:scheme fromResponder:ivc.view];
NSURL *ul = [NSURL URLWithString:[NSString stringWithFormat:@"%@?src=keyboard", KB_UL_LOGIN]];
NSURL *scheme =
[NSURL URLWithString:[NSString stringWithFormat:@"%@://login?src=keyboard", KB_APP_SCHEME]];
__weak typeof(self) weakSelf = self;
[KBExtensionAppLauncher openPrimaryURL:ul
fallbackURL:scheme
usingInputController:ivc
source:(ivc.view ?: (UIResponder *)weakSelf)
completion:^(BOOL success) {
if (success) {
return;
}
dispatch_async(dispatch_get_main_queue(), ^{
[KBHUD showInfo:KBLocalized(@"请回到桌面手动打开App登录")];
});
}];
return;
// if (!ivc) return;
// NSString *encodedTitle = [title
@@ -881,37 +849,34 @@ static void KBULDarwinCallback(CFNotificationCenterRef center, void *observer,
if (!ul)
return;
dispatch_after(
dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.05 * NSEC_PER_SEC)),
dispatch_get_main_queue(), ^{
// extensionContext UL
[ivc.extensionContext
openURL:ul
completionHandler:^(BOOL ok) {
if (ok) {
return;
}
// UL 宿 UIApplication + Scheme
NSURL *scheme = [NSURL
URLWithString:
[NSString
stringWithFormat:
@"%@@//login?src=functionView&index=%ld&title=%@",
KB_APP_SCHEME, (long)indexPath.item,
encodedTitle]];
UIResponder *start = ivc.view ?: (UIResponder *)self;
BOOL ok2 = [KBHostAppLauncher openHostAppURL:scheme
fromResponder:start];
if (!ok2) {
// 访宿 Manager
//
dispatch_async(dispatch_get_main_queue(), ^{
[[KBFullAccessManager shared]
ensureFullAccessOrGuideInView:self];
});
}
}];
});
NSURL *scheme = [NSURL
URLWithString:
[NSString stringWithFormat:
@"%@://login?src=functionView&index=%ld&title=%@",
KB_APP_SCHEME, (long)indexPath.item, encodedTitle]];
__weak typeof(self) weakSelf = self;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.05 * NSEC_PER_SEC)),
dispatch_get_main_queue(), ^{
__strong typeof(weakSelf) self = weakSelf;
if (!self) {
return;
}
[KBExtensionAppLauncher
openPrimaryURL:ul
fallbackURL:scheme
usingInputController:ivc
source:(ivc.view ?: (UIResponder *)self)
completion:^(BOOL success) {
if (success) {
return;
}
dispatch_async(dispatch_get_main_queue(), ^{
[[KBFullAccessManager shared]
ensureFullAccessOrGuideInView:self];
});
}];
});
}
#pragma mark - Button Actions