diff --git a/CustomKeyboard/CustomKeyboard.entitlements b/CustomKeyboard/CustomKeyboard.entitlements
new file mode 100644
index 0000000..89f84e3
--- /dev/null
+++ b/CustomKeyboard/CustomKeyboard.entitlements
@@ -0,0 +1,11 @@
+
+
+
+
+ keychain-access-groups
+
+ $(AppIdentifierPrefix)com.keyBoardst.shared
+
+
+
+
diff --git a/CustomKeyboard/KeyboardViewController.m b/CustomKeyboard/KeyboardViewController.m
index 06491fe..c4ae3af 100644
--- a/CustomKeyboard/KeyboardViewController.m
+++ b/CustomKeyboard/KeyboardViewController.m
@@ -12,6 +12,7 @@
#import "KBFunctionView.h"
#import "KBSettingView.h"
#import "Masonry.h"
+#import "KBAuthManager.h"
static CGFloat KEYBOARDHEIGHT = 256 + 20;
@@ -194,7 +195,10 @@ static CGFloat KEYBOARDHEIGHT = 256 + 20;
[super viewDidAppear:animated];
if (!_kb_didTriggerLoginDeepLinkOnce) {
_kb_didTriggerLoginDeepLinkOnce = YES;
- [self kb_tryOpenContainerForLoginIfNeeded];
+ // 仅在未登录时尝试拉起主App登录
+ if (!KBAuthManager.shared.isLoggedIn) {
+ [self kb_tryOpenContainerForLoginIfNeeded];
+ }
}
}
diff --git a/CustomKeyboard/Network/KBNetworkManager.m b/CustomKeyboard/Network/KBNetworkManager.m
index e4d993e..824109b 100644
--- a/CustomKeyboard/Network/KBNetworkManager.m
+++ b/CustomKeyboard/Network/KBNetworkManager.m
@@ -5,6 +5,7 @@
#import "KBNetworkManager.h"
#import "AFNetworking.h"
+#import "KBAuthManager.h"
NSErrorDomain const KBNetworkErrorDomain = @"com.company.keyboard.network";
@@ -95,8 +96,10 @@ NSErrorDomain const KBNetworkErrorDomain = @"com.company.keyboard.network";
}
- (void)applyHeaders:(NSDictionary *)headers toMutableRequest:(NSMutableURLRequest *)req contentType:(NSString *)contentType {
- // 合并默认头与局部头,局部覆盖
+ // 合并默认头与局部头,并注入授权头(若可用)。局部覆盖优先。
NSMutableDictionary *all = [self.defaultHeaders mutableCopy] ?: [NSMutableDictionary new];
+ NSDictionary *auth = [[KBAuthManager shared] authorizationHeader];
+ [auth enumerateKeysAndObjectsUsingBlock:^(NSString *key, NSString *obj, BOOL *stop) { all[key] = obj; }];
if (contentType) all[@"Content-Type"] = contentType;
[headers enumerateKeysAndObjectsUsingBlock:^(NSString *key, NSString *obj, BOOL *stop) { all[key] = obj; }];
[all enumerateKeysAndObjectsUsingBlock:^(NSString *key, NSString *obj, BOOL *stop) { [req setValue:obj forHTTPHeaderField:key]; }];
diff --git a/Shared/KBAuthManager.h b/Shared/KBAuthManager.h
new file mode 100644
index 0000000..b43bf2b
--- /dev/null
+++ b/Shared/KBAuthManager.h
@@ -0,0 +1,56 @@
+//
+// KBAuthManager.h
+// 主 App 与键盘扩展共享使用
+//
+// 通过 Keychain Sharing 统一管理用户登录态(access/refresh token)。
+// 线程安全;在保存/清空时同时发送进程内通知与 Darwin 跨进程通知,
+// 以便键盘扩展正运行在其他 App 时也能及时感知变更。
+//
+
+#import
+
+NS_ASSUME_NONNULL_BEGIN
+
+/// Darwin 跨进程通知名称:当令牌更新或清除时发送,用于提示 App/扩展刷新缓存。
+extern NSString * const kKBDarwinAuthChanged;
+
+/// 进程内通知(NSNotificationCenter):令牌更新或清除时发送。
+extern NSNotificationName const KBAuthChangedNotification;
+
+/// 简单的会话容器;可按需扩展字段。
+@interface KBAuthSession : NSObject
+@property (nonatomic, copy, nullable) NSString *accessToken;
+@property (nonatomic, copy, nullable) NSString *refreshToken;
+@property (nonatomic, strong, nullable) NSDate *expiryDate; // 可选:过期时间
+@property (nonatomic, copy, nullable) NSString *userIdentifier; // 可选:如“用 Apple 登录”的 userIdentifier
+@end
+
+/// 基于“共享钥匙串”的鉴权管理器(使用 Keychain Sharing 访问组)。
+@interface KBAuthManager : NSObject
+
++ (instancetype)shared;
+
+/// 当前会话(内存缓存),在加载/保存/清除后更新。
+@property (atomic, strong, readonly, nullable) KBAuthSession *current;
+
+/// 是否已登录:存在 accessToken 且未明显过期(未设置过期时间则只要有 token 即视为已登录)。
+- (BOOL)isLoggedIn;
+
+/// 从钥匙串加载到内存;通常首次访问时会自动加载。
+- (void)reloadFromKeychain;
+
+/// 保存令牌到“共享钥匙串”并通知观察者。
+- (BOOL)saveAccessToken:(NSString *)accessToken
+ refreshToken:(nullable NSString *)refreshToken
+ expiryDate:(nullable NSDate *)expiryDate
+ userIdentifier:(nullable NSString *)userIdentifier;
+
+/// 从钥匙串与内存中清除令牌,并通知观察者。
+- (void)signOut;
+
+/// 便捷方法:若存在有效令牌,返回 `Authorization` 请求头;否则返回空字典。
+- (NSDictionary *)authorizationHeader;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/Shared/KBAuthManager.m b/Shared/KBAuthManager.m
new file mode 100644
index 0000000..034613d
--- /dev/null
+++ b/Shared/KBAuthManager.m
@@ -0,0 +1,188 @@
+//
+// KBAuthManager.m
+//
+// 关键点:
+// - 使用固定的 service/account 将 KBAuthSession 序列化后保存到钥匙串;
+// - 通过 kSecAttrAccessGroup 指定 Keychain Sharing 访问组,实现 App 与扩展共享;
+// - 保存/清除时发送 Darwin 跨进程通知,便于对端刷新缓存;
+//
+
+#import "KBAuthManager.h"
+#import
+#import "KBConfig.h" // 需要共享钥匙串访问组常量,见 KBConfig.h 中的说明
+
+NSString * const kKBDarwinAuthChanged = @"com.keyBoardst.auth.changed";
+NSNotificationName const KBAuthChangedNotification = @"KBAuthChangedNotification";
+
+static NSString * const kKBKCService = @"com.keyBoardst.auth"; // 钥匙串 service 名
+static NSString * const kKBKCAccount = @"session"; // 钥匙串 account 键
+
+// 用于 Keychain Sharing 的访问组;必须与两个 target 的 entitlements 配置一致。
+// 示例(Capabilities 中勾选 Keychain Sharing 后的值):
+// $(AppIdentifierPrefix)com.keyBoardst.shared
+// 运行时会被展开为:TN6HHV45BB.com.keyBoardst.shared
+#ifndef KB_KEYCHAIN_ACCESS_GROUP
+#define KB_KEYCHAIN_ACCESS_GROUP @"TN6HHV45BB.com.keyBoardst.shared"
+#endif
+
+// 过期宽限:若过期时间距离当前 <= 该阈值,则视为已过期。
+static const NSTimeInterval kKBExpiryGrace = 5.0; // 秒
+
+@implementation KBAuthSession
+
++ (BOOL)supportsSecureCoding { return YES; }
+
+- (void)encodeWithCoder:(NSCoder *)coder {
+ [coder encodeObject:self.accessToken forKey:@"accessToken"];
+ [coder encodeObject:self.refreshToken forKey:@"refreshToken"];
+ [coder encodeObject:self.expiryDate forKey:@"expiryDate"];
+ [coder encodeObject:self.userIdentifier forKey:@"userIdentifier"];
+}
+
+- (instancetype)initWithCoder:(NSCoder *)coder {
+ if (self = [super init]) {
+ _accessToken = [coder decodeObjectOfClass:NSString.class forKey:@"accessToken"];
+ _refreshToken = [coder decodeObjectOfClass:NSString.class forKey:@"refreshToken"];
+ _expiryDate = [coder decodeObjectOfClass:NSDate.class forKey:@"expiryDate"];
+ _userIdentifier = [coder decodeObjectOfClass:NSString.class forKey:@"userIdentifier"];
+ }
+ return self;
+}
+
+@end
+
+@interface KBAuthManager ()
+@property (atomic, strong, readwrite, nullable) KBAuthSession *current;
+@end
+
+@implementation KBAuthManager
+
++ (instancetype)shared {
+ static KBAuthManager *m; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ m = [KBAuthManager new]; });
+ return m;
+}
+
+- (instancetype)init {
+ if (self = [super init]) {
+ [self reloadFromKeychain];
+ // 监听 Darwin 跨进程通知(App 与扩展之间)
+ CFNotificationCenterAddObserver(CFNotificationCenterGetDarwinNotifyCenter(),
+ (__bridge const void *)(self),
+ KBAuthDarwinCallback,
+ (__bridge CFStringRef)kKBDarwinAuthChanged,
+ NULL,
+ CFNotificationSuspensionBehaviorDeliverImmediately);
+ }
+ return self;
+}
+
+static void KBAuthDarwinCallback(CFNotificationCenterRef center, void *observer, CFStringRef name, const void *object, CFDictionaryRef userInfo) {
+ KBAuthManager *self = (__bridge KBAuthManager *)observer;
+ [self reloadFromKeychain];
+}
+
+- (void)dealloc {
+ CFNotificationCenterRemoveObserver(CFNotificationCenterGetDarwinNotifyCenter(), (__bridge const void *)(self), (__bridge CFStringRef)kKBDarwinAuthChanged, NULL);
+}
+
+- (BOOL)isLoggedIn {
+ KBAuthSession *s = self.current;
+ if (s.accessToken.length == 0) return NO;
+ if (!s.expiryDate) return YES; // 未设置过期时间时,只要有 token 即视为已登录
+ return ([s.expiryDate timeIntervalSinceNow] > kKBExpiryGrace);
+}
+
+#pragma mark - Public
+
+- (void)reloadFromKeychain {
+ NSData *data = [self keychainRead];
+ KBAuthSession *session = nil;
+ if (data.length > 0) {
+ @try {
+ session = [NSKeyedUnarchiver unarchivedObjectOfClass:KBAuthSession.class fromData:data error:NULL];
+ } @catch (__unused NSException *e) { session = nil; }
+ }
+ self.current = session;
+ [[NSNotificationCenter defaultCenter] postNotificationName:KBAuthChangedNotification object:nil]; // 进程内通知
+}
+
+- (BOOL)saveAccessToken:(NSString *)accessToken
+ refreshToken:(NSString *)refreshToken
+ expiryDate:(NSDate *)expiryDate
+ userIdentifier:(NSString *)userIdentifier {
+ KBAuthSession *s = [KBAuthSession new];
+ s.accessToken = accessToken ?: @"";
+ s.refreshToken = refreshToken;
+ s.expiryDate = expiryDate;
+ s.userIdentifier = userIdentifier;
+
+ NSError *err = nil;
+ NSData *data = [NSKeyedArchiver archivedDataWithRootObject:s requiringSecureCoding:YES error:&err];
+ if (err || data.length == 0) return NO;
+
+ BOOL ok = [self keychainWrite:data];
+ if (ok) {
+ self.current = s;
+ // 进程内通知
+ [[NSNotificationCenter defaultCenter] postNotificationName:KBAuthChangedNotification object:nil];
+ // 跨进程通知(App <-> 扩展)
+ CFNotificationCenterPostNotification(CFNotificationCenterGetDarwinNotifyCenter(), (__bridge CFStringRef)kKBDarwinAuthChanged, NULL, NULL, true);
+ }
+ return ok;
+}
+
+- (void)signOut {
+ [self keychainDelete];
+ self.current = nil;
+ [[NSNotificationCenter defaultCenter] postNotificationName:KBAuthChangedNotification object:nil];
+ CFNotificationCenterPostNotification(CFNotificationCenterGetDarwinNotifyCenter(), (__bridge CFStringRef)kKBDarwinAuthChanged, NULL, NULL, true);
+}
+
+- (NSDictionary *)authorizationHeader {
+ NSString *t = self.current.accessToken;
+ if (t.length == 0) return @{}; // 未登录返回空头部
+ return @{ @"Authorization": [@"Bearer " stringByAppendingString:t] };
+}
+
+#pragma mark - Keychain (shared)
+
+- (NSMutableDictionary *)baseKCQuery {
+ NSMutableDictionary *q = [@{ (__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword,
+ (__bridge id)kSecAttrService: kKBKCService,
+ (__bridge id)kSecAttrAccount: kKBKCAccount } mutableCopy];
+ // 指定共享访问组(App 与扩展共用同一组)
+ q[(__bridge id)kSecAttrAccessGroup] = KB_KEYCHAIN_ACCESS_GROUP;
+ return q;
+}
+
+- (BOOL)keychainWrite:(NSData *)data {
+ if (!data) return NO;
+ NSMutableDictionary *query = [self baseKCQuery];
+ SecItemDelete((__bridge CFDictionaryRef)query);
+
+ // 设置属性
+ query[(__bridge id)kSecValueData] = data;
+ // 访问控制:设备首次解锁后可读,不随备份迁移
+ query[(__bridge id)kSecAttrAccessible] = (__bridge id)kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly;
+
+ OSStatus status = SecItemAdd((__bridge CFDictionaryRef)query, NULL);
+ return (status == errSecSuccess);
+}
+
+- (NSData *)keychainRead {
+ NSMutableDictionary *query = [self baseKCQuery];
+ query[(__bridge id)kSecReturnData] = @YES;
+ query[(__bridge id)kSecMatchLimit] = (__bridge id)kSecMatchLimitOne;
+
+ CFTypeRef dataRef = NULL;
+ OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, &dataRef);
+ if (status != errSecSuccess || !dataRef) return nil;
+ return (__bridge_transfer NSData *)dataRef;
+}
+
+- (void)keychainDelete {
+ NSDictionary *query = [self baseKCQuery];
+ SecItemDelete((__bridge CFDictionaryRef)query);
+}
+
+@end
diff --git a/Shared/KBConfig.h b/Shared/KBConfig.h
index b5cf71c..5813a63 100644
--- a/Shared/KBConfig.h
+++ b/Shared/KBConfig.h
@@ -1,8 +1,8 @@
//
// KBConfig.h
-// Shared config/macros for both main app and keyboard extension.
+// 主 App 与键盘扩展共用的配置/宏。
//
-// Edit these values once and both targets pick them up via PCH import.
+// 在此处修改后,会通过 PCH 被两个 target 同步引用。
//
#ifndef KBConfig_h
@@ -23,3 +23,11 @@
#endif /* KBConfig_h */
+// --- 认证/共享钥匙串 配置 ---
+// 若已在 Capabilities 中启用 Keychain Sharing,并添加访问组:
+// $(AppIdentifierPrefix)com.keyBoardst.shared
+// 运行时会展开为:TN6HHV45BB.com.keyBoardst.shared
+// KBAuthManager 通过下面的宏定位访问组;如需修改,可在 Build Settings 或前缀头中覆盖该宏。
+#ifndef KB_KEYCHAIN_ACCESS_GROUP
+#define KB_KEYCHAIN_ACCESS_GROUP @"TN6HHV45BB.com.keyBoardst.shared"
+#endif
diff --git a/keyBoard.xcodeproj/project.pbxproj b/keyBoard.xcodeproj/project.pbxproj
index de04151..6f4f0e7 100644
--- a/keyBoard.xcodeproj/project.pbxproj
+++ b/keyBoard.xcodeproj/project.pbxproj
@@ -38,6 +38,8 @@
04FC95F12EB339A7007BD342 /* LoginViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 04FC95F02EB339A7007BD342 /* LoginViewController.m */; };
04FC95F42EB339C1007BD342 /* AppleSignInManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 04FC95F32EB339C1007BD342 /* AppleSignInManager.m */; };
04FC96142EB34E00007BD342 /* KBLoginSheetViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 04FC96122EB34E00007BD342 /* KBLoginSheetViewController.m */; };
+ A1B2C4002EB4A0A100000003 /* KBAuthManager.m in Sources */ = {isa = PBXBuildFile; fileRef = A1B2C4002EB4A0A100000002 /* KBAuthManager.m */; };
+ A1B2C4002EB4A0A100000004 /* KBAuthManager.m in Sources */ = {isa = PBXBuildFile; fileRef = A1B2C4002EB4A0A100000002 /* KBAuthManager.m */; };
04FC97002EB30A00007BD342 /* KBGuideTopCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 04FC96FF2EB30A00007BD342 /* KBGuideTopCell.m */; };
04FC97032EB30A00007BD342 /* KBGuideKFCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 04FC97022EB30A00007BD342 /* KBGuideKFCell.m */; };
04FC97062EB30A00007BD342 /* KBGuideUserCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 04FC97052EB30A00007BD342 /* KBGuideUserCell.m */; };
@@ -149,6 +151,8 @@
04FC970C2EB334F8007BD342 /* KBWebImageManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBWebImageManager.h; sourceTree = ""; };
04FC970D2EB334F8007BD342 /* KBWebImageManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBWebImageManager.m; sourceTree = ""; };
04FC98012EB36AAB007BD342 /* KBConfig.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBConfig.h; sourceTree = ""; };
+ A1B2C4002EB4A0A100000001 /* KBAuthManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBAuthManager.h; sourceTree = ""; };
+ A1B2C4002EB4A0A100000002 /* KBAuthManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBAuthManager.m; sourceTree = ""; };
2C1092FB2B452F95B15D4263 /* Pods_CustomKeyboard.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_CustomKeyboard.framework; sourceTree = BUILT_PRODUCTS_DIR; };
51FE7C4C42C2255B3C1C4128 /* Pods-keyBoard.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-keyBoard.release.xcconfig"; path = "Target Support Files/Pods-keyBoard/Pods-keyBoard.release.xcconfig"; sourceTree = ""; };
727EC7532EAF848B00B36487 /* keyBoard.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = keyBoard.app; sourceTree = BUILT_PRODUCTS_DIR; };
@@ -526,6 +530,8 @@
isa = PBXGroup;
children = (
04FC98012EB36AAB007BD342 /* KBConfig.h */,
+ A1B2C4002EB4A0A100000001 /* KBAuthManager.h */,
+ A1B2C4002EB4A0A100000002 /* KBAuthManager.m */,
);
path = Shared;
sourceTree = "";
@@ -767,6 +773,7 @@
04FC956D2EB054B7007BD342 /* KBKeyboardView.m in Sources */,
04FC95672EB0546C007BD342 /* KBKey.m in Sources */,
A1B2C3F42EB35A9900000001 /* KBFullAccessGuideView.m in Sources */,
+ A1B2C4002EB4A0A100000003 /* KBAuthManager.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -796,6 +803,7 @@
04FC95CC2EB1E780007BD342 /* BaseTabBarController.m in Sources */,
04FC95F42EB339C1007BD342 /* AppleSignInManager.m in Sources */,
04C6EAC12EAF86530089C901 /* ViewController.m in Sources */,
+ A1B2C4002EB4A0A100000004 /* KBAuthManager.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -833,6 +841,7 @@
isa = XCBuildConfiguration;
baseConfigurationReference = B8CA018AB878499327504AAD /* Pods-CustomKeyboard.debug.xcconfig */;
buildSettings = {
+ CODE_SIGN_ENTITLEMENTS = CustomKeyboard/CustomKeyboard.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = TN6HHV45BB;
@@ -861,6 +870,7 @@
isa = XCBuildConfiguration;
baseConfigurationReference = B12EC429812407B9F0E67565 /* Pods-CustomKeyboard.release.xcconfig */;
buildSettings = {
+ CODE_SIGN_ENTITLEMENTS = CustomKeyboard/CustomKeyboard.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = TN6HHV45BB;
diff --git a/keyBoard/Class/Login/VC/LoginViewController.m b/keyBoard/Class/Login/VC/LoginViewController.m
index 28c6ab1..5189830 100644
--- a/keyBoard/Class/Login/VC/LoginViewController.m
+++ b/keyBoard/Class/Login/VC/LoginViewController.m
@@ -4,6 +4,7 @@
#import "AppleSignInManager.h"
#import
#import
+#import "KBAuthManager.h"
@interface LoginViewController ()
// 容器视图(用于居中摆放内容)
@@ -147,6 +148,12 @@
if (code.length) info[@"authorizationCode"] = code;
}
+ // 将示例中的 identityToken 暂存为访问令牌(实际项目应调用服务端换取业务 token)
+ NSString *accessToken = info[@"identityToken"];
+ NSString *uid = info[@"userIdentifier"]; // 不变
+ if (accessToken.length > 0) {
+ [[KBAuthManager shared] saveAccessToken:accessToken refreshToken:nil expiryDate:nil userIdentifier:uid];
+ }
if (selfStrong.onLoginSuccess) selfStrong.onLoginSuccess(info);
}];
}
diff --git a/keyBoard/Class/Network/KBNetworkManager.m b/keyBoard/Class/Network/KBNetworkManager.m
index e4d993e..824109b 100644
--- a/keyBoard/Class/Network/KBNetworkManager.m
+++ b/keyBoard/Class/Network/KBNetworkManager.m
@@ -5,6 +5,7 @@
#import "KBNetworkManager.h"
#import "AFNetworking.h"
+#import "KBAuthManager.h"
NSErrorDomain const KBNetworkErrorDomain = @"com.company.keyboard.network";
@@ -95,8 +96,10 @@ NSErrorDomain const KBNetworkErrorDomain = @"com.company.keyboard.network";
}
- (void)applyHeaders:(NSDictionary *)headers toMutableRequest:(NSMutableURLRequest *)req contentType:(NSString *)contentType {
- // 合并默认头与局部头,局部覆盖
+ // 合并默认头与局部头,并注入授权头(若可用)。局部覆盖优先。
NSMutableDictionary *all = [self.defaultHeaders mutableCopy] ?: [NSMutableDictionary new];
+ NSDictionary *auth = [[KBAuthManager shared] authorizationHeader];
+ [auth enumerateKeysAndObjectsUsingBlock:^(NSString *key, NSString *obj, BOOL *stop) { all[key] = obj; }];
if (contentType) all[@"Content-Type"] = contentType;
[headers enumerateKeysAndObjectsUsingBlock:^(NSString *key, NSString *obj, BOOL *stop) { all[key] = obj; }];
[all enumerateKeysAndObjectsUsingBlock:^(NSString *key, NSString *obj, BOOL *stop) { [req setValue:obj forHTTPHeaderField:key]; }];
diff --git a/keyBoard/keyBoard.entitlements b/keyBoard/keyBoard.entitlements
index a812db5..1cc6c2d 100644
--- a/keyBoard/keyBoard.entitlements
+++ b/keyBoard/keyBoard.entitlements
@@ -6,5 +6,9 @@
Default
+ keychain-access-groups
+
+ $(AppIdentifierPrefix)com.keyBoardst.shared
+