Stuff all over the place
This commit is contained in:
@@ -24,6 +24,10 @@
|
|||||||
<groupId>org.springframework</groupId>
|
<groupId>org.springframework</groupId>
|
||||||
<artifactId>spring-web</artifactId>
|
<artifactId>spring-web</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-web</artifactId>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
@@ -32,7 +36,11 @@
|
|||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||||
<configuration>
|
<configuration>
|
||||||
<skip>true</skip>
|
<!--
|
||||||
|
Setting the classifier of the spring-boot uber JAR to exec will keep the verbatim JAR file
|
||||||
|
as actual distributed artifact required for users to include the beans
|
||||||
|
-->
|
||||||
|
<classifier>exec</classifier>
|
||||||
</configuration>
|
</configuration>
|
||||||
</plugin>
|
</plugin>
|
||||||
</plugins>
|
</plugins>
|
||||||
|
|||||||
15
push-service-client-lib/readme.txt
Normal file
15
push-service-client-lib/readme.txt
Normal file
@@ -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 '<script th:src="@{/javascript/init.js}"></script>'
|
||||||
|
- Also add the PushService client JavaScript to HTML via
|
||||||
|
'<script th:src="@{/javascript/push-service-client-init.js}"></script>'
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,6 +7,12 @@ public class MessageDto {
|
|||||||
private String topic;
|
private String topic;
|
||||||
private Urgency urgency;
|
private Urgency urgency;
|
||||||
|
|
||||||
|
public MessageDto(String payload, String topic, Urgency urgency) {
|
||||||
|
this.payload = payload;
|
||||||
|
this.topic = topic;
|
||||||
|
this.urgency = urgency;
|
||||||
|
}
|
||||||
|
|
||||||
public String getPayload() {
|
public String getPayload() {
|
||||||
return payload;
|
return payload;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,11 @@ public class NotificationRequestDto {
|
|||||||
private SubscriptionDto subscriptionDto;
|
private SubscriptionDto subscriptionDto;
|
||||||
private MessageDto messageDto;
|
private MessageDto messageDto;
|
||||||
|
|
||||||
|
public NotificationRequestDto(SubscriptionDto subscriptionDto, MessageDto messageDto) {
|
||||||
|
this.subscriptionDto = subscriptionDto;
|
||||||
|
this.messageDto = messageDto;
|
||||||
|
}
|
||||||
|
|
||||||
public SubscriptionDto getSubscriptionDto() {
|
public SubscriptionDto getSubscriptionDto() {
|
||||||
return subscriptionDto;
|
return subscriptionDto;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,63 @@
|
|||||||
|
package de.pushservice.client.dto;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>
|
||||||
|
* Payload for a notification. Gets sent to the third party push service which forwards it to the user agent.
|
||||||
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* Evaluated after receival in the <code>push-service-client-service-worker.js</code> service worker.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @see <a href="https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerRegistration/showNotification">MDN spec for notification payload</a>
|
||||||
|
*/
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<SubscriptionDto> 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<String> 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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))) : ''
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -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,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
@@ -5,6 +5,9 @@ import org.springframework.boot.autoconfigure.SpringBootApplication;
|
|||||||
import org.springframework.boot.builder.SpringApplicationBuilder;
|
import org.springframework.boot.builder.SpringApplicationBuilder;
|
||||||
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
|
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This whole application is basically just a thin layer around the webpush-java lib
|
||||||
|
*/
|
||||||
@SpringBootApplication
|
@SpringBootApplication
|
||||||
public class PushServiceApplication extends SpringBootServletInitializer {
|
public class PushServiceApplication extends SpringBootServletInitializer {
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
|
|||||||
@@ -6,18 +6,26 @@ import de.pushservice.server.decorator.MessageDecorator;
|
|||||||
import de.pushservice.server.decorator.SubscriptionDecorator;
|
import de.pushservice.server.decorator.SubscriptionDecorator;
|
||||||
import nl.martijndwars.webpush.Notification;
|
import nl.martijndwars.webpush.Notification;
|
||||||
import nl.martijndwars.webpush.PushService;
|
import nl.martijndwars.webpush.PushService;
|
||||||
|
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
import java.security.Security;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
public class PushServiceController {
|
public class PushServiceController {
|
||||||
private static final Logger LOGGER = LoggerFactory.getLogger(PushServiceController.class);
|
private static final Logger LOGGER = LoggerFactory.getLogger(PushServiceController.class);
|
||||||
|
|
||||||
|
static {
|
||||||
|
Security.addProvider(new BouncyCastleProvider());
|
||||||
|
}
|
||||||
|
|
||||||
@PostMapping
|
@PostMapping
|
||||||
public ResponseEntity notify(NotificationRequestDto notificationRequestDto) {
|
public ResponseEntity notify(@RequestBody NotificationRequestDto notificationRequestDto) {
|
||||||
try {
|
try {
|
||||||
final SubscriptionDecorator subscription = SubscriptionDecorator
|
final SubscriptionDecorator subscription = SubscriptionDecorator
|
||||||
.fromDto(notificationRequestDto.getSubscriptionDto());
|
.fromDto(notificationRequestDto.getSubscriptionDto());
|
||||||
|
|||||||
@@ -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
|
||||||
Reference in New Issue
Block a user