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.
Files changed (38) hide show
  1. package/CapacitorPushSignal.podspec +19 -0
  2. package/Package.swift +36 -0
  3. package/README.md +271 -0
  4. package/android/build.gradle +66 -0
  5. package/android/src/main/AndroidManifest.xml +19 -0
  6. package/android/src/main/java/com/antonseagull/cap/push/signal/PushModels.kt +15 -0
  7. package/android/src/main/java/com/antonseagull/cap/push/signal/PushSignalCenter.kt +397 -0
  8. package/android/src/main/java/com/antonseagull/cap/push/signal/PushSignalInitProvider.kt +34 -0
  9. package/android/src/main/java/com/antonseagull/cap/push/signal/PushSignalMessagingService.kt +20 -0
  10. package/android/src/main/java/com/antonseagull/cap/push/signal/PushSignalPlugin.kt +85 -0
  11. package/android/src/main/res/.gitkeep +0 -0
  12. package/dist/docs.json +275 -0
  13. package/dist/esm/definitions.d.ts +19 -0
  14. package/dist/esm/definitions.js +2 -0
  15. package/dist/esm/definitions.js.map +1 -0
  16. package/dist/esm/index.d.ts +2 -0
  17. package/dist/esm/index.js +2 -0
  18. package/dist/esm/index.js.map +1 -0
  19. package/dist/esm/pushSignal.d.ts +8 -0
  20. package/dist/esm/pushSignal.js +76 -0
  21. package/dist/esm/pushSignal.js.map +1 -0
  22. package/dist/esm/types.d.ts +20 -0
  23. package/dist/esm/types.js +2 -0
  24. package/dist/esm/types.js.map +1 -0
  25. package/dist/esm/web.d.ts +8 -0
  26. package/dist/esm/web.js +13 -0
  27. package/dist/esm/web.js.map +1 -0
  28. package/dist/plugin.cjs.js +100 -0
  29. package/dist/plugin.cjs.js.map +1 -0
  30. package/dist/plugin.js +103 -0
  31. package/dist/plugin.js.map +1 -0
  32. package/ios/Sources/PushSignalCore/PushSignalCenter.m +583 -0
  33. package/ios/Sources/PushSignalCore/PushSignalLaunchStore.m +38 -0
  34. package/ios/Sources/PushSignalCore/include/PushSignalCenter.h +26 -0
  35. package/ios/Sources/PushSignalCore/include/PushSignalLaunchStore.h +21 -0
  36. package/ios/Sources/PushSignalPlugin/PushSignalPlugin.swift +66 -0
  37. package/ios/Tests/PushSignalPluginTests/PushSignalTests.swift +9 -0
  38. 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":";;;AACK,UAAC,UAAU,GAAGA,mBAAc,CAAC,YAAY,EAAE;IAChD,IAAI,GAAG,EAAE,MAAM,mDAAe,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,aAAa,EAAE,CAAC;IACjE,CAAC;IACD,MAAM,gBAAgB,GAAG,IAAI,GAAG,EAAE;IAClC,MAAM,cAAc,GAAG,IAAI,GAAG,EAAE;IAChC,IAAI,oBAAoB,GAAG,KAAK;IAChC,SAAS,gBAAgB,CAAC,GAAG,EAAE;IAC/B,IAAI,MAAM,IAAI,GAAG,EAAE;IACnB,IAAI,IAAI,GAAG,CAAC,IAAI,IAAI,OAAO,GAAG,CAAC,IAAI,KAAK,QAAQ,EAAE;IAClD,QAAQ,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE;IAC7D,YAAY,IAAI,KAAK,IAAI,IAAI,EAAE;IAC/B,gBAAgB;IAChB,YAAY;IACZ,YAAY,IAAI,CAAC,GAAG,CAAC,GAAG,OAAO,KAAK,KAAK,QAAQ,GAAG,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC;IACzE,QAAQ;IACR,IAAI;IACJ,IAAI,OAAO;IACX,QAAQ,EAAE,EAAE,GAAG,CAAC,EAAE;IAClB,QAAQ,KAAK,EAAE,GAAG,CAAC,KAAK;IACxB,QAAQ,IAAI,EAAE,GAAG,CAAC,IAAI;IACtB,QAAQ,IAAI;IACZ,KAAK;IACL;IACA,SAAS,oBAAoB,CAAC,GAAG,EAAE;IACnC,IAAI,OAAO;IACX,QAAQ,QAAQ,EAAE,GAAG,CAAC,QAAQ;IAC9B,QAAQ,KAAK,EAAE,GAAG,CAAC,KAAK;IACxB,QAAQ,WAAW,EAAE,GAAG,CAAC,WAAW;IACpC,KAAK;IACL;IACA,SAAS,mBAAmB,GAAG;IAC/B,IAAI,IAAI,oBAAoB,EAAE;IAC9B,QAAQ;IACR,IAAI;IACJ,IAAI,oBAAoB,GAAG,IAAI;IAC/B,IAAI,KAAK,UAAU,CAAC,WAAW,CAAC,WAAW,EAAE,CAAC,GAAG,KAAK;IACtD,QAAQ,MAAM,OAAO,GAAG,gBAAgB,CAAC,GAAG,CAAC;IAC7C,QAAQ,KAAK,MAAM,QAAQ,IAAI,CAAC,GAAG,gBAAgB,CAAC,EAAE;IACtD,YAAY,IAAI;IAChB,gBAAgB,OAAO,CAAC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,SAAS,EAAE,MAAM,SAAS,CAAC;IACzF,YAAY;IACZ,YAAY,OAAO,EAAE,EAAE;IACvB;IACA,YAAY;IACZ,QAAQ;IACR,IAAI,CAAC,CAAC;IACN,IAAI,KAAK,UAAU,CAAC,WAAW,CAAC,qBAAqB,EAAE,CAAC,GAAG,KAAK;IAChE,QAAQ,MAAM,OAAO,GAAG,gBAAgB,CAAC,GAAG,CAAC;IAC7C,QAAQ,cAAc,CAAC,OAAO,CAAC,CAAC,QAAQ,KAAK,QAAQ,CAAC,OAAO,CAAC,CAAC;IAC/D,IAAI,CAAC,CAAC;IACN,IAAI,KAAK,UAAU,CAAC,cAAc,EAAE;IACpC;IACO,SAAS,UAAU,CAAC,MAAM,GAAG,EAAE,EAAE;IACxC,IAAI,OAAO,UAAU,CAAC,UAAU,CAAC,MAAM,CAAC;IACxC;IACO,SAAS,cAAc,GAAG;IACjC,IAAI,OAAO,UAAU,CAAC,cAAc,EAAE,CAAC,IAAI,CAAC,oBAAoB,CAAC;IACjE;IACO,SAAS,SAAS,CAAC,QAAQ,EAAE;IACpC,IAAI,mBAAmB,EAAE;IACzB,IAAI,gBAAgB,CAAC,GAAG,CAAC,QAAQ,CAAC;IAClC,IAAI,OAAO,MAAM;IACjB,QAAQ,gBAAgB,CAAC,MAAM,CAAC,QAAQ,CAAC;IACzC,IAAI,CAAC;IACL;IACO,SAAS,mBAAmB,CAAC,QAAQ,EAAE;IAC9C,IAAI,mBAAmB,EAAE;IACzB,IAAI,cAAc,CAAC,GAAG,CAAC,QAAQ,CAAC;IAChC,IAAI,OAAO,MAAM;IACjB,QAAQ,cAAc,CAAC,MAAM,CAAC,QAAQ,CAAC;IACvC,IAAI,CAAC;IACL;IACA,mBAAmB,EAAE;;ICxEd,MAAM,aAAa,SAASC,cAAS,CAAC;IAC7C,IAAI,MAAM,UAAU,CAAC,MAAM,EAAE;IAE7B,IAAI;IACJ,IAAI,MAAM,cAAc,GAAG;IAC3B,QAAQ,MAAM,IAAI,CAAC,aAAa,CAAC,2CAA2C,CAAC;IAC7E,IAAI;IACJ,IAAI,MAAM,cAAc,GAAG;IAC3B,QAAQ;IACR,IAAI;IACJ;;;;;;;;;;;;;;;"}
@@ -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