1 /*
2   ==============================================================================
3 
4    This file is part of the JUCE library.
5    Copyright (c) 2020 - Raw Material Software Limited
6 
7    JUCE is an open source library subject to commercial or open-source
8    licensing.
9 
10    By using JUCE, you agree to the terms of both the JUCE 6 End-User License
11    Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
12 
13    End User License Agreement: www.juce.com/juce-6-licence
14    Privacy Policy: www.juce.com/juce-privacy-policy
15 
16    Or: You may also use this code under the terms of the GPL v3 (see
17    www.gnu.org/licenses).
18 
19    JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
20    EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
21    DISCLAIMED.
22 
23   ==============================================================================
24 */
25 
26 JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations")
27 
28 namespace juce
29 {
30 
31 namespace PushNotificationsDelegateDetailsOsx
32 {
33     using Action = PushNotifications::Notification::Action;
34 
35     //==============================================================================
juceNotificationToNSUserNotification(const PushNotifications::Notification & n,bool isEarlierThanMavericks,bool isEarlierThanYosemite)36     NSUserNotification* juceNotificationToNSUserNotification (const PushNotifications::Notification& n,
37                                                               bool isEarlierThanMavericks,
38                                                               bool isEarlierThanYosemite)
39     {
40         auto notification = [[NSUserNotification alloc] init];
41 
42         notification.title           = juceStringToNS (n.title);
43         notification.subtitle        = juceStringToNS (n.subtitle);
44         notification.informativeText = juceStringToNS (n.body);
45         notification.userInfo = varObjectToNSDictionary (n.properties);
46 
47         auto triggerTime = Time::getCurrentTime() + RelativeTime (n.triggerIntervalSec);
48         notification.deliveryDate = [NSDate dateWithTimeIntervalSince1970: triggerTime.toMilliseconds() / 1000.];
49 
50         if (n.repeat && n.triggerIntervalSec >= 60)
51         {
52             auto dateComponents = [[NSDateComponents alloc] init];
53             auto intervalSec = NSInteger (n.triggerIntervalSec);
54             dateComponents.second = intervalSec;
55             dateComponents.nanosecond = NSInteger ((n.triggerIntervalSec - intervalSec) * 1000000000);
56 
57             notification.deliveryRepeatInterval = dateComponents;
58 
59             [dateComponents autorelease];
60         }
61 
62         auto soundToPlayString = n.soundToPlay.toString (true);
63 
64         if (soundToPlayString == "default_os_sound")
65         {
66             notification.soundName = NSUserNotificationDefaultSoundName;
67         }
68         else if (soundToPlayString.isNotEmpty())
69         {
70             auto* soundName = juceStringToNS (soundToPlayString.fromLastOccurrenceOf ("/", false, false)
71                                                                .upToLastOccurrenceOf (".", false, false));
72 
73             notification.soundName = soundName;
74         }
75 
76         notification.hasActionButton = n.actions.size() > 0;
77 
78         if (n.actions.size() > 0)
79             notification.actionButtonTitle = juceStringToNS (n.actions.getReference (0).title);
80 
81         if (! isEarlierThanMavericks)
82         {
83             notification.identifier = juceStringToNS (n.identifier);
84 
85             if (n.actions.size() > 0)
86             {
87                 notification.hasReplyButton = n.actions.getReference (0).style == Action::text;
88                 notification.responsePlaceholder = juceStringToNS (n.actions.getReference (0).textInputPlaceholder);
89             }
90 
91             auto* imageDirectory = n.icon.contains ("/")
92                                  ? juceStringToNS (n.icon.upToLastOccurrenceOf ("/", false, true))
93                                  : [NSString string];
94 
95             auto* imageName      = juceStringToNS (n.icon.fromLastOccurrenceOf ("/", false, false)
96                                                          .upToLastOccurrenceOf (".", false, false));
97             auto* imageExtension = juceStringToNS (n.icon.fromLastOccurrenceOf (".", false, false));
98 
99             NSString* imagePath = nil;
100 
101             if ([imageDirectory length] == NSUInteger (0))
102             {
103                 imagePath = [[NSBundle mainBundle] pathForResource: imageName
104                                                             ofType: imageExtension];
105             }
106             else
107             {
108                 imagePath = [[NSBundle mainBundle] pathForResource: imageName
109                                                             ofType: imageExtension
110                                                        inDirectory: imageDirectory];
111             }
112 
113             notification.contentImage = [[NSImage alloc] initWithContentsOfFile: imagePath];
114 
115             if (! isEarlierThanYosemite)
116             {
117                 if (n.actions.size() > 1)
118                 {
119                     auto additionalActions = [NSMutableArray arrayWithCapacity: (NSUInteger) n.actions.size() - 1];
120 
121                     for (int a = 1; a < n.actions.size(); ++a)
122                         [additionalActions addObject: [NSUserNotificationAction actionWithIdentifier: juceStringToNS (n.actions[a].identifier)
123                                                                                                title: juceStringToNS (n.actions[a].title)]];
124 
125                     notification.additionalActions = additionalActions;
126                 }
127             }
128         }
129 
130         [notification autorelease];
131 
132         return notification;
133     }
134 
135     //==============================================================================
nsUserNotificationToJuceNotification(NSUserNotification * n,bool isEarlierThanMavericks,bool isEarlierThanYosemite)136     PushNotifications::Notification nsUserNotificationToJuceNotification (NSUserNotification* n,
137                                                                           bool isEarlierThanMavericks,
138                                                                           bool isEarlierThanYosemite)
139     {
140         PushNotifications::Notification notif;
141 
142         notif.title       = nsStringToJuce (n.title);
143         notif.subtitle    = nsStringToJuce (n.subtitle);
144         notif.body        = nsStringToJuce (n.informativeText);
145 
146         notif.repeat = n.deliveryRepeatInterval != nil;
147 
148         if (n.deliveryRepeatInterval != nil)
149         {
150             notif.triggerIntervalSec = n.deliveryRepeatInterval.second + (n.deliveryRepeatInterval.nanosecond / 1000000000.);
151         }
152         else
153         {
154             NSDate* dateNow = [NSDate date];
155             NSDate* deliveryDate = n.deliveryDate;
156 
157             notif.triggerIntervalSec = [dateNow timeIntervalSinceDate: deliveryDate];
158         }
159 
160         notif.soundToPlay = URL (nsStringToJuce (n.soundName));
161         notif.properties  = nsDictionaryToVar (n.userInfo);
162 
163         if (! isEarlierThanMavericks)
164         {
165             notif.identifier = nsStringToJuce (n.identifier);
166 
167             if (n.contentImage != nil)
168                 notif.icon = nsStringToJuce ([n.contentImage name]);
169         }
170 
171         Array<Action> actions;
172 
173         if (n.actionButtonTitle != nil)
174         {
175             Action action;
176             action.title = nsStringToJuce (n.actionButtonTitle);
177 
178             if (! isEarlierThanMavericks)
179             {
180                 if (n.hasReplyButton)
181                     action.style = Action::text;
182 
183                 if (n.responsePlaceholder != nil)
184                     action.textInputPlaceholder = nsStringToJuce (n.responsePlaceholder);
185             }
186 
187             actions.add (action);
188         }
189 
190         if (! isEarlierThanYosemite)
191         {
192             if (n.additionalActions != nil)
193             {
194                 for (NSUserNotificationAction* a in n.additionalActions)
195                 {
196                     Action action;
197                     action.identifier = nsStringToJuce (a.identifier);
198                     action.title      = nsStringToJuce (a.title);
199 
200                     actions.add (action);
201                 }
202             }
203         }
204 
205         return notif;
206     }
207 
208     //==============================================================================
getNotificationPropertiesFromDictionaryVar(const var & dictionaryVar)209     var getNotificationPropertiesFromDictionaryVar (const var& dictionaryVar)
210     {
211         auto* dictionaryVarObject = dictionaryVar.getDynamicObject();
212 
213         if (dictionaryVarObject == nullptr)
214             return {};
215 
216         const auto& properties = dictionaryVarObject->getProperties();
217 
218         DynamicObject::Ptr propsVarObject = new DynamicObject();
219 
220         for (int i = 0; i < properties.size(); ++i)
221         {
222             auto propertyName = properties.getName (i).toString();
223 
224             if (propertyName == "aps")
225                 continue;
226 
227             propsVarObject->setProperty (propertyName, properties.getValueAt (i));
228         }
229 
230         return var (propsVarObject.get());
231     }
232 
nsDictionaryToJuceNotification(NSDictionary * dictionary)233     PushNotifications::Notification nsDictionaryToJuceNotification (NSDictionary* dictionary)
234     {
235         const var dictionaryVar = nsDictionaryToVar (dictionary);
236 
237         const var apsVar = dictionaryVar.getProperty ("aps", {});
238 
239         if (! apsVar.isObject())
240             return {};
241 
242         var alertVar = apsVar.getProperty ("alert", {});
243 
244         const var titleVar = alertVar.getProperty ("title", {});
245         const var bodyVar  = alertVar.isObject() ? alertVar.getProperty ("body", {}) : alertVar;
246 
247         const var categoryVar = apsVar.getProperty ("category", {});
248         const var soundVar    = apsVar.getProperty ("sound", {});
249         const var badgeVar    = apsVar.getProperty ("badge", {});
250         const var threadIdVar = apsVar.getProperty ("thread-id", {});
251 
252         PushNotifications::Notification notification;
253 
254         notification.title       = titleVar   .toString();
255         notification.body        = bodyVar    .toString();
256         notification.groupId     = threadIdVar.toString();
257         notification.category    = categoryVar.toString();
258         notification.soundToPlay = URL (soundVar.toString());
259         notification.badgeNumber = (int) badgeVar;
260         notification.properties  = getNotificationPropertiesFromDictionaryVar (dictionaryVar);
261 
262         return notification;
263     }
264 }
265 
266 //==============================================================================
267 struct PushNotificationsDelegate
268 {
PushNotificationsDelegatejuce::PushNotificationsDelegate269     PushNotificationsDelegate() : delegate ([getClass().createInstance() init])
270     {
271         Class::setThis (delegate.get(), this);
272 
273         id<NSApplicationDelegate> appDelegate = [[NSApplication sharedApplication] delegate];
274 
275         SEL selector = NSSelectorFromString (@"setPushNotificationsDelegate:");
276 
277         if ([appDelegate respondsToSelector: selector])
278             [appDelegate performSelector: selector withObject: delegate.get()];
279 
280         [NSUserNotificationCenter defaultUserNotificationCenter].delegate = delegate.get();
281     }
282 
~PushNotificationsDelegatejuce::PushNotificationsDelegate283     virtual ~PushNotificationsDelegate()
284     {
285         [NSUserNotificationCenter defaultUserNotificationCenter].delegate = nil;
286     }
287 
288     virtual void registeredForRemoteNotifications (NSData* deviceToken) = 0;
289 
290     virtual void failedToRegisterForRemoteNotifications (NSError* error) = 0;
291 
292     virtual void didReceiveRemoteNotification (NSDictionary* userInfo) = 0;
293 
294     virtual void didDeliverNotification (NSUserNotification* notification) = 0;
295 
296     virtual void didActivateNotification (NSUserNotification* notification) = 0;
297 
298     virtual bool shouldPresentNotification (NSUserNotification* notification) = 0;
299 
300 protected:
301     std::unique_ptr<NSObject<NSApplicationDelegate, NSUserNotificationCenterDelegate>, NSObjectDeleter> delegate;
302 
303 private:
304     struct Class    : public ObjCClass<NSObject<NSApplicationDelegate, NSUserNotificationCenterDelegate>>
305     {
Classjuce::PushNotificationsDelegate::Class306         Class() : ObjCClass<NSObject<NSApplicationDelegate, NSUserNotificationCenterDelegate>> ("JucePushNotificationsDelegate_")
307         {
308             addIvar<PushNotificationsDelegate*> ("self");
309 
310             addMethod (@selector (application:didRegisterForRemoteNotificationsWithDeviceToken:), registeredForRemoteNotifications,       "v@:@@");
311             addMethod (@selector (application:didFailToRegisterForRemoteNotificationsWithError:), failedToRegisterForRemoteNotifications, "v@:@@");
312             addMethod (@selector (application:didReceiveRemoteNotification:),                     didReceiveRemoteNotification,           "v@:@@");
313             addMethod (@selector (userNotificationCenter:didDeliverNotification:),                didDeliverNotification,                 "v@:@@");
314             addMethod (@selector (userNotificationCenter:didActivateNotification:),               didActivateNotification,                "v@:@@");
315             addMethod (@selector (userNotificationCenter:shouldPresentNotification:),             shouldPresentNotification,              "B@:@@");
316 
317             registerClass();
318         }
319 
320         //==============================================================================
getThisjuce::PushNotificationsDelegate::Class321         static PushNotificationsDelegate& getThis (id self)         { return *getIvar<PushNotificationsDelegate*> (self, "self"); }
setThisjuce::PushNotificationsDelegate::Class322         static void setThis (id self, PushNotificationsDelegate* d) { object_setInstanceVariable (self, "self", d); }
323 
324         //==============================================================================
registeredForRemoteNotificationsjuce::PushNotificationsDelegate::Class325         static void registeredForRemoteNotifications       (id self, SEL, NSApplication*,
326                                                             NSData* deviceToken)                { getThis (self).registeredForRemoteNotifications (deviceToken); }
327 
failedToRegisterForRemoteNotificationsjuce::PushNotificationsDelegate::Class328         static void failedToRegisterForRemoteNotifications (id self, SEL, NSApplication*,
329                                                             NSError* error)                     { getThis (self).failedToRegisterForRemoteNotifications (error); }
330 
didReceiveRemoteNotificationjuce::PushNotificationsDelegate::Class331         static void didReceiveRemoteNotification           (id self, SEL, NSApplication*,
332                                                             NSDictionary* userInfo)             { getThis (self).didReceiveRemoteNotification (userInfo); }
333 
didDeliverNotificationjuce::PushNotificationsDelegate::Class334         static void didDeliverNotification          (id self, SEL, NSUserNotificationCenter*,
335                                                      NSUserNotification* notification)          { getThis (self).didDeliverNotification (notification); }
336 
didActivateNotificationjuce::PushNotificationsDelegate::Class337         static void didActivateNotification         (id self, SEL, NSUserNotificationCenter*,
338                                                      NSUserNotification* notification)          { getThis (self).didActivateNotification (notification); }
339 
shouldPresentNotificationjuce::PushNotificationsDelegate::Class340         static bool shouldPresentNotification       (id self, SEL, NSUserNotificationCenter*,
341                                                      NSUserNotification* notification)          { return getThis (self).shouldPresentNotification (notification); }
342     };
343 
344     //==============================================================================
getClassjuce::PushNotificationsDelegate345     static Class& getClass()
346     {
347         static Class c;
348         return c;
349     }
350 };
351 
352 //==============================================================================
isValid() const353 bool PushNotifications::Notification::isValid() const noexcept { return true; }
354 
355 //==============================================================================
356 struct PushNotifications::Pimpl : private PushNotificationsDelegate
357 {
Pimpljuce::PushNotifications::Pimpl358     Pimpl (PushNotifications& p)
359         : owner (p)
360     {
361     }
362 
requestPermissionsWithSettingsjuce::PushNotifications::Pimpl363     void requestPermissionsWithSettings (const PushNotifications::Settings& settingsToUse)
364     {
365         if (isEarlierThanLion)
366             return;
367 
368         settings = settingsToUse;
369 
370         NSRemoteNotificationType types = NSUInteger ((bool) settings.allowBadge);
371 
372         if (isAtLeastMountainLion)
373             types |= (NSUInteger) ((bool) settings.allowSound << 1 | (bool) settings.allowAlert << 2);
374 
375         [[NSApplication sharedApplication] registerForRemoteNotificationTypes: types];
376     }
377 
requestSettingsUsedjuce::PushNotifications::Pimpl378     void requestSettingsUsed()
379     {
380         if (isEarlierThanLion)
381         {
382             // no settings available
383             owner.listeners.call ([] (Listener& l) { l.notificationSettingsReceived ({}); });
384             return;
385         }
386 
387         settings.allowBadge = [NSApplication sharedApplication].enabledRemoteNotificationTypes & NSRemoteNotificationTypeBadge;
388 
389         if (isAtLeastMountainLion)
390         {
391             settings.allowSound = [NSApplication sharedApplication].enabledRemoteNotificationTypes & NSRemoteNotificationTypeSound;
392             settings.allowAlert = [NSApplication sharedApplication].enabledRemoteNotificationTypes & NSRemoteNotificationTypeAlert;
393         }
394 
395         owner.listeners.call ([&] (Listener& l) { l.notificationSettingsReceived (settings); });
396     }
397 
areNotificationsEnabledjuce::PushNotifications::Pimpl398     bool areNotificationsEnabled() const { return true; }
399 
sendLocalNotificationjuce::PushNotifications::Pimpl400     void sendLocalNotification (const Notification& n)
401     {
402         auto* notification = PushNotificationsDelegateDetailsOsx::juceNotificationToNSUserNotification (n, isEarlierThanMavericks, isEarlierThanYosemite);
403 
404         [[NSUserNotificationCenter defaultUserNotificationCenter] scheduleNotification: notification];
405     }
406 
getDeliveredNotificationsjuce::PushNotifications::Pimpl407     void getDeliveredNotifications() const
408     {
409         Array<PushNotifications::Notification> notifs;
410 
411         for (NSUserNotification* n in [NSUserNotificationCenter defaultUserNotificationCenter].deliveredNotifications)
412             notifs.add (PushNotificationsDelegateDetailsOsx::nsUserNotificationToJuceNotification (n, isEarlierThanMavericks, isEarlierThanYosemite));
413 
414         owner.listeners.call ([&] (Listener& l) { l.deliveredNotificationsListReceived (notifs); });
415     }
416 
removeAllDeliveredNotificationsjuce::PushNotifications::Pimpl417     void removeAllDeliveredNotifications()
418     {
419         [[NSUserNotificationCenter defaultUserNotificationCenter] removeAllDeliveredNotifications];
420     }
421 
removeDeliveredNotificationjuce::PushNotifications::Pimpl422     void removeDeliveredNotification (const String& identifier)
423     {
424         PushNotifications::Notification n;
425         n.identifier = identifier;
426 
427         auto nsNotification = PushNotificationsDelegateDetailsOsx::juceNotificationToNSUserNotification (n, isEarlierThanMavericks, isEarlierThanYosemite);
428 
429         [[NSUserNotificationCenter defaultUserNotificationCenter] removeDeliveredNotification: nsNotification];
430     }
431 
setupChannelsjuce::PushNotifications::Pimpl432     void setupChannels (const Array<ChannelGroup>& groups, const Array<Channel>& channels)
433     {
434         ignoreUnused (groups, channels);
435     }
436 
getPendingLocalNotificationsjuce::PushNotifications::Pimpl437     void getPendingLocalNotifications() const
438     {
439         Array<PushNotifications::Notification> notifs;
440 
441         for (NSUserNotification* n in [NSUserNotificationCenter defaultUserNotificationCenter].scheduledNotifications)
442             notifs.add (PushNotificationsDelegateDetailsOsx::nsUserNotificationToJuceNotification (n, isEarlierThanMavericks, isEarlierThanYosemite));
443 
444         owner.listeners.call ([&] (Listener& l) { l.pendingLocalNotificationsListReceived (notifs); });
445     }
446 
removePendingLocalNotificationjuce::PushNotifications::Pimpl447     void removePendingLocalNotification (const String& identifier)
448     {
449         PushNotifications::Notification n;
450         n.identifier = identifier;
451 
452         auto nsNotification = PushNotificationsDelegateDetailsOsx::juceNotificationToNSUserNotification (n, isEarlierThanMavericks, isEarlierThanYosemite);
453 
454         [[NSUserNotificationCenter defaultUserNotificationCenter] removeScheduledNotification: nsNotification];
455     }
456 
removeAllPendingLocalNotificationsjuce::PushNotifications::Pimpl457     void removeAllPendingLocalNotifications()
458     {
459         for (NSUserNotification* n in [NSUserNotificationCenter defaultUserNotificationCenter].scheduledNotifications)
460             [[NSUserNotificationCenter defaultUserNotificationCenter] removeScheduledNotification: n];
461     }
462 
getDeviceTokenjuce::PushNotifications::Pimpl463     String getDeviceToken()
464     {
465         // You need to call requestPermissionsWithSettings() first.
466         jassert (initialised);
467 
468         return deviceToken;
469     }
470 
471     //==============================================================================
472     //PushNotificationsDelegate
registeredForRemoteNotificationsjuce::PushNotifications::Pimpl473     void registeredForRemoteNotifications (NSData* deviceTokenToUse) override
474     {
475         deviceToken = [deviceTokenToUse]() -> String
476         {
477             auto length = deviceTokenToUse.length;
478 
479             if (auto* buffer = (const unsigned char*) deviceTokenToUse.bytes)
480             {
481                 NSMutableString* hexString = [NSMutableString stringWithCapacity: (length * 2)];
482 
483                 for (NSUInteger i = 0; i < length; ++i)
484                     [hexString appendFormat:@"%02x", buffer[i]];
485 
486                 return nsStringToJuce ([hexString copy]);
487             }
488 
489             return {};
490         }();
491 
492         initialised = true;
493 
494         owner.listeners.call ([&] (Listener& l) { l.deviceTokenRefreshed (deviceToken); });
495     }
496 
failedToRegisterForRemoteNotificationsjuce::PushNotifications::Pimpl497     void failedToRegisterForRemoteNotifications (NSError* error) override
498     {
499         ignoreUnused (error);
500         deviceToken.clear();
501     }
502 
didReceiveRemoteNotificationjuce::PushNotifications::Pimpl503     void didReceiveRemoteNotification (NSDictionary* userInfo) override
504     {
505         auto n = PushNotificationsDelegateDetailsOsx::nsDictionaryToJuceNotification (userInfo);
506         owner.listeners.call ([&] (Listener& l) { l.handleNotification (true, n); });
507     }
508 
didDeliverNotificationjuce::PushNotifications::Pimpl509     void didDeliverNotification (NSUserNotification* notification) override
510     {
511         ignoreUnused (notification);
512     }
513 
didActivateNotificationjuce::PushNotifications::Pimpl514     void didActivateNotification (NSUserNotification* notification) override
515     {
516         auto n = PushNotificationsDelegateDetailsOsx::nsUserNotificationToJuceNotification (notification, isEarlierThanMavericks, isEarlierThanYosemite);
517 
518         if (notification.activationType == NSUserNotificationActivationTypeContentsClicked)
519         {
520             owner.listeners.call ([&] (Listener& l) { l.handleNotification (notification.remote, n); });
521         }
522         else
523         {
524             auto actionIdentifier = (! isEarlierThanYosemite && notification.additionalActivationAction != nil)
525                                         ? nsStringToJuce (notification.additionalActivationAction.identifier)
526                                         : nsStringToJuce (notification.actionButtonTitle);
527 
528             auto reply = notification.activationType == NSUserNotificationActivationTypeReplied
529                             ? nsStringToJuce ([notification.response string])
530                             : String();
531 
532             owner.listeners.call ([&] (Listener& l) { l.handleNotificationAction (notification.remote, n, actionIdentifier, reply); });
533         }
534     }
535 
shouldPresentNotificationjuce::PushNotifications::Pimpl536     bool shouldPresentNotification (NSUserNotification*) override { return true; }
537 
subscribeToTopicjuce::PushNotifications::Pimpl538     void subscribeToTopic (const String& topic)     { ignoreUnused (topic); }
unsubscribeFromTopicjuce::PushNotifications::Pimpl539     void unsubscribeFromTopic (const String& topic) { ignoreUnused (topic); }
540 
sendUpstreamMessagejuce::PushNotifications::Pimpl541     void sendUpstreamMessage (const String& serverSenderId,
542                               const String& collapseKey,
543                               const String& messageId,
544                               const String& messageType,
545                               int timeToLive,
546                               const StringPairArray& additionalData)
547     {
548         ignoreUnused (serverSenderId, collapseKey, messageId, messageType);
549         ignoreUnused (timeToLive, additionalData);
550     }
551 
552 private:
553     PushNotifications& owner;
554 
555     const bool isEarlierThanLion      = std::floor (NSFoundationVersionNumber) < std::floor (NSFoundationVersionNumber10_7);
556     const bool isAtLeastMountainLion  = std::floor (NSFoundationVersionNumber) >= NSFoundationVersionNumber10_7;
557     const bool isEarlierThanMavericks = std::floor (NSFoundationVersionNumber) < NSFoundationVersionNumber10_9;
558     const bool isEarlierThanYosemite  = std::floor (NSFoundationVersionNumber) <= NSFoundationVersionNumber10_9;
559 
560     bool initialised = false;
561     String deviceToken;
562 
563     PushNotifications::Settings settings;
564 };
565 
566 } // namespace juce
567 
568 JUCE_END_IGNORE_WARNINGS_GCC_LIKE
569