add apns supports.

This commit is contained in:
aria committed 2026-04-23 15:38:20 +09:00
1 parent 855d24bc64
commit 21c392db8f
6 files changed
+441 -30

No files matched your search

+1
View File
@@ -4,6 +4,7 @@ POST https://tokyohakuzencoltd-dev.outsystemsenterprise.com/BluememeCordovaCore/
Content-Type: application/json
{
"TargetUserId": 23,
"notification": {
"title": "Push Test",
"body": "This is a push test."
+23 -2
View File
@@ -136,7 +136,23 @@ BluememeUtils.getInitialNotification(success, error);
BluememeUtils.stopNotificationListener(success, error);
```
`getPushPermissionStatus` 只读取当前系统通知状态,不弹授权框。可用于填充后端 `NotificationsEnabled`。
`getPushPermissionStatus` 只读取当前系统通知状态,不弹授权框。可用于填充后端 `NotificationsEnabled`。iOS 返回 `authorizationStatus: "notDetermined"` 时,说明还没有触发系统通知授权弹窗,需要先调用 `requestPushPermission`,用户授权后再调用 `getFirebaseToken`。
iOS 获取 FCM token 的推荐顺序:
```js
BluememeUtils.requestPushPermission(function (permission) {
if (permission.enabled !== true) {
return;
}
BluememeUtils.getFirebaseToken(function (result) {
console.log(result.token);
}, error);
}, error);
```
iOS 上 FCM token 依赖 APNs device token。插件会在 `getFirebaseToken` 内等待 Cordova 收到 APNs token 后再向 Firebase 请求 FCM token;如果 APNs token 超时或注册失败,会返回 `APNS_TOKEN_NOT_AVAILABLE` 或 `APNS_TOKEN_FAILED`。
返回示例:
@@ -224,7 +240,10 @@ iOS WiFi:
Firebase:
- Android 13+ 需要通知权限,调用 `requestPushPermission`。
- iOS 需要 Push Notifications capability、Background Modes 中的 Remote notifications,以及 APNs key/certificate 已配置到 Firebase。
- iOS 需要 Apple Developer App ID 开启 Push Notifications,并使用重新生成的 provisioning profile 构建。
- 插件会向 Cordova iOS entitlements 写入 `aps-environment`:Debug 为 `development`,Release 为 `production`。仍建议在构建后的 IPA 中检查签名 entitlements 是否包含该 key。
- Firebase Console 需要在 Project Settings > Cloud Messaging > Apple app configuration 中上传 APNs Auth Key `.p8` 或 APNs certificate。推荐使用 APNs Auth Key。
- Background Modes 中的 Remote notifications 只影响后台静默推送/后台唤醒,不是获取 APNs token 和 FCM token 的前置条件。
- 如果项目中还有其他推送插件,也可能同时修改 `UNUserNotificationCenterDelegate` 或 Firebase service,需要在真机上确认通知打开事件。
Biometric:
@@ -249,6 +268,8 @@ Biometric:
- `INVALID_WIFI_OPTIONS`
- `WIFI_CONNECT_FAILED`
- `FIREBASE_NOT_CONFIGURED`
- `APNS_TOKEN_NOT_AVAILABLE`
- `APNS_TOKEN_FAILED`
- `FCM_TOKEN_FAILED`
- `BIOMETRIC_UNAVAILABLE`
- `AUTHENTICATION_ERROR`
+143 -21
View File
@@ -1,7 +1,9 @@
#!/usr/bin/env bash
set -u
ENTITLEMENT_KEY="com.apple.developer.networking.HotspotConfiguration"
HOTSPOT_ENTITLEMENT_KEY="com.apple.developer.networking.HotspotConfiguration"
APNS_ENTITLEMENT_KEY="aps-environment"
CHECK_SCRIPT_VERSION="2026-04-23.3"
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
TIMESTAMP="$(date '+%Y%m%d-%H%M%S')"
@@ -23,8 +25,9 @@ Usage:
$(basename "$0") /path/to/App.ipa
Checks:
1. embedded.mobileprovision contains ${ENTITLEMENT_KEY}=true
2. App executable appears to contain ${ENTITLEMENT_KEY}=true
1. embedded.mobileprovision contains ${HOTSPOT_ENTITLEMENT_KEY}=true
2. embedded.mobileprovision contains ${APNS_ENTITLEMENT_KEY}=development|production
3. App executable appears to contain both signed entitlements
Notes:
Linux cannot run Apple's codesign tool. The executable check is a best-effort
@@ -59,22 +62,32 @@ has_command() {
extract_plist_from_binary() {
local source_file="$1"
local target_file="$2"
local needle="${3:-}"
python3 - "$source_file" "$target_file" <<'PY'
python3 - "$source_file" "$target_file" "$needle" <<'PY'
import sys
from pathlib import Path
source = Path(sys.argv[1])
target = Path(sys.argv[2])
needle = sys.argv[3].encode("utf-8") if len(sys.argv) > 3 and sys.argv[3] else b""
data = source.read_bytes()
start = data.find(b"<?xml")
if start < 0:
needle_index = data.find(needle) if needle else -1
if needle_index >= 0:
start = data.rfind(b"<?xml", 0, needle_index)
if start < 0:
start = data.rfind(b"<plist", 0, needle_index)
end = data.find(b"</plist>", needle_index)
else:
start = data.find(b"<?xml")
if start < 0:
start = data.find(b"<plist")
end = data.find(b"</plist>", start if start >= 0 else 0)
if start < 0:
sys.exit(2)
end = data.find(b"</plist>", start)
if end < 0:
sys.exit(3)
@@ -97,7 +110,28 @@ key = sys.argv[2]
data = path.read_bytes()
try:
value = plistlib.loads(data).get(key)
plist = plistlib.loads(data)
values = []
def collect(obj):
if isinstance(obj, dict):
if key in obj:
values.append(obj.get(key))
entitlements = obj.get("Entitlements")
if isinstance(entitlements, dict) and key in entitlements:
values.append(entitlements.get(key))
for value in obj.values():
if isinstance(value, (dict, list, tuple)):
collect(value)
elif isinstance(obj, (list, tuple)):
for value in obj:
if isinstance(value, (dict, list, tuple)):
collect(value)
collect(plist)
value = values[0] if values else None
if value is True:
print("true")
sys.exit(0)
@@ -123,6 +157,63 @@ except Exception:
PY
}
check_plist_key_string_allowed() {
local plist_file="$1"
local key="$2"
shift 2
python3 - "$plist_file" "$key" "$@" <<'PY'
import plistlib
import re
import sys
from pathlib import Path
path = Path(sys.argv[1])
key = sys.argv[2]
allowed = set(sys.argv[3:])
data = path.read_bytes()
def finish(value):
if value is None:
print("missing")
sys.exit(1)
print(value)
if str(value) in allowed:
sys.exit(0)
sys.exit(2)
try:
plist = plistlib.loads(data)
values = []
def collect(obj):
if isinstance(obj, dict):
if key in obj:
values.append(obj.get(key))
entitlements = obj.get("Entitlements")
if isinstance(entitlements, dict) and key in entitlements:
values.append(entitlements.get(key))
for value in obj.values():
if isinstance(value, (dict, list, tuple)):
collect(value)
elif isinstance(obj, (list, tuple)):
for value in obj:
if isinstance(value, (dict, list, tuple)):
collect(value)
collect(plist)
finish(values[0] if values else None)
except Exception:
text = data.decode("utf-8", errors="ignore")
pattern = re.compile(
r"<key>\s*" + re.escape(key) + r"\s*</key>\s*<string>\s*([^<]+?)\s*</string>",
re.IGNORECASE,
)
match = pattern.search(text)
finish(match.group(1).strip() if match else None)
PY
}
find_main_executable() {
local app_dir="$1"
local info_plist="${app_dir}/Info.plist"
@@ -157,7 +248,8 @@ print_section() {
echo "==== $* ===="
}
echo "iOS Hotspot Configuration Signature Check"
echo "iOS Hotspot/APNs Signature Check"
echo "Checker version: ${CHECK_SCRIPT_VERSION}"
echo "Started: $(date -Is)"
echo "Script: ${BASH_SOURCE[0]}"
echo "Log: ${LOG_FILE}"
@@ -246,43 +338,71 @@ if [[ "${PROFILE_EXTRACTED}" -eq 0 ]]; then
fi
fi
PROFILE_RESULT="$(check_plist_key_true "${PROFILE_PLIST}" "${ENTITLEMENT_KEY}" 2>&1)"
PROFILE_RESULT="$(check_plist_key_true "${PROFILE_PLIST}" "${HOTSPOT_ENTITLEMENT_KEY}" 2>&1)"
PROFILE_STATUS=$?
echo "Profile entitlement value: ${PROFILE_RESULT}"
echo "Profile Hotspot entitlement value: ${PROFILE_RESULT}"
if [[ "${PROFILE_STATUS}" -eq 0 ]]; then
pass "Provisioning profile contains ${ENTITLEMENT_KEY}=true"
pass "Provisioning profile contains ${HOTSPOT_ENTITLEMENT_KEY}=true"
else
fail "Provisioning profile does not contain ${ENTITLEMENT_KEY}=true"
fail "Provisioning profile does not contain ${HOTSPOT_ENTITLEMENT_KEY}=true"
fi
PROFILE_APNS_RESULT="$(check_plist_key_string_allowed "${PROFILE_PLIST}" "${APNS_ENTITLEMENT_KEY}" development production 2>&1)"
PROFILE_APNS_STATUS=$?
echo "Profile APNs entitlement value: ${PROFILE_APNS_RESULT}"
if [[ "${PROFILE_APNS_STATUS}" -eq 0 ]]; then
pass "Provisioning profile contains ${APNS_ENTITLEMENT_KEY}=${PROFILE_APNS_RESULT}"
else
fail "Provisioning profile does not contain ${APNS_ENTITLEMENT_KEY}=development|production"
fi
echo
echo "Profile entitlement context:"
grep -n -A2 -B1 "${ENTITLEMENT_KEY}" "${PROFILE_PLIST}" || true
grep -n -A2 -B1 "${HOTSPOT_ENTITLEMENT_KEY}" "${PROFILE_PLIST}" || true
grep -n -A2 -B1 "${APNS_ENTITLEMENT_KEY}" "${PROFILE_PLIST}" || true
print_section "Signed App Entitlements"
echo "Linux cannot run Apple's codesign. This is a best-effort scan of the app executable."
if grep -a -q "${ENTITLEMENT_KEY}" "${EXECUTABLE_PATH}"; then
pass "Executable contains entitlement key string: ${ENTITLEMENT_KEY}"
if grep -a -q "${HOTSPOT_ENTITLEMENT_KEY}" "${EXECUTABLE_PATH}"; then
pass "Executable contains entitlement key string: ${HOTSPOT_ENTITLEMENT_KEY}"
else
fail "Executable does not contain entitlement key string. Signed app entitlements likely do not include Hotspot Configuration."
fi
if grep -a -q "${APNS_ENTITLEMENT_KEY}" "${EXECUTABLE_PATH}"; then
pass "Executable contains entitlement key string: ${APNS_ENTITLEMENT_KEY}"
else
fail "Executable does not contain entitlement key string. Signed app entitlements likely do not include APNs aps-environment."
fi
EXEC_ENTITLEMENTS_PLIST="${WORK_DIR}/signed-entitlements-heuristic.plist"
if extract_plist_from_binary "${EXECUTABLE_PATH}" "${EXEC_ENTITLEMENTS_PLIST}"; then
if extract_plist_from_binary "${EXECUTABLE_PATH}" "${EXEC_ENTITLEMENTS_PLIST}" "${HOTSPOT_ENTITLEMENT_KEY}"; then
echo "Extracted executable plist candidate: ${EXEC_ENTITLEMENTS_PLIST}"
EXEC_RESULT="$(check_plist_key_true "${EXEC_ENTITLEMENTS_PLIST}" "${ENTITLEMENT_KEY}" 2>&1)"
EXEC_RESULT="$(check_plist_key_true "${EXEC_ENTITLEMENTS_PLIST}" "${HOTSPOT_ENTITLEMENT_KEY}" 2>&1)"
EXEC_STATUS=$?
echo "Executable entitlement value: ${EXEC_RESULT}"
echo "Executable Hotspot entitlement value: ${EXEC_RESULT}"
if [[ "${EXEC_STATUS}" -eq 0 ]]; then
pass "Executable plist candidate contains ${ENTITLEMENT_KEY}=true"
pass "Executable plist candidate contains ${HOTSPOT_ENTITLEMENT_KEY}=true"
else
warn "Executable contains entitlement key, but extracted plist candidate did not prove value=true."
warn "Use macOS codesign for authoritative validation."
fi
EXEC_APNS_RESULT="$(check_plist_key_string_allowed "${EXEC_ENTITLEMENTS_PLIST}" "${APNS_ENTITLEMENT_KEY}" development production 2>&1)"
EXEC_APNS_STATUS=$?
echo "Executable APNs entitlement value: ${EXEC_APNS_RESULT}"
if [[ "${EXEC_APNS_STATUS}" -eq 0 ]]; then
pass "Executable plist candidate contains ${APNS_ENTITLEMENT_KEY}=${EXEC_APNS_RESULT}"
else
warn "Executable contains APNs entitlement key, but extracted plist candidate did not prove value=development|production."
warn "Use macOS codesign for authoritative validation."
fi
echo
echo "Executable entitlement context:"
grep -n -A2 -B1 "${ENTITLEMENT_KEY}" "${EXEC_ENTITLEMENTS_PLIST}" || true
grep -n -A2 -B1 "${HOTSPOT_ENTITLEMENT_KEY}" "${EXEC_ENTITLEMENTS_PLIST}" || true
grep -n -A2 -B1 "${APNS_ENTITLEMENT_KEY}" "${EXEC_ENTITLEMENTS_PLIST}" || true
else
warn "Could not extract XML plist from executable. Key string was present, but value=true was not proven."
warn "Use macOS codesign for authoritative validation."
@@ -290,7 +410,9 @@ fi
print_section "Summary"
pass "Provisioning profile Hotspot entitlement: true"
pass "Provisioning profile APNs entitlement: ${PROFILE_APNS_RESULT}"
pass "Executable Hotspot entitlement key: present"
pass "Executable APNs entitlement key: present"
echo
echo "Full log written to:"
echo " ${LOG_FILE}"
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "bluememe-utils",
"version": "0.1.11",
"version": "0.1.12",
"description": "Cordova utilities for WiFi, Firebase Cloud Messaging, and biometric unlock for OutSystems 11 apps.",
"license": "MIT",
"files": [
+7 -1
View File
@@ -2,7 +2,7 @@
<plugin xmlns="http://apache.org/cordova/ns/plugins/1.0"
xmlns:android="http://schemas.android.com/apk/res/android"
id="bluememe-utils"
version="0.1.11">
version="0.1.12">
<name>bluememe-utils</name>
<description>Cordova utilities for WiFi, Firebase Cloud Messaging, and biometric unlock.</description>
<license>MIT</license>
@@ -104,6 +104,12 @@
<config-file target="*-Release.plist" parent="com.apple.developer.networking.HotspotConfiguration">
<true />
</config-file>
<config-file target="*-Debug.plist" parent="aps-environment">
<string>development</string>
</config-file>
<config-file target="*-Release.plist" parent="aps-environment">
<string>production</string>
</config-file>
<header-file src="src/ios/CDVBluememeUtils.h" />
<source-file src="src/ios/CDVBluememeUtils.m" />
+266 -5
View File
@@ -8,12 +8,18 @@
#import <UIKit/UIKit.h>
static NSString * const BluememeDeviceKeyDefaultsKey = @"bluememe_utils_device_key";
static NSString * const BluememeUtilsPluginVersion = @"0.1.11";
static NSString * const BluememeUtilsPluginVersion = @"0.1.12";
static NSString * const BluememeCordovaRemoteNotificationName = @"CDVRemoteNotification";
static NSString * const BluememeCordovaRemoteNotificationErrorName = @"CDVRemoteNotificationError";
static NSTimeInterval const BluememeAPNSTokenTimeoutSeconds = 10.0;
@interface CDVBluememeUtils ()
@property (nonatomic, copy) NSString *notificationCallbackId;
@property (nonatomic, strong) NSDictionary *initialNotification;
@property (nonatomic, weak) id<UNUserNotificationCenterDelegate> previousNotificationDelegate;
@property (nonatomic, strong) NSData *apnsDeviceToken;
@property (nonatomic, strong) NSError *apnsRegistrationError;
@property (nonatomic, strong) CDVInvokedUrlCommand *pendingFirebaseTokenCommand;
@end
@implementation CDVBluememeUtils
@@ -29,6 +35,20 @@ static NSString * const BluememeUtilsPluginVersion = @"0.1.11";
self.previousNotificationDelegate = center.delegate;
center.delegate = self;
}
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(remoteNotificationRegistered:)
name:BluememeCordovaRemoteNotificationName
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(remoteNotificationRegistrationFailed:)
name:BluememeCordovaRemoteNotificationErrorName
object:nil];
}
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
- (void)saveWifi:(CDVInvokedUrlCommand *)command
@@ -159,14 +179,31 @@ static NSString * const BluememeUtilsPluginVersion = @"0.1.11";
return;
}
[[UIApplication sharedApplication] registerForRemoteNotifications];
[self sendSuccessForCommand:command dictionary:@{@"type": @"notification", @"granted": @(granted), @"missing": granted ? @[] : @[@"notification"]}];
[[UNUserNotificationCenter currentNotificationCenter] getNotificationSettingsWithCompletionHandler:^(UNNotificationSettings *settings) {
dispatch_async(dispatch_get_main_queue(), ^{
NSMutableDictionary *result = [[self dictionaryFromNotificationSettings:settings] mutableCopy];
result[@"granted"] = @(granted);
result[@"registeredForRemoteNotifications"] = @([[UIApplication sharedApplication] isRegisteredForRemoteNotifications]);
result[@"apnsTokenAvailable"] = @(self.apnsDeviceToken.length > 0);
[self sendSuccessForCommand:command dictionary:result];
});
}];
});
}];
} else {
UIUserNotificationSettings *settings = [UIUserNotificationSettings settingsForTypes:(UIUserNotificationTypeAlert | UIUserNotificationTypeBadge | UIUserNotificationTypeSound) categories:nil];
[[UIApplication sharedApplication] registerUserNotificationSettings:settings];
[[UIApplication sharedApplication] registerForRemoteNotifications];
[self sendSuccessForCommand:command dictionary:@{@"type": @"notification", @"granted": @YES, @"missing": @[]}];
[self sendSuccessForCommand:command dictionary:@{
@"type": @"notification",
@"platform": @"iOS",
@"enabled": @YES,
@"granted": @YES,
@"authorizationStatus": @"authorized",
@"registeredForRemoteNotifications": @([[UIApplication sharedApplication] isRegisteredForRemoteNotifications]),
@"apnsTokenAvailable": @(self.apnsDeviceToken.length > 0),
@"missing": @[]
}];
}
}
@@ -190,6 +227,8 @@ static NSString * const BluememeUtilsPluginVersion = @"0.1.11";
@"alertEnabled": @((settings.types & UIUserNotificationTypeAlert) != 0),
@"soundEnabled": @((settings.types & UIUserNotificationTypeSound) != 0),
@"badgeEnabled": @((settings.types & UIUserNotificationTypeBadge) != 0),
@"registeredForRemoteNotifications": @([[UIApplication sharedApplication] isRegisteredForRemoteNotifications]),
@"apnsTokenAvailable": @(self.apnsDeviceToken.length > 0),
@"missing": enabled ? @[] : @[@"notification"]
}];
}
@@ -204,13 +243,102 @@ static NSString * const BluememeUtilsPluginVersion = @"0.1.11";
}
[FIRMessaging messaging].delegate = self;
if (@available(iOS 10.0, *)) {
[[UNUserNotificationCenter currentNotificationCenter] getNotificationSettingsWithCompletionHandler:^(UNNotificationSettings *settings) {
dispatch_async(dispatch_get_main_queue(), ^{
if (![self notificationAuthorizationAllowsFirebaseToken:settings.authorizationStatus]) {
NSMutableDictionary *details = [[self dictionaryFromNotificationSettings:settings] mutableCopy];
details[@"apnsTokenAvailable"] = @(self.apnsDeviceToken.length > 0);
details[@"registeredForRemoteNotifications"] = @([[UIApplication sharedApplication] isRegisteredForRemoteNotifications]);
[self sendErrorForCommand:command
code:@"PERMISSION_REQUIRED"
message:@"Call requestPushPermission and wait for notification authorization before getFirebaseToken."
details:details];
return;
}
[self registerForRemoteNotificationsAndFetchFirebaseTokenForCommand:command];
});
}];
return;
}
[self registerForRemoteNotificationsAndFetchFirebaseTokenForCommand:command];
}
- (void)registerForRemoteNotificationsAndFetchFirebaseTokenForCommand:(CDVInvokedUrlCommand *)command
{
if (self.apnsDeviceToken.length > 0) {
[FIRMessaging messaging].APNSToken = self.apnsDeviceToken;
[self fetchFirebaseTokenForCommand:command];
return;
}
if (self.pendingFirebaseTokenCommand != nil) {
[self sendErrorForCommand:self.pendingFirebaseTokenCommand
code:@"FCM_TOKEN_REQUEST_REPLACED"
message:@"A newer getFirebaseToken request replaced this pending request."
details:@{
@"apnsTokenAvailable": @NO,
@"registeredForRemoteNotifications": @([[UIApplication sharedApplication] isRegisteredForRemoteNotifications])
}];
}
self.pendingFirebaseTokenCommand = command;
self.apnsRegistrationError = nil;
[[UIApplication sharedApplication] registerForRemoteNotifications];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(BluememeAPNSTokenTimeoutSeconds * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
if (self.pendingFirebaseTokenCommand != command) {
return;
}
self.pendingFirebaseTokenCommand = nil;
if (self.apnsRegistrationError != nil) {
[self sendErrorForCommand:command
code:@"APNS_TOKEN_FAILED"
message:(self.apnsRegistrationError.localizedDescription ? self.apnsRegistrationError.localizedDescription : @"APNs registration failed.")
details:[self detailsForNativeError:self.apnsRegistrationError includeAPNSState:YES]];
return;
}
[self sendErrorForCommand:command
code:@"APNS_TOKEN_NOT_AVAILABLE"
message:@"APNs device token was not received before fetching the FCM token. Check Push Notifications capability, aps-environment entitlement, provisioning profile, and Firebase APNs key configuration."
details:@{
@"apnsTokenAvailable": @NO,
@"registeredForRemoteNotifications": @([[UIApplication sharedApplication] isRegisteredForRemoteNotifications]),
@"timeoutSeconds": @(BluememeAPNSTokenTimeoutSeconds)
}];
});
}
- (void)fetchFirebaseTokenForCommand:(CDVInvokedUrlCommand *)command
{
if (self.apnsDeviceToken.length > 0) {
[FIRMessaging messaging].APNSToken = self.apnsDeviceToken;
}
[[FIRMessaging messaging] tokenWithCompletion:^(NSString *token, NSError *error) {
dispatch_async(dispatch_get_main_queue(), ^{
if (error != nil || token.length == 0) {
[self sendErrorForCommand:command code:@"FCM_TOKEN_FAILED" message:(error.localizedDescription ? error.localizedDescription : @"Failed to get Firebase token.")];
NSMutableDictionary *details = [NSMutableDictionary dictionary];
details[@"apnsTokenAvailable"] = @(self.apnsDeviceToken.length > 0);
details[@"registeredForRemoteNotifications"] = @([[UIApplication sharedApplication] isRegisteredForRemoteNotifications]);
if (error != nil) {
[details addEntriesFromDictionary:[self detailsForNativeError:error includeAPNSState:NO]];
}
[self sendErrorForCommand:command
code:@"FCM_TOKEN_FAILED"
message:(error.localizedDescription ? error.localizedDescription : @"Failed to get Firebase token.")
details:details];
return;
}
[self sendSuccessForCommand:command dictionary:@{@"token": token}];
[self sendSuccessForCommand:command dictionary:@{
@"token": token,
@"platform": @"iOS",
@"apnsTokenAvailable": @(self.apnsDeviceToken.length > 0),
@"registeredForRemoteNotifications": @([[UIApplication sharedApplication] isRegisteredForRemoteNotifications])
}];
[self sendEvent:@{@"type": @"token", @"token": token}];
});
}];
@@ -716,6 +844,8 @@ static NSString * const BluememeUtilsPluginVersion = @"0.1.11";
@"alertEnabled": @(alertEnabled),
@"soundEnabled": @(soundEnabled),
@"badgeEnabled": @(badgeEnabled),
@"registeredForRemoteNotifications": @([[UIApplication sharedApplication] isRegisteredForRemoteNotifications]),
@"apnsTokenAvailable": @(self.apnsDeviceToken.length > 0),
@"missing": missing
};
}
@@ -746,6 +876,137 @@ static NSString * const BluememeUtilsPluginVersion = @"0.1.11";
return @"unknown";
}
- (BOOL)notificationAuthorizationAllowsFirebaseToken:(UNAuthorizationStatus)status API_AVAILABLE(ios(10.0))
{
if (status == UNAuthorizationStatusAuthorized) {
return YES;
}
if (@available(iOS 12.0, *)) {
if (status == UNAuthorizationStatusProvisional) {
return YES;
}
}
if (@available(iOS 14.0, *)) {
if (status == UNAuthorizationStatusEphemeral) {
return YES;
}
}
return NO;
}
- (void)remoteNotificationRegistered:(NSNotification *)notification
{
NSData *tokenData = [self dataFromAPNSTokenObject:notification.object];
if (tokenData.length == 0) {
tokenData = [self dataFromAPNSTokenObject:notification.userInfo[@"deviceToken"]];
}
if (tokenData.length == 0) {
return;
}
self.apnsDeviceToken = tokenData;
self.apnsRegistrationError = nil;
if ([FIRApp defaultApp] != nil) {
[FIRMessaging messaging].APNSToken = tokenData;
}
if (self.pendingFirebaseTokenCommand != nil) {
CDVInvokedUrlCommand *command = self.pendingFirebaseTokenCommand;
self.pendingFirebaseTokenCommand = nil;
[self fetchFirebaseTokenForCommand:command];
}
}
- (void)remoteNotificationRegistrationFailed:(NSNotification *)notification
{
NSError *error = nil;
if ([notification.object isKindOfClass:[NSError class]]) {
error = notification.object;
} else if ([notification.userInfo[@"error"] isKindOfClass:[NSError class]]) {
error = notification.userInfo[@"error"];
}
self.apnsRegistrationError = error;
if (self.pendingFirebaseTokenCommand != nil) {
CDVInvokedUrlCommand *command = self.pendingFirebaseTokenCommand;
self.pendingFirebaseTokenCommand = nil;
[self sendErrorForCommand:command
code:@"APNS_TOKEN_FAILED"
message:(error.localizedDescription ? error.localizedDescription : @"APNs registration failed.")
details:[self detailsForNativeError:error includeAPNSState:YES]];
}
}
- (NSData *)dataFromAPNSTokenObject:(id)object
{
if ([object isKindOfClass:[NSData class]]) {
return object;
}
if (![object isKindOfClass:[NSString class]]) {
return nil;
}
NSString *tokenString = (NSString *)object;
NSRange angleStart = [tokenString rangeOfString:@"<"];
NSRange angleEnd = [tokenString rangeOfString:@">" options:NSBackwardsSearch];
if (angleStart.location != NSNotFound && angleEnd.location != NSNotFound && angleEnd.location > angleStart.location) {
tokenString = [tokenString substringWithRange:NSMakeRange(angleStart.location + 1, angleEnd.location - angleStart.location - 1)];
} else {
NSRange bytesRange = [tokenString rangeOfString:@"bytes = 0x"];
if (bytesRange.location != NSNotFound) {
tokenString = [tokenString substringFromIndex:bytesRange.location + bytesRange.length];
}
}
NSMutableString *hex = [NSMutableString string];
NSCharacterSet *hexCharacters = [NSCharacterSet characterSetWithCharactersInString:@"0123456789abcdefABCDEF"];
for (NSUInteger index = 0; index < tokenString.length; index++) {
unichar character = [tokenString characterAtIndex:index];
if ([hexCharacters characterIsMember:character]) {
[hex appendFormat:@"%C", character];
} else if ([tokenString rangeOfString:@"bytes = 0x"].location != NSNotFound) {
break;
}
}
if (hex.length == 0 || (hex.length % 2) != 0) {
return nil;
}
NSMutableData *data = [NSMutableData dataWithCapacity:(hex.length / 2)];
for (NSUInteger index = 0; index < hex.length; index += 2) {
NSString *byteString = [hex substringWithRange:NSMakeRange(index, 2)];
unsigned int byte = 0;
NSScanner *scanner = [NSScanner scannerWithString:byteString];
if (![scanner scanHexInt:&byte]) {
return nil;
}
unsigned char value = (unsigned char)byte;
[data appendBytes:&value length:1];
}
return data;
}
- (NSDictionary *)detailsForNativeError:(NSError *)error includeAPNSState:(BOOL)includeAPNSState
{
NSMutableDictionary *details = [NSMutableDictionary dictionary];
if (includeAPNSState) {
details[@"apnsTokenAvailable"] = @(self.apnsDeviceToken.length > 0);
details[@"registeredForRemoteNotifications"] = @([[UIApplication sharedApplication] isRegisteredForRemoteNotifications]);
}
if (error.domain.length > 0) {
details[@"nativeDomain"] = error.domain;
}
if (error != nil) {
details[@"nativeCode"] = @(error.code);
}
if (error.localizedDescription.length > 0) {
details[@"nativeError"] = error.localizedDescription;
}
return details;
}
- (BOOL)ensureFirebaseConfigured:(NSError **)error
{
if ([FIRApp defaultApp] != nil) {