diff --git a/Command/TestPushCommand.php b/Command/TestPushCommand.php index df87e40..7dd9011 100644 --- a/Command/TestPushCommand.php +++ b/Command/TestPushCommand.php @@ -30,7 +30,7 @@ protected function configure() ->setDescription("Sends a push command to a supplied push token'd device") ->addOption("badge", "b", InputOption::VALUE_OPTIONAL, "Badge number (for iOS devices)", 0) ->addOption("text", "t", InputOption::VALUE_OPTIONAL, "Text message") - ->addArgument("service", InputArgument::REQUIRED, "One of 'ios', 'c2dm', 'gcm', 'mac', 'blackberry' or 'windowsphone'") + ->addArgument("service", InputArgument::REQUIRED, "One of 'ios', 'c2dm', 'gcm', 'fcm', 'mac', 'blackberry' or 'windowsphone'") ->addArgument("token", InputArgument::REQUIRED, "Authentication token for the service") ->addArgument("payload", InputArgument::OPTIONAL, "The payload data to send (JSON)", '{"data": "test"}') ; @@ -111,6 +111,11 @@ protected function getMessageClass($service) $message = new PushMessage\AndroidMessage(); $message->setGCM(true); + return $message; + case "fcm": + $message = new PushMessage\AndroidMessage(); + $message->setFCM(true); + return $message; case "blackberry": return new PushMessage\BlackberryMessage(); diff --git a/DependencyInjection/Configuration.php b/DependencyInjection/Configuration.php index 9d8f30f..f9d59c3 100644 --- a/DependencyInjection/Configuration.php +++ b/DependencyInjection/Configuration.php @@ -68,6 +68,13 @@ protected function addAndroid() booleanNode("dry_run")->defaultFalse()->end()-> end()-> end()-> + arrayNode("fcm")-> + canBeUnset()-> + children()-> + scalarNode("api_key")->isRequired()->cannotBeEmpty()->end()-> + booleanNode("use_multi_curl")->defaultValue(true)->end()-> + end()-> + end()-> end()-> end()-> end() diff --git a/DependencyInjection/RMSPushNotificationsExtension.php b/DependencyInjection/RMSPushNotificationsExtension.php index a0c4745..c35d140 100644 --- a/DependencyInjection/RMSPushNotificationsExtension.php +++ b/DependencyInjection/RMSPushNotificationsExtension.php @@ -97,11 +97,18 @@ protected function setAndroidConfig(array $config) // GCM $this->container->setParameter("rms_push_notifications.android.gcm.enabled", isset($config["android"]["gcm"])); - if (isset($config["android"]["gcm"])) { - $this->container->setParameter("rms_push_notifications.android.gcm.api_key", $config["android"]["gcm"]["api_key"]); - $this->container->setParameter("rms_push_notifications.android.gcm.use_multi_curl", $config["android"]["gcm"]["use_multi_curl"]); - $this->container->setParameter('rms_push_notifications.android.gcm.dry_run', $config["android"]["gcm"]["dry_run"]); - } +// if (isset($config["android"]["gcm"])) { + $this->container->setParameter("rms_push_notifications.android.gcm.api_key", isset($config["android"]["gcm"]["api_key"]) ? $config["android"]["gcm"]["api_key"] : null); + $this->container->setParameter("rms_push_notifications.android.gcm.use_multi_curl", isset($config["android"]["gcm"]["use_multi_curl"]) ? $config["android"]["gcm"]["use_multi_curl"] : null); + $this->container->setParameter('rms_push_notifications.android.gcm.dry_run', isset($config["android"]["gcm"]["dry_run"]) ? $config["android"]["gcm"]["dry_run"] : null); +// } + + // FCM + $this->container->setParameter("rms_push_notifications.android.fcm.enabled", isset($config["android"]["fcm"])); +// if (isset($config["android"]["fcm"])) { + $this->container->setParameter("rms_push_notifications.android.fcm.api_key", isset($config["android"]["fcm"]["api_key"]) ? $config["android"]["fcm"]["api_key"] : null); + $this->container->setParameter("rms_push_notifications.android.fcm.use_multi_curl", isset($config["android"]["fcm"]["use_multi_curl"]) ? $config["android"]["fcm"]["use_multi_curl"] : null); +// } } /** diff --git a/Device/Types.php b/Device/Types.php index c97e408..a686cbe 100644 --- a/Device/Types.php +++ b/Device/Types.php @@ -6,6 +6,7 @@ class Types { const OS_ANDROID_C2DM = "rms_push_notifications.os.android.c2dm"; const OS_ANDROID_GCM = "rms_push_notifications.os.android.gcm"; + const OS_ANDROID_FCM = "rms_push_notifications.os.android.fcm"; const OS_IOS = "rms_push_notifications.os.ios"; const OS_MAC = "rms_push_notifications.os.mac"; const OS_BLACKBERRY = "rms_push_notifications.os.blackberry"; diff --git a/Message/AndroidMessage.php b/Message/AndroidMessage.php index 1d9f04b..734d6c9 100644 --- a/Message/AndroidMessage.php +++ b/Message/AndroidMessage.php @@ -43,6 +43,13 @@ class AndroidMessage implements MessageInterface */ protected $isGCM = false; + /** + * Whether this is a FCM message + * + * @var bool + */ + protected $isFCM = false; + /** * A collection of device identifiers that the message * is intended for. GCM use only @@ -58,6 +65,13 @@ class AndroidMessage implements MessageInterface */ protected $gcmOptions = array(); + /** + * Options for FCM messages + * + * @var array + */ + protected $fcmOptions = array(); + /** * Sets the string message * @@ -136,7 +150,15 @@ public function setDeviceIdentifier($identifier) */ public function getTargetOS() { - return ($this->isGCM ? Types::OS_ANDROID_GCM : Types::OS_ANDROID_C2DM); + if($this->isGCM) { + return Types::OS_ANDROID_GCM; + } + + if($this->isFCM) { + return Types::OS_ANDROID_FCM; + } + + return Types::OS_ANDROID_C2DM; } /** @@ -192,6 +214,27 @@ public function isGCM() return $this->isGCM; } + /** + * Set whether this is a FCM message + * (default false) + * + * @param $fcm + */ + public function setFCM($fcm) + { + $this->isFCM = !!$fcm; + } + + /** + * Returns whether this is a FCM message + * + * @return mixed + */ + public function isFCM() + { + return $this->isFCM; + } + /** * Returns an array of device identifiers * Not used in C2DM @@ -212,6 +255,26 @@ public function addGCMIdentifier($identifier) $this->allIdentifiers[$identifier] = $identifier; } + /** + * Returns an array of device identifiers + * Not used in C2DM + * + * @return mixed + */ + public function getFCMIdentifiers() + { + return array_values($this->allIdentifiers); + } + + /** + * Adds a device identifier to the FCM list + * @param string $identifier + */ + public function addFCMIdentifier($identifier) + { + $this->allIdentifiers[$identifier] = $identifier; + } + /** * Sets the GCM list * @param array $allIdentifiers @@ -238,4 +301,23 @@ public function getGCMOptions() { return $this->gcmOptions; } + + /** + * Sets FCM options + * @param array $options + */ + public function setFCMOptions($options) + { + $this->fcmOptions = $options; + } + + /** + * Returns FCM options + * + * @return array + */ + public function getFCMOptions() + { + return $this->fcmOptions; + } } diff --git a/README.md b/README.md index e3cc8af..81daf04 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # RMSPushNotificationsBundle ![](https://secure.travis-ci.org/richsage/RMSPushNotificationsBundle.png) -A bundle to allow sending of push notifications to mobile devices. Currently supports Android (C2DM, GCM), Blackberry and iOS devices. +A bundle to allow sending of push notifications to mobile devices. Currently supports Android (C2DM, GCM, FCM), Blackberry and iOS devices. ## Installation @@ -44,6 +44,9 @@ only be available if you provide configuration respectively for them. api_key: # This is titled "Server Key" when creating it use_multi_curl: # default is true dry_run: + fcm: + api_key: # This is titled "Server Key" when creating it + use_multi_curl: # default is true ios: timeout: 60 # Seconds to wait for connection timeout, default is 60 sandbox: @@ -62,7 +65,7 @@ only be available if you provide configuration respectively for them. windowsphone: timeout: 5 # Seconds to wait for connection timeout, default is 5 -NOTE: If you are using Windows, you may need to set the Android GCM `use_multi_curl` flag to false for GCM messages to be sent correctly. +NOTE: If you are using Windows, you may need to set the Android GCM/FCM `use_multi_curl` flag to false for GCM/FCM messages to be sent correctly. Timeout defaults are the defaults from prior to the introduction of this configuration value. @@ -89,15 +92,15 @@ A little example of how to push your first message to an iOS device, we'll assum The send method will detect the type of message so if you'll pass it an `AndroidMessage` it will automatically send it through the C2DM/GCM servers, and likewise for Mac and Blackberry. ## Android messages - -Since both C2DM and GCM are still available, the `AndroidMessage` class has a small flag on it to toggle which service to send it to. Use as follows: +You have a choice of three services, C2DM, GCM and FCM. C2DM is defined as main option, but the `AndroidMessage` class has flags to toggle which service to send it to. use RMS\PushNotificationsBundle\Message\AndroidMessage; $message = new AndroidMessage(); - $message->setGCM(true); -to send as a GCM message rather than C2DM. +You may choose `setGCM` or `setFCM` - this is optional. + + $message->setFCM(true); ## iOS Feedback service diff --git a/Resources/config/android.xml b/Resources/config/android.xml index 5802e00..e6cf521 100644 --- a/Resources/config/android.xml +++ b/Resources/config/android.xml @@ -6,6 +6,7 @@ RMS\PushNotificationsBundle\Service\OS\AndroidNotification RMS\PushNotificationsBundle\Service\OS\AndroidGCMNotification + RMS\PushNotificationsBundle\Service\OS\AndroidFCMNotification @@ -30,6 +31,16 @@ + + + %rms_push_notifications.android.fcm.api_key% + %rms_push_notifications.android.fcm.use_multi_curl% + %rms_push_notifications.android.timeout% + + null + + + diff --git a/Service/OS/AndroidFCMNotification.php b/Service/OS/AndroidFCMNotification.php new file mode 100644 index 0000000..2dd6eb8 --- /dev/null +++ b/Service/OS/AndroidFCMNotification.php @@ -0,0 +1,158 @@ +apiKey = $apiKey; + if (!$client) { + $client = ($useMultiCurl ? new MultiCurl() : new Curl()); + } + $client->setTimeout($timeout); + + $this->browser = new Browser($client); + $this->browser->getClient()->setVerifyPeer(false); + $this->logger = $logger; + } + + /** + * Sends the data to the given registration IDs via the FCM server + * + * @param \RMS\PushNotificationsBundle\Message\MessageInterface $message + * @throws \RMS\PushNotificationsBundle\Exception\InvalidMessageTypeException + * @return bool + */ + public function send(MessageInterface $message) + { + if (!$message instanceof AndroidMessage) { + throw new InvalidMessageTypeException(sprintf("Message type '%s' not supported by FCM", get_class($message))); + } + if (!$message->isFCM()) { + throw new InvalidMessageTypeException("Non-FCM messages not supported by the Android FCM sender"); + } + + $headers = array( + "Authorization: key=" . $this->apiKey, + "Content-Type: application/json", + ); + $data = array_merge( + $message->getFCMOptions(), + array("data" => $message->getData()) + ); + + // Perform the calls (in parallel) + $this->responses = array(); + $fcmIdentifiers = $message->getFCMIdentifiers(); + + if (count($message->getFCMIdentifiers()) == 1) { + $data['to'] = $fcmIdentifiers[0]; + $this->responses[] = $this->browser->post($this->apiURL, $headers, json_encode($data)); + } else { + // Chunk number of registration IDs according to the maximum allowed by FCM + $chunks = array_chunk($message->getFCMIdentifiers(), $this->registrationIdMaxCount); + + foreach ($chunks as $registrationIDs) { + $data['registration_ids'] = $registrationIDs; + $this->responses[] = $this->browser->post($this->apiURL, $headers, json_encode($data)); + } + } + + // If we're using multiple concurrent connections via MultiCurl + // then we should flush all requests + if ($this->browser->getClient() instanceof MultiCurl) { + $this->browser->getClient()->flush(); + } + + // Determine success + foreach ($this->responses as $response) { + $message = json_decode($response->getContent()); + if ($message === null || $message->success == 0 || $message->failure > 0) { + if ($message == null) { + $this->logger->error($response->getContent()); + } else { + foreach ($message->results as $result) { + if (isset($result->error)) { + $this->logger->error($result->error); + } + } + } + return false; + } + } + + return true; + } + + /** + * Returns responses + * + * @return array + */ + public function getResponses() + { + return $this->responses; + } +} diff --git a/Tests/DependencyInjection/ConfigurationTest.php b/Tests/DependencyInjection/ConfigurationTest.php index b6e64aa..31e4f7d 100644 --- a/Tests/DependencyInjection/ConfigurationTest.php +++ b/Tests/DependencyInjection/ConfigurationTest.php @@ -125,6 +125,22 @@ public function testGCMIsOK() $this->assertTrue($config["android"]["gcm"]["dry_run"]); } + public function testFCMIsOK() + { + $arr = array( + array( + "android" => array( + "fcm" => array( + "api_key" => "foo", + "use_multi_curl" => true, + ) + ) + ), + ); + $config = $this->process($arr); + $this->assertEquals("foo", $config["android"]["fcm"]["api_key"]); + } + /** * @expectedException \Symfony\Component\Config\Definition\Exception\InvalidConfigurationException */