Hi,
Can anyone write me step-by-step hows Confleunce Data Center Events works? I want to do real-time chat powered by OpenAI.
This App using ReactJS frontend in iframe and Confluence Java API.
I am asked from ChatGPT/Copilot but these will give false infromation like use 3rd party APIs.
I have file and folder structure:
Code I am done:
package fi.i4ware.dark.listeners;
import com.atlassian.event.api.EventListener;
import com.atlassian.event.api.EventPublisher;
import com.atlassian.plugin.event.PluginEventManager;
import fi.i4ware.dark.events.MessageEvent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import javax.inject.Named;
@Named
public class MessageEventListener {
private static final Logger log = LoggerFactory.getLogger(MessageEventListener.class);
private final PluginEventManager pluginEventManager;
@Inject
public MessageEventListener(EventPublisher eventPublisher, PluginEventManager pluginEventManager) {
// Register this listener with the event publisher
eventPublisher.register(this);
this.pluginEventManager = pluginEventManager;
}
@EventListener
public void onMessageSent(MessageEvent event) {
log.info("New chat message from {}: {}", event.getUsername(), event.getMessage());
// Broadcast the event using PluginEventManager
pluginEventManager.broadcast(event);
// TODO: Implement WebSockets or UI updates for real-time messaging
}
}
Also I need information how to do ReactJS part.
These messages are event “listeners”, not meant to be an asynchronous communications protocol…
Web sockets would be nice, but I’m not sure if you even can (easily) provide this within Confluence.
Provide a REST API for server communication…
Yes but AI suggest 3rd party APIs for web sockets and these not work in Confluence 9 because Atlassian is prevented them to work. I had working real-time chat with Pusher https://pusher.com/ in Confluence 8 but now it not work because it use 3rd party API and I am no succeed to do my own API.
I made now WebSocket:
package fi.i4ware.dark.websocket;
import javax.websocket.*;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
@ServerEndpoint("/ai/message")
public class MessageWebSocket {
private static final Set<Session> sessions = new CopyOnWriteArraySet<>();
@OnOpen
public void onOpen(Session session) {
sessions.add(session);
}
@OnClose
public void onClose(Session session) {
sessions.remove(session);
}
@OnMessage
public void onMessage(String message, Session session) throws IOException {
// Broadcast message to all sessions
for (Session s : sessions) {
if (s.isOpen()) {
s.getBasicRemote().sendText(message);
}
}
}
@OnError
public void onError(Session session, Throwable throwable) {
// Handle error
}
// Utility method to broadcast from outside (e.g., from your event listener)
public static void broadcast(String message) throws IOException {
for (Session s : sessions) {
if (s.isOpen()) {
s.getBasicRemote().sendText(message);
}
}
}
}
And Event Listener:
package fi.i4ware.dark.listeners;
import com.atlassian.event.api.EventListener;
import com.atlassian.event.api.EventPublisher;
import com.atlassian.plugin.event.PluginEventManager;
import fi.i4ware.dark.events.MessageEvent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import javax.inject.Named;
import fi.i4ware.dark.websocket.MessageWebSocket;
@Named
public class MessageEventListener {
private static final Logger log = LoggerFactory.getLogger(MessageEventListener.class);
private final PluginEventManager pluginEventManager;
@Inject
public MessageEventListener(EventPublisher eventPublisher, PluginEventManager pluginEventManager) {
// Register this listener with the event publisher
eventPublisher.register(this);
this.pluginEventManager = pluginEventManager;
}
@EventListener
public void onMessageSent(MessageEvent event) {
log.info("New chat message from {}: {}", event.getUsername(), event.getMessage());
// Broadcast the event using PluginEventManager
pluginEventManager.broadcast(event);
// Broadcast to WebSocket clients
try {
MessageWebSocket.broadcast(event.getUsername() + ": " + event.getMessage());
} catch (Exception e) {
log.error("WebSocket broadcast failed", e);
}
}
}
And Event:
package fi.i4ware.dark.events;
import com.atlassian.confluence.event.events.ConfluenceEvent;
public class MessageEvent extends ConfluenceEvent {
private final String username;
private final String message;
private final String pageId;
public MessageEvent(Object src, String username, String message, String pageId) {
super(src);
this.username = username;
this.message = message;
this.pageId = pageId;
}
public String getUsername() {
return username;
}
public String getMessage() {
return message;
}
public String getPageId() {
return pageId;
}
}
But my App can not be enabled:
I think is cased by this:
https://developer.atlassian.com/server/confluence/get-your-apps-ready-for-gray-api-removal/
I got my App enabled with WebSocket by configuration in pom.xml below:
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
<version>4.2.1</version>
<extensions>true</extensions>
<configuration>
<instructions>
<Bundle-SymbolicName>fi.i4ware.dark</Bundle-SymbolicName>
<Import-Package>
org.springframework.osgi.*;resolution:=optional,
!com.atlassian.util.concurrent,
!com.google.common.base,
javax.websocket;version="1.1",
*
</Import-Package>
<Embed-Dependency>
atlassian-util-concurrent,
guava
</Embed-Dependency>
<Embed-Transitive>true</Embed-Transitive>
<Export-Package>
fi.i4ware.api,
fi.i4ware.dark,
javax.websocket;version="1.1",
*
</Export-Package>
<Private-Package>
</Private-Package>
<DynamicImport-Package>
*
</DynamicImport-Package>
<Bundle-StartLevel>20</Bundle-StartLevel>
</instructions>
</configuration>
</plugin>
And:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.6.0</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<minimizeJar>true</minimizeJar>
<artifactSet>
<includes>
<include>javax.websocket:javax.websocket-api</include>
</includes>
</artifactSet>
<relocations>
<!-- Optionally relocate to avoid conflicts -->
</relocations>
</configuration>
</execution>
</executions>
</plugin>
And:
<dependency>
<groupId>javax.websocket</groupId>
<artifactId>javax.websocket-api</artifactId>
<version>1.1</version>
<scope>compile</scope>
</dependency>
I got is now work but only with Pusher by programming my own API to use it because Atlassian has prevented to use 3rd party APIs for security reasons.
Also thats why WebSoucket can not be imported to App and external websocket is needed like do it with NodeJS. But this is too dificult to install by customers.
I personally annotate classes with @Component. And don’t execute .register() in the constructor, it should be a class implementing InitializingBean and you put it in the afterInitialization() method.