capacitor-push-signal 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CapacitorPushSignal.podspec +19 -0
- package/Package.swift +36 -0
- package/README.md +271 -0
- package/android/build.gradle +66 -0
- package/android/src/main/AndroidManifest.xml +19 -0
- package/android/src/main/java/com/antonseagull/cap/push/signal/PushModels.kt +15 -0
- package/android/src/main/java/com/antonseagull/cap/push/signal/PushSignalCenter.kt +397 -0
- package/android/src/main/java/com/antonseagull/cap/push/signal/PushSignalInitProvider.kt +34 -0
- package/android/src/main/java/com/antonseagull/cap/push/signal/PushSignalMessagingService.kt +20 -0
- package/android/src/main/java/com/antonseagull/cap/push/signal/PushSignalPlugin.kt +85 -0
- package/android/src/main/res/.gitkeep +0 -0
- package/dist/docs.json +275 -0
- package/dist/esm/definitions.d.ts +19 -0
- package/dist/esm/definitions.js +2 -0
- package/dist/esm/definitions.js.map +1 -0
- package/dist/esm/index.d.ts +2 -0
- package/dist/esm/index.js +2 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/pushSignal.d.ts +8 -0
- package/dist/esm/pushSignal.js +76 -0
- package/dist/esm/pushSignal.js.map +1 -0
- package/dist/esm/types.d.ts +20 -0
- package/dist/esm/types.js +2 -0
- package/dist/esm/types.js.map +1 -0
- package/dist/esm/web.d.ts +8 -0
- package/dist/esm/web.js +13 -0
- package/dist/esm/web.js.map +1 -0
- package/dist/plugin.cjs.js +100 -0
- package/dist/plugin.cjs.js.map +1 -0
- package/dist/plugin.js +103 -0
- package/dist/plugin.js.map +1 -0
- package/ios/Sources/PushSignalCore/PushSignalCenter.m +583 -0
- package/ios/Sources/PushSignalCore/PushSignalLaunchStore.m +38 -0
- package/ios/Sources/PushSignalCore/include/PushSignalCenter.h +26 -0
- package/ios/Sources/PushSignalCore/include/PushSignalLaunchStore.h +21 -0
- package/ios/Sources/PushSignalPlugin/PushSignalPlugin.swift +66 -0
- package/ios/Tests/PushSignalPluginTests/PushSignalTests.swift +9 -0
- package/package.json +80 -0
package/dist/plugin.js
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
var capacitorPushSignal = (function (exports, core) {
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const PushSignal = core.registerPlugin('PushSignal', {
|
|
5
|
+
web: () => Promise.resolve().then(function () { return web; }).then((m) => new m.PushSignalWeb()),
|
|
6
|
+
});
|
|
7
|
+
const messageListeners = new Set();
|
|
8
|
+
const pressListeners = new Set();
|
|
9
|
+
let nativeCallbacksBound = false;
|
|
10
|
+
function normalizeMessage(raw) {
|
|
11
|
+
const data = {};
|
|
12
|
+
if (raw.data && typeof raw.data === 'object') {
|
|
13
|
+
for (const [key, value] of Object.entries(raw.data)) {
|
|
14
|
+
if (value == null) {
|
|
15
|
+
continue;
|
|
16
|
+
}
|
|
17
|
+
data[key] = typeof value === 'string' ? value : String(value);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return {
|
|
21
|
+
id: raw.id,
|
|
22
|
+
title: raw.title,
|
|
23
|
+
body: raw.body,
|
|
24
|
+
data,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
function normalizeCredentials(raw) {
|
|
28
|
+
return {
|
|
29
|
+
platform: raw.platform,
|
|
30
|
+
token: raw.token,
|
|
31
|
+
environment: raw.environment,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
function bindNativeCallbacks() {
|
|
35
|
+
if (nativeCallbacksBound) {
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
nativeCallbacksBound = true;
|
|
39
|
+
void PushSignal.addListener('onMessage', (raw) => {
|
|
40
|
+
const message = normalizeMessage(raw);
|
|
41
|
+
for (const listener of [...messageListeners]) {
|
|
42
|
+
try {
|
|
43
|
+
Promise.resolve(listener(message)).then(() => undefined, () => undefined);
|
|
44
|
+
}
|
|
45
|
+
catch (_a) {
|
|
46
|
+
// Ignore listener failures so one bad subscriber cannot break delivery.
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
void PushSignal.addListener('onNotificationPress', (raw) => {
|
|
51
|
+
const message = normalizeMessage(raw);
|
|
52
|
+
pressListeners.forEach((listener) => listener(message));
|
|
53
|
+
});
|
|
54
|
+
void PushSignal.startListening();
|
|
55
|
+
}
|
|
56
|
+
function initialize(config = {}) {
|
|
57
|
+
return PushSignal.initialize(config);
|
|
58
|
+
}
|
|
59
|
+
function getCredentials() {
|
|
60
|
+
return PushSignal.getCredentials().then(normalizeCredentials);
|
|
61
|
+
}
|
|
62
|
+
function onMessage(listener) {
|
|
63
|
+
bindNativeCallbacks();
|
|
64
|
+
messageListeners.add(listener);
|
|
65
|
+
return () => {
|
|
66
|
+
messageListeners.delete(listener);
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
function onNotificationPress(listener) {
|
|
70
|
+
bindNativeCallbacks();
|
|
71
|
+
pressListeners.add(listener);
|
|
72
|
+
return () => {
|
|
73
|
+
pressListeners.delete(listener);
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
bindNativeCallbacks();
|
|
77
|
+
|
|
78
|
+
class PushSignalWeb extends core.WebPlugin {
|
|
79
|
+
async initialize(config) {
|
|
80
|
+
}
|
|
81
|
+
async getCredentials() {
|
|
82
|
+
throw this.unimplemented('Push credentials are not supported on web');
|
|
83
|
+
}
|
|
84
|
+
async startListening() {
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
var web = /*#__PURE__*/Object.freeze({
|
|
90
|
+
__proto__: null,
|
|
91
|
+
PushSignalWeb: PushSignalWeb
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
exports.PushSignal = PushSignal;
|
|
95
|
+
exports.getCredentials = getCredentials;
|
|
96
|
+
exports.initialize = initialize;
|
|
97
|
+
exports.onMessage = onMessage;
|
|
98
|
+
exports.onNotificationPress = onNotificationPress;
|
|
99
|
+
|
|
100
|
+
return exports;
|
|
101
|
+
|
|
102
|
+
})({}, capacitorExports);
|
|
103
|
+
//# sourceMappingURL=plugin.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin.js","sources":["esm/pushSignal.js","esm/web.js"],"sourcesContent":["import { registerPlugin } from '@capacitor/core';\nconst PushSignal = registerPlugin('PushSignal', {\n web: () => import('./web').then((m) => new m.PushSignalWeb()),\n});\nconst messageListeners = new Set();\nconst pressListeners = new Set();\nlet nativeCallbacksBound = false;\nfunction normalizeMessage(raw) {\n const data = {};\n if (raw.data && typeof raw.data === 'object') {\n for (const [key, value] of Object.entries(raw.data)) {\n if (value == null) {\n continue;\n }\n data[key] = typeof value === 'string' ? value : String(value);\n }\n }\n return {\n id: raw.id,\n title: raw.title,\n body: raw.body,\n data,\n };\n}\nfunction normalizeCredentials(raw) {\n return {\n platform: raw.platform,\n token: raw.token,\n environment: raw.environment,\n };\n}\nfunction bindNativeCallbacks() {\n if (nativeCallbacksBound) {\n return;\n }\n nativeCallbacksBound = true;\n void PushSignal.addListener('onMessage', (raw) => {\n const message = normalizeMessage(raw);\n for (const listener of [...messageListeners]) {\n try {\n Promise.resolve(listener(message)).then(() => undefined, () => undefined);\n }\n catch (_a) {\n // Ignore listener failures so one bad subscriber cannot break delivery.\n }\n }\n });\n void PushSignal.addListener('onNotificationPress', (raw) => {\n const message = normalizeMessage(raw);\n pressListeners.forEach((listener) => listener(message));\n });\n void PushSignal.startListening();\n}\nexport function initialize(config = {}) {\n return PushSignal.initialize(config);\n}\nexport function getCredentials() {\n return PushSignal.getCredentials().then(normalizeCredentials);\n}\nexport function onMessage(listener) {\n bindNativeCallbacks();\n messageListeners.add(listener);\n return () => {\n messageListeners.delete(listener);\n };\n}\nexport function onNotificationPress(listener) {\n bindNativeCallbacks();\n pressListeners.add(listener);\n return () => {\n pressListeners.delete(listener);\n };\n}\nbindNativeCallbacks();\nexport { PushSignal };\n//# sourceMappingURL=pushSignal.js.map","import { WebPlugin } from '@capacitor/core';\nexport class PushSignalWeb extends WebPlugin {\n async initialize(config) {\n void config;\n }\n async getCredentials() {\n throw this.unimplemented('Push credentials are not supported on web');\n }\n async startListening() {\n return;\n }\n}\n//# sourceMappingURL=web.js.map"],"names":["registerPlugin","WebPlugin"],"mappings":"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"}
|
|
@@ -0,0 +1,583 @@
|
|
|
1
|
+
#import "PushSignalCenter.h"
|
|
2
|
+
#import "PushSignalLaunchStore.h"
|
|
3
|
+
#import <objc/runtime.h>
|
|
4
|
+
#import <UIKit/UIKit.h>
|
|
5
|
+
|
|
6
|
+
@interface PushSignalAppDelegateHook : NSObject
|
|
7
|
+
@end
|
|
8
|
+
|
|
9
|
+
@interface UNUserNotificationCenter (PushSignal)
|
|
10
|
+
- (void)pushSignal_setDelegate:(id<UNUserNotificationCenterDelegate>)delegate;
|
|
11
|
+
@end
|
|
12
|
+
|
|
13
|
+
@implementation PushSignalCenter {
|
|
14
|
+
NSLock *_lock;
|
|
15
|
+
BOOL _didInstall;
|
|
16
|
+
BOOL _didSwizzleAppDelegate;
|
|
17
|
+
BOOL _didSwizzleNotificationCenter;
|
|
18
|
+
NSString *_deviceToken;
|
|
19
|
+
NSError *_registrationError;
|
|
20
|
+
NSMutableArray<void (^)(NSString *_Nullable, NSError *_Nullable)> *_tokenWaiters;
|
|
21
|
+
NSDictionary *_pendingPress;
|
|
22
|
+
NSMutableArray<NSDictionary *> *_pendingMessages;
|
|
23
|
+
NSDictionary *_pendingLaunchOptions;
|
|
24
|
+
BOOL _didCaptureLaunchNotification;
|
|
25
|
+
NSMutableDictionary<NSString *, NSDate *> *_recentMessageIds;
|
|
26
|
+
__weak id<UNUserNotificationCenterDelegate> _forwardingDelegate;
|
|
27
|
+
void (^_onMessage)(NSDictionary *);
|
|
28
|
+
void (^_onNotificationPress)(NSDictionary *);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
+ (instancetype)shared {
|
|
32
|
+
static PushSignalCenter *sharedInstance = nil;
|
|
33
|
+
static dispatch_once_t onceToken;
|
|
34
|
+
dispatch_once(&onceToken, ^{
|
|
35
|
+
sharedInstance = [[PushSignalCenter alloc] init];
|
|
36
|
+
});
|
|
37
|
+
return sharedInstance;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
+ (void)installEarly {
|
|
41
|
+
[[PushSignalCenter shared] install];
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
+ (void)bootstrapWithLaunchOptions:(NSDictionary *)launchOptions {
|
|
45
|
+
PushSignalCenter *center = [PushSignalCenter shared];
|
|
46
|
+
center->_pendingLaunchOptions = [launchOptions copy];
|
|
47
|
+
[center install];
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
- (instancetype)init {
|
|
51
|
+
if (self = [super init]) {
|
|
52
|
+
_lock = [[NSLock alloc] init];
|
|
53
|
+
_tokenWaiters = [NSMutableArray array];
|
|
54
|
+
_pendingMessages = [NSMutableArray array];
|
|
55
|
+
_recentMessageIds = [NSMutableDictionary dictionary];
|
|
56
|
+
}
|
|
57
|
+
return self;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
- (void)install {
|
|
61
|
+
if ([NSThread isMainThread]) {
|
|
62
|
+
[self installOnMain];
|
|
63
|
+
} else {
|
|
64
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
65
|
+
[self installOnMain];
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
- (void)installOnMain {
|
|
71
|
+
[self swizzleNotificationCenterDelegateIfNeeded];
|
|
72
|
+
|
|
73
|
+
if (_didInstall) {
|
|
74
|
+
[UNUserNotificationCenter currentNotificationCenter].delegate = self;
|
|
75
|
+
[self swizzleAppDelegate];
|
|
76
|
+
[self captureLaunchNotificationIfNeeded];
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
_didInstall = YES;
|
|
81
|
+
[UNUserNotificationCenter currentNotificationCenter].delegate = self;
|
|
82
|
+
[self swizzleAppDelegate];
|
|
83
|
+
[self captureLaunchNotificationIfNeeded];
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
- (void)rememberForwardingDelegate:(id<UNUserNotificationCenterDelegate>)delegate {
|
|
87
|
+
if (delegate != nil && delegate != self) {
|
|
88
|
+
_forwardingDelegate = delegate;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
- (void)setOnMessage:(void (^)(NSDictionary *))callback {
|
|
93
|
+
_onMessage = [callback copy];
|
|
94
|
+
[self flushPendingMessages];
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
- (void)setOnNotificationPress:(void (^)(NSDictionary *))callback {
|
|
98
|
+
_onNotificationPress = [callback copy];
|
|
99
|
+
[self flushPendingPress];
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
- (void)fetchCredentialsWithResolver:(void (^)(NSDictionary *))resolve
|
|
103
|
+
rejecter:(void (^)(NSError *))reject {
|
|
104
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
105
|
+
[self installOnMain];
|
|
106
|
+
[[UIApplication sharedApplication] registerForRemoteNotifications];
|
|
107
|
+
|
|
108
|
+
[self waitForTokenWithCompletion:^(NSString *token, NSError *error) {
|
|
109
|
+
if (error != nil) {
|
|
110
|
+
reject(error);
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
NSMutableDictionary *credentials = [NSMutableDictionary dictionary];
|
|
114
|
+
credentials[@"platform"] = @"ios";
|
|
115
|
+
credentials[@"token"] = token ?: @"";
|
|
116
|
+
credentials[@"environment"] = [self currentEnvironment];
|
|
117
|
+
resolve(credentials);
|
|
118
|
+
}];
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
- (void)handleDeviceToken:(NSData *)deviceToken {
|
|
123
|
+
const unsigned char *bytes = (const unsigned char *)deviceToken.bytes;
|
|
124
|
+
NSMutableString *token = [NSMutableString stringWithCapacity:deviceToken.length * 2];
|
|
125
|
+
for (NSUInteger i = 0; i < deviceToken.length; i++) {
|
|
126
|
+
[token appendFormat:@"%02x", bytes[i]];
|
|
127
|
+
}
|
|
128
|
+
[self finishRegistrationWithToken:token error:nil];
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
- (void)handleRegistrationError:(NSError *)error {
|
|
132
|
+
[self finishRegistrationWithToken:nil error:error];
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
- (void)userNotificationCenter:(UNUserNotificationCenter *)center
|
|
136
|
+
willPresentNotification:(UNNotification *)notification
|
|
137
|
+
withCompletionHandler:(void (^)(UNNotificationPresentationOptions))completionHandler {
|
|
138
|
+
[self deliverMessage:[PushSignalCenter messageFromNotification:notification]];
|
|
139
|
+
[self forwardWillPresent:center
|
|
140
|
+
notification:notification
|
|
141
|
+
ourOptions:[PushSignalCenter foregroundPresentationOptions]
|
|
142
|
+
completionHandler:completionHandler];
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
- (void)userNotificationCenter:(UNUserNotificationCenter *)center
|
|
146
|
+
didReceiveNotificationResponse:(UNNotificationResponse *)response
|
|
147
|
+
withCompletionHandler:(void (^)(void))completionHandler {
|
|
148
|
+
[self emitPress:[PushSignalCenter messageFromNotification:response.notification]];
|
|
149
|
+
SEL selector = @selector(userNotificationCenter:didReceiveNotificationResponse:withCompletionHandler:);
|
|
150
|
+
id<UNUserNotificationCenterDelegate> forwarding = _forwardingDelegate;
|
|
151
|
+
if (forwarding != nil && [forwarding respondsToSelector:selector]) {
|
|
152
|
+
[forwarding userNotificationCenter:center
|
|
153
|
+
didReceiveNotificationResponse:response
|
|
154
|
+
withCompletionHandler:completionHandler];
|
|
155
|
+
} else {
|
|
156
|
+
completionHandler();
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
#pragma mark - Token
|
|
161
|
+
|
|
162
|
+
- (void)waitForTokenWithCompletion:(void (^)(NSString *_Nullable, NSError *_Nullable))completion {
|
|
163
|
+
[_lock lock];
|
|
164
|
+
if (_deviceToken != nil) {
|
|
165
|
+
NSString *token = _deviceToken;
|
|
166
|
+
[_lock unlock];
|
|
167
|
+
completion(token, nil);
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
if (_registrationError != nil) {
|
|
171
|
+
NSError *error = _registrationError;
|
|
172
|
+
[_lock unlock];
|
|
173
|
+
completion(nil, error);
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
[_tokenWaiters addObject:[completion copy]];
|
|
177
|
+
[_lock unlock];
|
|
178
|
+
|
|
179
|
+
__weak PushSignalCenter *weakSelf = self;
|
|
180
|
+
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(20 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
|
181
|
+
NSError *timeout = [NSError errorWithDomain:@"PushSignal"
|
|
182
|
+
code:1
|
|
183
|
+
userInfo:@{
|
|
184
|
+
NSLocalizedDescriptionKey :
|
|
185
|
+
@"APNs registration timed out. Use a physical iOS device and enable the Push Notifications capability."
|
|
186
|
+
}];
|
|
187
|
+
[weakSelf finishRegistrationWithToken:nil error:timeout];
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
- (void)finishRegistrationWithToken:(NSString *)token error:(NSError *)error {
|
|
192
|
+
[_lock lock];
|
|
193
|
+
if (token != nil) {
|
|
194
|
+
_deviceToken = token;
|
|
195
|
+
_registrationError = nil;
|
|
196
|
+
} else if (_deviceToken == nil) {
|
|
197
|
+
_registrationError = error;
|
|
198
|
+
}
|
|
199
|
+
NSArray *waiters = [_tokenWaiters copy];
|
|
200
|
+
[_tokenWaiters removeAllObjects];
|
|
201
|
+
[_lock unlock];
|
|
202
|
+
|
|
203
|
+
for (void (^waiter)(NSString *, NSError *) in waiters) {
|
|
204
|
+
waiter(token, error);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
#pragma mark - Messages
|
|
209
|
+
|
|
210
|
+
- (void)deliverMessage:(NSDictionary *)message {
|
|
211
|
+
if (![self shouldEmit:message]) {
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
void (^callback)(NSDictionary *) = nil;
|
|
216
|
+
[_lock lock];
|
|
217
|
+
callback = _onMessage;
|
|
218
|
+
if (callback == nil) {
|
|
219
|
+
[_pendingMessages addObject:message];
|
|
220
|
+
[_lock unlock];
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
[_lock unlock];
|
|
224
|
+
|
|
225
|
+
[self emitToJs:callback message:message];
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
- (void)emitToJs:(void (^)(NSDictionary *))callback message:(NSDictionary *)message {
|
|
229
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
230
|
+
callback(message);
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
- (void)flushPendingMessages {
|
|
235
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
236
|
+
void (^callback)(NSDictionary *) = nil;
|
|
237
|
+
NSArray<NSDictionary *> *queued = nil;
|
|
238
|
+
[self->_lock lock];
|
|
239
|
+
callback = self->_onMessage;
|
|
240
|
+
queued = [self->_pendingMessages copy];
|
|
241
|
+
[self->_pendingMessages removeAllObjects];
|
|
242
|
+
[self->_lock unlock];
|
|
243
|
+
|
|
244
|
+
if (callback == nil || queued.count == 0) {
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
for (NSDictionary *message in queued) {
|
|
248
|
+
[self emitToJs:callback message:message];
|
|
249
|
+
}
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
- (BOOL)shouldEmit:(NSDictionary *)message {
|
|
254
|
+
NSString *messageId = message[@"id"];
|
|
255
|
+
NSString *title = message[@"title"] ?: @"";
|
|
256
|
+
NSString *body = message[@"body"] ?: @"";
|
|
257
|
+
NSString *key = messageId ?: [NSString stringWithFormat:@"%@|%@", title, body];
|
|
258
|
+
|
|
259
|
+
[_lock lock];
|
|
260
|
+
NSDate *now = [NSDate date];
|
|
261
|
+
NSMutableArray<NSString *> *expired = [NSMutableArray array];
|
|
262
|
+
[_recentMessageIds enumerateKeysAndObjectsUsingBlock:^(NSString *k, NSDate *date, BOOL *stop) {
|
|
263
|
+
if ([now timeIntervalSinceDate:date] >= 5) {
|
|
264
|
+
[expired addObject:k];
|
|
265
|
+
}
|
|
266
|
+
}];
|
|
267
|
+
[_recentMessageIds removeObjectsForKeys:expired];
|
|
268
|
+
|
|
269
|
+
NSDate *last = _recentMessageIds[key];
|
|
270
|
+
if (last != nil && [now timeIntervalSinceDate:last] < 2) {
|
|
271
|
+
[_lock unlock];
|
|
272
|
+
return NO;
|
|
273
|
+
}
|
|
274
|
+
_recentMessageIds[key] = now;
|
|
275
|
+
[_lock unlock];
|
|
276
|
+
return YES;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
- (void)forwardWillPresent:(UNUserNotificationCenter *)center
|
|
280
|
+
notification:(UNNotification *)notification
|
|
281
|
+
ourOptions:(UNNotificationPresentationOptions)ourOptions
|
|
282
|
+
completionHandler:(void (^)(UNNotificationPresentationOptions))completionHandler {
|
|
283
|
+
SEL selector = @selector(userNotificationCenter:willPresentNotification:withCompletionHandler:);
|
|
284
|
+
id<UNUserNotificationCenterDelegate> forwarding = _forwardingDelegate;
|
|
285
|
+
if (forwarding == nil || ![forwarding respondsToSelector:selector]) {
|
|
286
|
+
completionHandler(ourOptions);
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
[forwarding userNotificationCenter:center
|
|
291
|
+
willPresentNotification:notification
|
|
292
|
+
withCompletionHandler:^(UNNotificationPresentationOptions forwarded) {
|
|
293
|
+
completionHandler(ourOptions | forwarded);
|
|
294
|
+
}];
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
+ (UNNotificationPresentationOptions)foregroundPresentationOptions {
|
|
298
|
+
if (@available(iOS 14.0, *)) {
|
|
299
|
+
return UNNotificationPresentationOptionBanner | UNNotificationPresentationOptionList |
|
|
300
|
+
UNNotificationPresentationOptionSound | UNNotificationPresentationOptionBadge;
|
|
301
|
+
}
|
|
302
|
+
return UNNotificationPresentationOptionAlert | UNNotificationPresentationOptionSound |
|
|
303
|
+
UNNotificationPresentationOptionBadge;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
- (void)emitPress:(NSDictionary *)message {
|
|
307
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
308
|
+
if (self->_onNotificationPress != nil) {
|
|
309
|
+
self->_onNotificationPress(message);
|
|
310
|
+
} else {
|
|
311
|
+
self->_pendingPress = message;
|
|
312
|
+
}
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
- (void)flushPendingPress {
|
|
317
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
318
|
+
if (self->_pendingPress == nil || self->_onNotificationPress == nil) {
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
NSDictionary *pending = self->_pendingPress;
|
|
322
|
+
self->_pendingPress = nil;
|
|
323
|
+
self->_onNotificationPress(pending);
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
- (void)captureLaunchNotificationIfNeeded {
|
|
328
|
+
if (_didCaptureLaunchNotification) {
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
if (_pendingLaunchOptions == nil) {
|
|
333
|
+
_pendingLaunchOptions = PushSignalCopyLaunchOptions();
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
NSDictionary *launchOptions = _pendingLaunchOptions;
|
|
337
|
+
if (launchOptions == nil) {
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
_didCaptureLaunchNotification = YES;
|
|
342
|
+
|
|
343
|
+
id payload = launchOptions[UIApplicationLaunchOptionsRemoteNotificationKey];
|
|
344
|
+
if (![payload isKindOfClass:[NSDictionary class]]) {
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
[self emitPress:[PushSignalCenter messageFromUserInfo:payload
|
|
349
|
+
fallbackId:@"launch"
|
|
350
|
+
title:nil
|
|
351
|
+
body:nil]];
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
- (NSString *)currentEnvironment {
|
|
355
|
+
NSString *environment = [PushSignalCenter apsEnvironment];
|
|
356
|
+
if (environment != nil) {
|
|
357
|
+
return environment;
|
|
358
|
+
}
|
|
359
|
+
#if DEBUG
|
|
360
|
+
return @"sandbox";
|
|
361
|
+
#else
|
|
362
|
+
return @"production";
|
|
363
|
+
#endif
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
+ (nullable NSString *)apsEnvironment {
|
|
367
|
+
NSURL *url = [[NSBundle mainBundle] URLForResource:@"embedded" withExtension:@"mobileprovision"];
|
|
368
|
+
if (url == nil) {
|
|
369
|
+
return nil;
|
|
370
|
+
}
|
|
371
|
+
NSData *data = [NSData dataWithContentsOfURL:url];
|
|
372
|
+
if (data == nil) {
|
|
373
|
+
return nil;
|
|
374
|
+
}
|
|
375
|
+
NSString *contents = [[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding];
|
|
376
|
+
if (contents == nil) {
|
|
377
|
+
return nil;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
NSRegularExpression *regex =
|
|
381
|
+
[NSRegularExpression regularExpressionWithPattern:@"<key>aps-environment</key>\\s*<string>(\\w+)</string>"
|
|
382
|
+
options:0
|
|
383
|
+
error:nil];
|
|
384
|
+
NSTextCheckingResult *match =
|
|
385
|
+
[regex firstMatchInString:contents options:0 range:NSMakeRange(0, contents.length)];
|
|
386
|
+
if (match == nil || match.numberOfRanges < 2) {
|
|
387
|
+
return nil;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
NSString *value = [contents substringWithRange:[match rangeAtIndex:1]];
|
|
391
|
+
if ([value isEqualToString:@"development"]) {
|
|
392
|
+
return @"sandbox";
|
|
393
|
+
}
|
|
394
|
+
if ([value isEqualToString:@"production"]) {
|
|
395
|
+
return @"production";
|
|
396
|
+
}
|
|
397
|
+
return nil;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
+ (NSDictionary *)messageFromNotification:(UNNotification *)notification {
|
|
401
|
+
UNNotificationContent *content = notification.request.content;
|
|
402
|
+
return [self messageFromUserInfo:content.userInfo
|
|
403
|
+
fallbackId:notification.request.identifier
|
|
404
|
+
title:content.title.length > 0 ? content.title : nil
|
|
405
|
+
body:content.body.length > 0 ? content.body : nil];
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
+ (NSDictionary *)messageFromUserInfo:(NSDictionary *)userInfo
|
|
409
|
+
fallbackId:(NSString *)fallbackId
|
|
410
|
+
title:(NSString *)title
|
|
411
|
+
body:(NSString *)body {
|
|
412
|
+
id aps = userInfo[@"aps"];
|
|
413
|
+
id alert = [aps isKindOfClass:[NSDictionary class]] ? aps[@"alert"] : nil;
|
|
414
|
+
NSString *resolvedTitle = title;
|
|
415
|
+
NSString *resolvedBody = body;
|
|
416
|
+
|
|
417
|
+
if ([alert isKindOfClass:[NSString class]]) {
|
|
418
|
+
resolvedBody = resolvedBody ?: (NSString *)alert;
|
|
419
|
+
} else if ([alert isKindOfClass:[NSDictionary class]]) {
|
|
420
|
+
resolvedTitle = resolvedTitle ?: [self stringify:alert[@"title"]];
|
|
421
|
+
resolvedBody = resolvedBody ?: [self stringify:alert[@"body"]];
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
NSMutableDictionary *data = [NSMutableDictionary dictionary];
|
|
425
|
+
[userInfo enumerateKeysAndObjectsUsingBlock:^(id key, id value, BOOL *stop) {
|
|
426
|
+
NSString *stringKey = [NSString stringWithFormat:@"%@", key];
|
|
427
|
+
if ([stringKey isEqualToString:@"aps"]) {
|
|
428
|
+
return;
|
|
429
|
+
}
|
|
430
|
+
NSString *stringValue = [self stringify:value];
|
|
431
|
+
if (stringValue != nil) {
|
|
432
|
+
data[stringKey] = stringValue;
|
|
433
|
+
}
|
|
434
|
+
}];
|
|
435
|
+
|
|
436
|
+
NSMutableDictionary *message = [NSMutableDictionary dictionary];
|
|
437
|
+
NSString *messageId = [self stringify:userInfo[@"gcm.message_id"]] ?: fallbackId;
|
|
438
|
+
if (messageId != nil) {
|
|
439
|
+
message[@"id"] = messageId;
|
|
440
|
+
}
|
|
441
|
+
if (resolvedTitle != nil) {
|
|
442
|
+
message[@"title"] = resolvedTitle;
|
|
443
|
+
}
|
|
444
|
+
if (resolvedBody != nil) {
|
|
445
|
+
message[@"body"] = resolvedBody;
|
|
446
|
+
}
|
|
447
|
+
message[@"data"] = data;
|
|
448
|
+
return message;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
+ (nullable NSString *)stringify:(id)value {
|
|
452
|
+
if (value == nil || value == [NSNull null]) {
|
|
453
|
+
return nil;
|
|
454
|
+
}
|
|
455
|
+
if ([value isKindOfClass:[NSString class]]) {
|
|
456
|
+
return (NSString *)value;
|
|
457
|
+
}
|
|
458
|
+
if ([value isKindOfClass:[NSNumber class]]) {
|
|
459
|
+
return [(NSNumber *)value stringValue];
|
|
460
|
+
}
|
|
461
|
+
if ([NSJSONSerialization isValidJSONObject:value]) {
|
|
462
|
+
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:value options:0 error:nil];
|
|
463
|
+
if (jsonData != nil) {
|
|
464
|
+
return [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
return [NSString stringWithFormat:@"%@", value];
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
#pragma mark - Swizzling
|
|
471
|
+
|
|
472
|
+
- (void)swizzleAppDelegate {
|
|
473
|
+
if (_didSwizzleAppDelegate) {
|
|
474
|
+
return;
|
|
475
|
+
}
|
|
476
|
+
id appDelegate = [UIApplication sharedApplication].delegate;
|
|
477
|
+
if (appDelegate == nil) {
|
|
478
|
+
return;
|
|
479
|
+
}
|
|
480
|
+
_didSwizzleAppDelegate = YES;
|
|
481
|
+
|
|
482
|
+
Class target = object_getClass(appDelegate);
|
|
483
|
+
Class source = [PushSignalAppDelegateHook class];
|
|
484
|
+
|
|
485
|
+
[self swizzleTarget:target
|
|
486
|
+
original:@selector(application:didRegisterForRemoteNotificationsWithDeviceToken:)
|
|
487
|
+
replacement:@selector(pushSignal_application:didRegisterForRemoteNotificationsWithDeviceToken:)
|
|
488
|
+
source:source];
|
|
489
|
+
[self swizzleTarget:target
|
|
490
|
+
original:@selector(application:didFailToRegisterForRemoteNotificationsWithError:)
|
|
491
|
+
replacement:@selector(pushSignal_application:didFailToRegisterForRemoteNotificationsWithError:)
|
|
492
|
+
source:source];
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
- (void)swizzleNotificationCenterDelegateIfNeeded {
|
|
496
|
+
if (_didSwizzleNotificationCenter) {
|
|
497
|
+
return;
|
|
498
|
+
}
|
|
499
|
+
_didSwizzleNotificationCenter = YES;
|
|
500
|
+
|
|
501
|
+
Class target = [UNUserNotificationCenter class];
|
|
502
|
+
SEL original = @selector(setDelegate:);
|
|
503
|
+
SEL replacement = @selector(pushSignal_setDelegate:);
|
|
504
|
+
|
|
505
|
+
Method originalMethod = class_getInstanceMethod(target, original);
|
|
506
|
+
Method replacementMethod = class_getInstanceMethod(target, replacement);
|
|
507
|
+
if (originalMethod == NULL || replacementMethod == NULL) {
|
|
508
|
+
return;
|
|
509
|
+
}
|
|
510
|
+
method_exchangeImplementations(originalMethod, replacementMethod);
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
- (void)swizzleTarget:(Class)target
|
|
514
|
+
original:(SEL)original
|
|
515
|
+
replacement:(SEL)replacement
|
|
516
|
+
source:(Class)source {
|
|
517
|
+
Method replacementMethod = class_getInstanceMethod(source, replacement);
|
|
518
|
+
if (replacementMethod == NULL) {
|
|
519
|
+
return;
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
IMP replacementImp = method_getImplementation(replacementMethod);
|
|
523
|
+
const char *types = method_getTypeEncoding(replacementMethod);
|
|
524
|
+
Class superClass = class_getSuperclass(target);
|
|
525
|
+
Method superMethod = superClass != Nil ? class_getInstanceMethod(superClass, original) : NULL;
|
|
526
|
+
|
|
527
|
+
if (class_addMethod(target, original, replacementImp, types)) {
|
|
528
|
+
if (superMethod != NULL) {
|
|
529
|
+
class_addMethod(
|
|
530
|
+
target, replacement, method_getImplementation(superMethod), method_getTypeEncoding(superMethod));
|
|
531
|
+
}
|
|
532
|
+
return;
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
if (!class_addMethod(target, replacement, replacementImp, types)) {
|
|
536
|
+
return;
|
|
537
|
+
}
|
|
538
|
+
Method originalMethod = class_getInstanceMethod(target, original);
|
|
539
|
+
Method addedReplacement = class_getInstanceMethod(target, replacement);
|
|
540
|
+
if (originalMethod == NULL || addedReplacement == NULL) {
|
|
541
|
+
return;
|
|
542
|
+
}
|
|
543
|
+
method_exchangeImplementations(originalMethod, addedReplacement);
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
@end
|
|
547
|
+
|
|
548
|
+
@implementation PushSignalAppDelegateHook
|
|
549
|
+
|
|
550
|
+
- (void)pushSignal_application:(UIApplication *)application
|
|
551
|
+
didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
|
|
552
|
+
[[PushSignalCenter shared] handleDeviceToken:deviceToken];
|
|
553
|
+
// After swizzle, this selector holds the original AppDelegate implementation (if any).
|
|
554
|
+
SEL original = @selector(pushSignal_application:didRegisterForRemoteNotificationsWithDeviceToken:);
|
|
555
|
+
if ([self respondsToSelector:original]) {
|
|
556
|
+
[self pushSignal_application:application didRegisterForRemoteNotificationsWithDeviceToken:deviceToken];
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
- (void)pushSignal_application:(UIApplication *)application
|
|
561
|
+
didFailToRegisterForRemoteNotificationsWithError:(NSError *)error {
|
|
562
|
+
[[PushSignalCenter shared] handleRegistrationError:error];
|
|
563
|
+
SEL original = @selector(pushSignal_application:didFailToRegisterForRemoteNotificationsWithError:);
|
|
564
|
+
if ([self respondsToSelector:original]) {
|
|
565
|
+
[self pushSignal_application:application didFailToRegisterForRemoteNotificationsWithError:error];
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
@end
|
|
570
|
+
|
|
571
|
+
@implementation UNUserNotificationCenter (PushSignal)
|
|
572
|
+
|
|
573
|
+
- (void)pushSignal_setDelegate:(id<UNUserNotificationCenterDelegate>)delegate {
|
|
574
|
+
if (delegate != nil && [delegate isKindOfClass:[PushSignalCenter class]]) {
|
|
575
|
+
[self pushSignal_setDelegate:delegate];
|
|
576
|
+
return;
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
[[PushSignalCenter shared] rememberForwardingDelegate:delegate];
|
|
580
|
+
[self pushSignal_setDelegate:[PushSignalCenter shared]];
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
@end
|