@@ -32,7 +36,11 @@
org.springframework.boot
spring-boot-maven-plugin
- true
+
+ exec
diff --git a/push-service-client-lib/readme.txt b/push-service-client-lib/readme.txt
new file mode 100644
index 0000000..a47e019
--- /dev/null
+++ b/push-service-client-lib/readme.txt
@@ -0,0 +1,15 @@
+1.
+======================
+
+
+2. How to use the client-lib in a client application
+====================================================
+- Include property 'push-service-client.serverUrl=' and set it to the path to the server part
+- (optional) Enable loggin via property 'logging.level.de.pushservice=DEBUG'
+- Enable component scan in app configuration via '@ComponentScan("de.pushservice.client")'
+- Obtain an instance of SubscriptionService via '@Autowired'
+- Add a JavaScript file with content 'registerServiceWorker();' to register the service worker and subscribing at the
+ third party push service
+- Add the created JavaScript file to HTML via ''
+- Also add the PushService client JavaScript to HTML via
+ ''
\ No newline at end of file
diff --git a/push-service-client-lib/src/main/java/de/pushservice/client/PushServiceClientApplication.java b/push-service-client-lib/src/main/java/de/pushservice/client/PushServiceClientApplication.java
new file mode 100644
index 0000000..ac96a1c
--- /dev/null
+++ b/push-service-client-lib/src/main/java/de/pushservice/client/PushServiceClientApplication.java
@@ -0,0 +1,21 @@
+package de.pushservice.client;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.builder.SpringApplicationBuilder;
+import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
+
+/**
+ * Not used but required by the spring boot maven plugin during repackage
+ */
+@SpringBootApplication
+public class PushServiceClientApplication extends SpringBootServletInitializer {
+ public static void main(String[] args) {
+ SpringApplication.run(PushServiceClientApplication.class);
+ }
+
+ @Override
+ protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
+ return application.sources(PushServiceClientApplication.class);
+ }
+}
\ No newline at end of file
diff --git a/push-service-client-lib/src/main/java/de/pushservice/client/config/PushServiceClientConfig.java b/push-service-client-lib/src/main/java/de/pushservice/client/config/PushServiceClientConfig.java
new file mode 100644
index 0000000..7b40e75
--- /dev/null
+++ b/push-service-client-lib/src/main/java/de/pushservice/client/config/PushServiceClientConfig.java
@@ -0,0 +1,18 @@
+package de.pushservice.client.config;
+
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+@ConfigurationProperties(prefix = "push-service-client")
+public class PushServiceClientConfig {
+ private String serverUrl;
+
+ public String getServerUrl() {
+ return serverUrl;
+ }
+
+ public void setServerUrl(String serverUrl) {
+ this.serverUrl = serverUrl;
+ }
+}
diff --git a/push-service-client-lib/src/main/java/de/pushservice/client/controller/SubscriptionController.java b/push-service-client-lib/src/main/java/de/pushservice/client/controller/SubscriptionController.java
new file mode 100644
index 0000000..8d63749
--- /dev/null
+++ b/push-service-client-lib/src/main/java/de/pushservice/client/controller/SubscriptionController.java
@@ -0,0 +1,29 @@
+package de.pushservice.client.controller;
+
+import de.pushservice.client.ResponseReason;
+import de.pushservice.client.dto.SubscriptionDto;
+import de.pushservice.client.service.SubscriptionService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.ResponseEntity;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+
+@Controller
+public class SubscriptionController {
+ private static final Logger LOGGER = LoggerFactory.getLogger(SubscriptionController.class);
+
+ @Autowired
+ private SubscriptionService subscriptionService;
+
+ @PostMapping("/subscription/subscribe")
+ public ResponseEntity subscribe(@RequestBody SubscriptionDto subscriptionDto) {
+ LOGGER.debug(String.format("Received subscription for endpoint %s", subscriptionDto.getEndpoint()));
+
+ this.subscriptionService.subscribe(subscriptionDto);
+
+ return ResponseReason.OK.toResponseEntity();
+ }
+}
diff --git a/push-service-client-lib/src/main/java/de/pushservice/client/dto/MessageDto.java b/push-service-client-lib/src/main/java/de/pushservice/client/dto/MessageDto.java
index 37f8105..464ee50 100644
--- a/push-service-client-lib/src/main/java/de/pushservice/client/dto/MessageDto.java
+++ b/push-service-client-lib/src/main/java/de/pushservice/client/dto/MessageDto.java
@@ -7,6 +7,12 @@ public class MessageDto {
private String topic;
private Urgency urgency;
+ public MessageDto(String payload, String topic, Urgency urgency) {
+ this.payload = payload;
+ this.topic = topic;
+ this.urgency = urgency;
+ }
+
public String getPayload() {
return payload;
}
diff --git a/push-service-client-lib/src/main/java/de/pushservice/client/dto/NotificationRequestDto.java b/push-service-client-lib/src/main/java/de/pushservice/client/dto/NotificationRequestDto.java
index b808b45..4758416 100644
--- a/push-service-client-lib/src/main/java/de/pushservice/client/dto/NotificationRequestDto.java
+++ b/push-service-client-lib/src/main/java/de/pushservice/client/dto/NotificationRequestDto.java
@@ -4,6 +4,11 @@ public class NotificationRequestDto {
private SubscriptionDto subscriptionDto;
private MessageDto messageDto;
+ public NotificationRequestDto(SubscriptionDto subscriptionDto, MessageDto messageDto) {
+ this.subscriptionDto = subscriptionDto;
+ this.messageDto = messageDto;
+ }
+
public SubscriptionDto getSubscriptionDto() {
return subscriptionDto;
}
diff --git a/push-service-client-lib/src/main/java/de/pushservice/client/dto/PayloadDto.java b/push-service-client-lib/src/main/java/de/pushservice/client/dto/PayloadDto.java
new file mode 100644
index 0000000..921343d
--- /dev/null
+++ b/push-service-client-lib/src/main/java/de/pushservice/client/dto/PayloadDto.java
@@ -0,0 +1,63 @@
+package de.pushservice.client.dto;
+
+/**
+ *
+ * Payload for a notification. Gets sent to the third party push service which forwards it to the user agent.
+ *
+ *
+ * Evaluated after receival in the push-service-client-service-worker.js service worker.
+ *
+ *
+ * @see MDN spec for notification payload
+ */
+public class PayloadDto {
+ private String title;
+ private String message;
+ private String icon;
+ private int[] vibration;
+ private long timestamp;
+
+ public PayloadDto(String title) {
+ this.title = title;
+ }
+
+ public String getMessage() {
+ return message;
+ }
+
+ public void setMessage(String message) {
+ this.message = message;
+ }
+
+ public String getIcon() {
+ return icon;
+ }
+
+ public void setIcon(String icon) {
+ this.icon = icon;
+ }
+
+ public int[] getVibration() {
+ return vibration;
+ }
+
+ public void setVibration(int[] vibration) {
+ this.vibration = vibration;
+ }
+
+ public long getTimestamp() {
+ return timestamp;
+ }
+
+ public void setTimestamp(long timestamp) {
+ this.timestamp = timestamp;
+ }
+
+ public String getTitle() {
+ return title;
+ }
+
+ public void setTitle(String title) {
+ this.title = title;
+ }
+}
diff --git a/push-service-client-lib/src/main/java/de/pushservice/client/service/SubscriptionService.java b/push-service-client-lib/src/main/java/de/pushservice/client/service/SubscriptionService.java
new file mode 100644
index 0000000..8181686
--- /dev/null
+++ b/push-service-client-lib/src/main/java/de/pushservice/client/service/SubscriptionService.java
@@ -0,0 +1,66 @@
+package de.pushservice.client.service;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import de.pushservice.client.ResponseReason;
+import de.pushservice.client.config.PushServiceClientConfig;
+import de.pushservice.client.dto.MessageDto;
+import de.pushservice.client.dto.NotificationRequestDto;
+import de.pushservice.client.dto.PayloadDto;
+import de.pushservice.client.dto.SubscriptionDto;
+import de.pushservice.client.model.Urgency;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpEntity;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.ResponseEntity;
+import org.springframework.stereotype.Service;
+import org.springframework.web.client.RestTemplate;
+
+import java.util.*;
+
+@Service
+public class SubscriptionService {
+ private static final Logger LOGGER = LoggerFactory.getLogger(SubscriptionService.class);
+
+ @Autowired
+ private PushServiceClientConfig config;
+
+ @Autowired
+ private ObjectMapper objectMapper;
+
+ private Set subscriptions = new HashSet<>();
+
+ public void subscribe(SubscriptionDto subscriptionDto) {
+ this.subscriptions.add(subscriptionDto);
+ }
+
+ public int notifyAll(PayloadDto payload, String topic, Urgency urgency) throws JsonProcessingException {
+ int notificationsSend = 0;
+ final Urgency tmpUrgency = Optional.ofNullable(urgency).orElse(Urgency.NORMAL);
+
+ final MessageDto messageDto = new MessageDto(objectMapper.writeValueAsString(payload), topic, tmpUrgency);
+
+ for (SubscriptionDto subscription : this.subscriptions) {
+ final NotificationRequestDto requestDto = new NotificationRequestDto(subscription, messageDto);
+
+ LOGGER.debug(String.format("Sending notification for endpoint %s", subscription.getEndpoint()));
+
+ final ResponseEntity responseEntity = new RestTemplate()
+ .exchange(this.config.getServerUrl(), HttpMethod.POST, new HttpEntity<>(requestDto), String.class);
+
+ final ResponseReason responseReason = ResponseReason.fromResponseEntity(responseEntity);
+
+ if (ResponseReason.OK == responseReason) {
+ notificationsSend++;
+ }
+ else {
+ // Well, nothing we can do about it
+ LOGGER.error("Sending notification to endpoint failed! %s", responseReason);
+ }
+ }
+
+ return notificationsSend;
+ }
+}
diff --git a/push-service-client-lib/src/main/resources/static/javascript/push-service-client-init.js b/push-service-client-lib/src/main/resources/static/javascript/push-service-client-init.js
new file mode 100644
index 0000000..7053990
--- /dev/null
+++ b/push-service-client-lib/src/main/resources/static/javascript/push-service-client-init.js
@@ -0,0 +1,82 @@
+function registerServiceWorker() {
+ if ('serviceWorker' in navigator) {
+ window.addEventListener('load', function() {
+ navigator.serviceWorker.register('push-service-client-service-worker.js').then(function(registration) {
+ // Registration was successful
+ console.log('ServiceWorker registration successful with scope: ', registration.scope);
+
+ initializeState();
+ }, function(err) {
+ // registration failed :(
+ console.log('ServiceWorker registration failed: ', err);
+ });
+ });
+ }
+}
+
+function initializeState() {
+ if (!('showNotification' in ServiceWorkerRegistration.prototype)) {
+ console.warn('Notifications aren\'t supported.');
+ return;
+ }
+
+ if (Notification.permission === 'denied') {
+ console.warn('The user has blocked notifications.');
+ return;
+ }
+
+ if (!('PushManager' in window)) {
+ console.warn('Push messaging isn\'t supported.');
+ return;
+ }
+
+ navigator.serviceWorker.ready.then(function (serviceWorkerRegistration) {
+ serviceWorkerRegistration.pushManager.getSubscription().then(function (subscription) {
+ if (!subscription) {
+ subscribe();
+ return;
+ }
+
+ sendSubscriptionToServer(subscription);
+ })
+ .catch(function(err) {
+ console.warn('Error during getSubscription()', err);
+ });
+ });
+}
+
+function subscribe() {
+ navigator.serviceWorker.ready.then(function (serviceWorkerRegistration) {
+ serviceWorkerRegistration.pushManager.subscribe({userVisibleOnly: true}).then(function (subscription) {
+ return sendSubscriptionToServer(subscription);
+ })
+ .catch(function (e) {
+ if (Notification.permission === 'denied') {
+ console.warn('Permission for Notifications was denied');
+ } else {
+ console.error('Unable to subscribe to push.', e);
+ }
+ });
+ });
+}
+
+function sendSubscriptionToServer(subscription) {
+ var key = subscription.getKey ? subscription.getKey('p256dh') : '';
+ var auth = subscription.getKey ? subscription.getKey('auth') : '';
+
+ console.log('Sending subscription to push-service-client');
+
+ return fetch('subscription/subscribe', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json'
+ },
+ body: JSON.stringify({
+ endpoint: subscription.endpoint,
+ // Take byte[] and turn it into a base64 encoded string suitable for
+ // POSTing to a server over HTTP
+ key: key ? btoa(String.fromCharCode.apply(null, new Uint8Array(key))) : '',
+ auth: auth ? btoa(String.fromCharCode.apply(null, new Uint8Array(auth))) : ''
+ })
+ });
+}
\ No newline at end of file
diff --git a/push-service-client-lib/src/main/resources/static/push-service-client-service-worker.js b/push-service-client-lib/src/main/resources/static/push-service-client-service-worker.js
new file mode 100644
index 0000000..658be18
--- /dev/null
+++ b/push-service-client-lib/src/main/resources/static/push-service-client-service-worker.js
@@ -0,0 +1,14 @@
+// The worker cannot reside in javascript/ because of the scope policy for service workers defined in the spec
+
+self.addEventListener('push', function(event) {
+ const payload = event.data.json();
+
+ event.waitUntil(
+ self.registration.showNotification(payload.title, {
+ body: payload.message,
+ icon: payload.icon,
+ vibrate: payload.vibration,
+ timestamp: payload.timestamp,
+ })
+ );
+});
\ No newline at end of file
diff --git a/push-service-server/src/main/java/de/pushservice/server/PushServiceApplication.java b/push-service-server/src/main/java/de/pushservice/server/PushServiceApplication.java
index 26e4175..e2bdeea 100644
--- a/push-service-server/src/main/java/de/pushservice/server/PushServiceApplication.java
+++ b/push-service-server/src/main/java/de/pushservice/server/PushServiceApplication.java
@@ -5,6 +5,9 @@ import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
+/**
+ * This whole application is basically just a thin layer around the webpush-java lib
+ */
@SpringBootApplication
public class PushServiceApplication extends SpringBootServletInitializer {
public static void main(String[] args) {
diff --git a/push-service-server/src/main/java/de/pushservice/server/controller/PushServiceController.java b/push-service-server/src/main/java/de/pushservice/server/controller/PushServiceController.java
index e06ba8b..9f91079 100644
--- a/push-service-server/src/main/java/de/pushservice/server/controller/PushServiceController.java
+++ b/push-service-server/src/main/java/de/pushservice/server/controller/PushServiceController.java
@@ -6,18 +6,26 @@ import de.pushservice.server.decorator.MessageDecorator;
import de.pushservice.server.decorator.SubscriptionDecorator;
import nl.martijndwars.webpush.Notification;
import nl.martijndwars.webpush.PushService;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
+import java.security.Security;
+
@RestController
public class PushServiceController {
private static final Logger LOGGER = LoggerFactory.getLogger(PushServiceController.class);
+ static {
+ Security.addProvider(new BouncyCastleProvider());
+ }
+
@PostMapping
- public ResponseEntity notify(NotificationRequestDto notificationRequestDto) {
+ public ResponseEntity notify(@RequestBody NotificationRequestDto notificationRequestDto) {
try {
final SubscriptionDecorator subscription = SubscriptionDecorator
.fromDto(notificationRequestDto.getSubscriptionDto());
diff --git a/push-service-server/src/main/resources/config/application.properties b/push-service-server/src/main/resources/config/application.properties
new file mode 100644
index 0000000..4a7a510
--- /dev/null
+++ b/push-service-server/src/main/resources/config/application.properties
@@ -0,0 +1,13 @@
+server.servlet.context-path=/push-service-server
+server.port=8077
+
+info.app.name=PushService server
+info.app.description=A simple server for webpush
+info.build.group=@project.groupId@
+info.build.artifact=@project.artifactId@
+info.build.version=@project.version@
+
+logging.level.de.pushservice=DEBUG
+logging.file=push-service-server.log
+logging.file.max-history=7
+logging.file.max-size=50MB
\ No newline at end of file