Skip to content

Commit 3ad3cad

Browse files
committed
V1
1 parent 9d03123 commit 3ad3cad

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+4000
-0
lines changed

.github/workflows/ci.yml

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
name: CI
2+
3+
on: [push]
4+
5+
jobs:
6+
build:
7+
runs-on: ubuntu-latest
8+
9+
steps:
10+
- uses: actions/checkout@v2
11+
- name: Set up JDK 17
12+
uses: actions/setup-java@v2
13+
with:
14+
java-version: '17'
15+
distribution: 'adopt'
16+
- name: Build with Maven
17+
uses: GabrielBB/xvfb-action@v1
18+
with:
19+
run: ./mvnw --batch-mode verify

.gitignore

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
# Created by .ignore support plugin (hsz.mobi)
2+
### JetBrains template
3+
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm
4+
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
5+
6+
# User-specific stuff:
7+
.idea
8+
9+
# Sensitive or high-churn files:
10+
.idea/dataSources.ids
11+
.idea/dataSources.xml
12+
.idea/dataSources.local.xml
13+
.idea/sqlDataSources.xml
14+
.idea/dynamic.xml
15+
.idea/uiDesigner.xml
16+
17+
# Gradle:
18+
.idea/gradle.xml
19+
.idea/libraries
20+
21+
# Mongo Explorer plugin:
22+
.idea/mongoSettings.xml
23+
24+
## File-based project format:
25+
*.iws
26+
*.iml
27+
28+
## Plugin-specific files:
29+
30+
# IntelliJ
31+
/out/
32+
33+
# mpeltonen/sbt-idea plugin
34+
.idea_modules/
35+
36+
# JIRA plugin
37+
atlassian-ide-plugin.xml
38+
39+
# Crashlytics plugin (for Android Studio and IntelliJ)
40+
com_crashlytics_export_strings.xml
41+
crashlytics.properties
42+
crashlytics-build.properties
43+
fabric.properties
44+
### Maven template
45+
target/
46+
pom.xml.tag
47+
pom.xml.releaseBackup
48+
pom.xml.versionsBackup
49+
pom.xml.next
50+
release.properties
51+
dependency-reduced-pom.xml
52+
buildNumber.properties
53+
.mvn/timing.properties
54+

.mvn/jvm.config

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
--add-exports jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED --add-exports jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED --add-exports jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED --add-exports jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED --add-exports jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED --add-opens=java.base/java.util=ALL-UNNAMED --add-opens=java.base/java.lang.reflect=ALL-UNNAMED --add-opens=java.base/java.text=ALL-UNNAMED --add-opens=java.desktop/java.awt.font=ALL-UNNAMED

.mvn/wrapper/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
maven-wrapper.jar

.mvn/wrapper/maven-wrapper.properties

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Licensed to the Apache Software Foundation (ASF) under one
2+
# or more contributor license agreements. See the NOTICE file
3+
# distributed with this work for additional information
4+
# regarding copyright ownership. The ASF licenses this file
5+
# to you under the Apache License, Version 2.0 (the
6+
# "License"); you may not use this file except in compliance
7+
# with the License. You may obtain a copy of the License at
8+
#
9+
# https://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing,
12+
# software distributed under the License is distributed on an
13+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
# KIND, either express or implied. See the License for the
15+
# specific language governing permissions and limitations
16+
# under the License.
17+
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.1/apache-maven-3.8.1-bin.zip
18+
wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar

README.md

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
[![Build Status](https://github.com/Cosium/web-native-messaging-host/actions/workflows/ci.yml/badge.svg)](https://github.com/Cosium/web-native-messaging-host/actions/workflows/ci.yml)
2+
[![Maven Central](https://img.shields.io/maven-central/v/com.cosium.web_native_messaging_host/web-native-messaging-host.svg)](https://search.maven.org/#search%7Cgav%7C1%7Cg%3A%22com.cosium.web_native_messaging_host%22%20AND%20a%3A%22web-native-messaging-host%22)
3+
4+
# Web Native Messaging Host
5+
6+
A java library allowing to turn any JVM application into a [Web Native Messaging Host](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Native_messaging) .
7+
8+
# Gotchas
9+
10+
To date, [Web native messaging protocol](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Native_messaging) only supports `stdio` communication:
11+
- messages are sent to the native application's `stdin`
12+
- messages are sent to the browser extension via native application's `stdout`
13+
14+
Native application (e.g. your JVM application) instances started by the browser must never use `System.out` for anything else than native messaging. Very often, you will have to make sure your logger never writes to `System.out`.
15+
16+
As soon as a `Channel` is open, the current library will swap `System.in` and `System.out` for instances throwing exception on any interaction attempt coming from components foreign to the current library. On `Channel` shutdown, the library will swap back the standard `System.in` and `System.out` instances. The current library cannot cover the full application lifecycle, therefore it is only a best effort.
17+
18+
Using `System.out` (e.g. for logging) before opening a `Channel` will trigger a fatal error on the browser side leading the end of the communication channel and the native application shutdown.
19+
20+
# Quick start
21+
22+
1. Add the following dependency:
23+
```xml
24+
<dependency>
25+
<groupId>com.cosium.web_native_messaging_host</groupId>
26+
<artifactId>web-native-messaging-host</artifactId>
27+
<version>${web-native-messaging-host.version}</version>
28+
</dependency>
29+
```
30+
2. Open the communication channel:
31+
```java
32+
public class App implements MessageHandler {
33+
34+
private final ObjectMapper objectMapper = new ObjectMapper();
35+
36+
public static void main(String[] args) {
37+
Host host = Host.builder(new App()).build();
38+
// Open the communication channel
39+
try (CloseableChannel channel = host.openChannel()) {
40+
// Block until channel shutdown
41+
channel.waitForShutdown();
42+
}
43+
}
44+
45+
@Override
46+
public void onMessage(Channel channel, ContainerNode<?> rawMessage) {
47+
Message message = objectMapper.convertValue(rawMessage, Message.class);
48+
// Print the received message
49+
System.out.printf("Received message '%s'%n", message.body);
50+
// Send a message to the other end
51+
channel.sendMessage(objectMapper.valueToTree(new Message("I got your message")));
52+
}
53+
54+
private record Message(String body) {
55+
}
56+
}
57+
```
58+
59+
# Build
60+
61+
`e2e-tests` can only run on Unix systems.
62+
63+
```shell
64+
./mvnw clean verify
65+
```
66+
67+
# Release
68+
69+
```shell
70+
./release.sh
71+
```

core/pom.xml

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
4+
<modelVersion>4.0.0</modelVersion>
5+
6+
<parent>
7+
<groupId>com.cosium.web_native_messaging_host</groupId>
8+
<artifactId>web-native-messaging-host-parent</artifactId>
9+
<version>1.0-SNAPSHOT</version>
10+
</parent>
11+
12+
<artifactId>web-native-messaging-host</artifactId>
13+
<name>Core</name>
14+
15+
<dependencies>
16+
<dependency>
17+
<groupId>com.fasterxml.jackson.core</groupId>
18+
<artifactId>jackson-databind</artifactId>
19+
</dependency>
20+
21+
<dependency>
22+
<groupId>org.junit.jupiter</groupId>
23+
<artifactId>junit-jupiter</artifactId>
24+
<scope>test</scope>
25+
</dependency>
26+
<dependency>
27+
<groupId>org.assertj</groupId>
28+
<artifactId>assertj-core</artifactId>
29+
<scope>test</scope>
30+
</dependency>
31+
<dependency>
32+
<groupId>com.fasterxml.jackson.jr</groupId>
33+
<artifactId>jackson-jr-objects</artifactId>
34+
<scope>test</scope>
35+
</dependency>
36+
<dependency>
37+
<groupId>org.apache.commons</groupId>
38+
<artifactId>commons-lang3</artifactId>
39+
<scope>test</scope>
40+
</dependency>
41+
</dependencies>
42+
43+
</project>
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package com.cosium.web_native_messaging_host;
2+
3+
import com.fasterxml.jackson.databind.node.ContainerNode;
4+
5+
/**
6+
* @author Réda Housni Alaoui
7+
*/
8+
public interface Channel {
9+
10+
/** Sends a message to the other side */
11+
void sendMessage(ContainerNode<?> message);
12+
13+
/** Pauses the current Thread until the Channel shutdowns */
14+
void waitForShutdown();
15+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package com.cosium.web_native_messaging_host;
2+
3+
/**
4+
* @author Réda Housni Alaoui
5+
*/
6+
public interface CloseableChannel extends Channel, AutoCloseable {
7+
@Override
8+
void close();
9+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package com.cosium.web_native_messaging_host;
2+
3+
/**
4+
* @author Réda Housni Alaoui
5+
*/
6+
public interface CloseableStdinLease extends StdinLease, AutoCloseable {
7+
8+
@Override
9+
void close();
10+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package com.cosium.web_native_messaging_host;
2+
3+
/**
4+
* @author Réda Housni Alaoui
5+
*/
6+
public interface CloseableStdoutLease extends StdoutLease, AutoCloseable {
7+
8+
@Override
9+
void close();
10+
}
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
package com.cosium.web_native_messaging_host;
2+
3+
import static java.util.Objects.requireNonNull;
4+
5+
import com.fasterxml.jackson.databind.ObjectMapper;
6+
import com.fasterxml.jackson.databind.node.ContainerNode;
7+
import java.io.IOException;
8+
import java.util.Optional;
9+
import java.util.concurrent.ExecutionException;
10+
import java.util.concurrent.ExecutorService;
11+
import java.util.concurrent.Executors;
12+
import java.util.concurrent.Future;
13+
import java.util.function.Supplier;
14+
15+
/**
16+
* @author Réda Housni Alaoui
17+
*/
18+
class DefaultChannel implements CloseableChannel {
19+
20+
private final Messages messages;
21+
private final Supplier<Runnable> shutdownHook;
22+
private final MessageHandler messageHandler;
23+
private final Logger logger;
24+
private final CloseableStdinLease stdinLease;
25+
private final CloseableStdoutLease stdoutLease;
26+
private final Future<?> stdinWatch;
27+
28+
private DefaultChannel(
29+
ObjectMapper objectMapper,
30+
Supplier<Runnable> shutdownHook,
31+
MessageHandler messageHandler,
32+
Logger logger,
33+
Stdin stdin,
34+
Stdout stdout) {
35+
36+
this.messages = new Messages(objectMapper);
37+
this.shutdownHook = requireNonNull(shutdownHook);
38+
this.messageHandler = requireNonNull(messageHandler);
39+
this.logger = requireNonNull(logger);
40+
this.stdinLease = stdin.startLease();
41+
this.stdoutLease = stdout.startLease();
42+
43+
ExecutorService executorService = Executors.newSingleThreadExecutor();
44+
stdinWatch = executorService.submit(this::watchStdin);
45+
executorService.shutdown();
46+
}
47+
48+
public static DefaultChannel open(
49+
ObjectMapper objectMapper,
50+
Supplier<Runnable> shutdownHook,
51+
MessageHandler messageHandler,
52+
Logger logger,
53+
Stdin stdin,
54+
Stdout stdout) {
55+
56+
return new DefaultChannel(objectMapper, shutdownHook, messageHandler, logger, stdin, stdout);
57+
}
58+
59+
@Override
60+
public void sendMessage(ContainerNode<?> message) {
61+
try {
62+
logger.debug("Sending message %s".formatted(message), null);
63+
messages.write(message, stdoutLease);
64+
} catch (IOException e) {
65+
throw new RuntimeException(e);
66+
}
67+
}
68+
69+
@Override
70+
public void waitForShutdown() {
71+
try {
72+
stdinWatch.get();
73+
} catch (InterruptedException e) {
74+
Thread.currentThread().interrupt();
75+
throw new RuntimeException(e);
76+
} catch (ExecutionException e) {
77+
throw new RuntimeException(e);
78+
}
79+
}
80+
81+
private void watchStdin() {
82+
while (!Thread.currentThread().isInterrupted()) {
83+
try {
84+
ContainerNode<?> message = messages.read(stdinLease);
85+
logger.debug("Received message %s".formatted(message), null);
86+
messageHandler.onMessage(this, message);
87+
} catch (IOException e) {
88+
logger.warn(e.getMessage(), e);
89+
}
90+
}
91+
}
92+
93+
@Override
94+
public void close() {
95+
Runnable shutdownHookRunnable = null;
96+
try {
97+
shutdownHookRunnable = shutdownHook.get();
98+
} catch (RuntimeException e) {
99+
logger.error(e.getMessage(), e);
100+
}
101+
102+
stdinWatch.cancel(true);
103+
stdinLease.close();
104+
stdoutLease.close();
105+
Optional.ofNullable(shutdownHookRunnable).ifPresent(Runnable::run);
106+
}
107+
}

0 commit comments

Comments
 (0)