Hello,
I am trying to use SSE in a Vue SPA
For example with the
If we add the TypeScript ServerEventsClient
to a Pinia store
We just display a trace for all event handlers…
import { defineStore } from "pinia"
import {
ServerEventConnect,
ServerEventJoin,
ServerEventLeave,
ServerEventMessage,
ServerEventsClient,
ServerEventUpdate
} from "@servicestack/client"
export const useSseStore = defineStore('sse', () => {
const client = ref<ServerEventsClient|null>()
const startServerEventsClient = () => {
client.value = new ServerEventsClient("/", ["app"], {
handlers: {
onConnect: (sub:ServerEventConnect) => { // Successful SSE connection
console.log("SSE onConnect");
},
onJoin: (msg:ServerEventJoin) => { // User has joined subscribed channel
console.log("SSE onJoin");
},
onLeave: (msg:ServerEventLeave) => { // User has left subscribed channel
console.log("SSE onLeave");
},
onUpdate: (msg:ServerEventUpdate) => { // User channel subscription was changed
console.log("SSE onUpdate");
},
onMessage: (msg:ServerEventMessage) => {
console.log("SSE onMessage");
}
},
onException: (e:Error) => {
console.log("SSE onException");
},
onReconnect: (e:Error) => {
console.log("SSE onReconnect");
}
}).start();
}
return {
startServerEventsClient
}
});
I put simple code in a vue page, to just start the SSE client, nothing else
import { useSseStore } from "@/stores/sse"
const storeSse = useSseStore()
storeSse.startServerEventsClient()
Then Server-side, we add the plugin and just trace the events, nothing else
Plugins.Add(new ServerEventsFeature
{
HeartbeatInterval = TimeSpan.FromSeconds(30),
IdleTimeout = TimeSpan.FromSeconds(90),
NotifyChannelOfSubscriptions = true,
// Subscription pre-initialization callback
OnInit = req =>
{
var subscriptionId = req.QueryString["id"];
if(subscriptionId != null) MLog.Info($"OnInit: {subscriptionId}");
else MLog.Info($"OnInit: ");
},
OnConnect = (eventSubscription, _) =>
{
MLog.Info($"OnConnect: {eventSubscription.SubscriptionId}");
},
// Subscription is created
OnCreated = (eventSubscription, req) =>
{
MLog.Info($"OnCreated: {eventSubscription.SubscriptionId}");
},
OnUpdateAsync = eventSubscription =>
{
MLog.Info($"OnUpdateAsync: {eventSubscription.SubscriptionId}");
return Task.CompletedTask;
},
OnSubscribeAsync = eventSubscription =>
{
MLog.Info($"OnSubscribe: {eventSubscription.SubscriptionId}");
return Task.CompletedTask;
},
OnUnsubscribeAsync = eventSubscription =>
{
MLog.Info($"OnUnsubscribe: {eventSubscription.SubscriptionId}");
return Task.CompletedTask;
},
OnHeartbeatInit = req =>
{
var subscriptionId = req.QueryString["id"];
if(subscriptionId != null) MLog.Info($"OnHeartbeatInit: {subscriptionId}");
var subscription = req.TryResolve<IServerEvents>().GetSubscriptionInfo(subscriptionId);
if (subscription == null)
{
MLog.Info("... subscription no longer exists");
}
else
{
MLog.Info($"Found subscription for Username: {subscription.UserName}");
}
},
});
I am getting error on every heart beat called Client side
404 Subscription 'WB4Sa8sx9XM5SVqET1rT' does not exist
and the trace server-side is:
[info] OnInit:
[info] OnCreated: WB4Sa8sx9XM5SVqET1rT
[info] OnConnect: WB4Sa8sx9XM5SVqET1rT
[info] OnSubscribe: WB4Sa8sx9XM5SVqET1rT
[info] OnInit:
[info] OnCreated: nZDgBhAzOJNDHnxjmad2
[info] OnConnect: nZDgBhAzOJNDHnxjmad2
[info] OnSubscribe: nZDgBhAzOJNDHnxjmad2
[info] OnUnsubscribe: WB4Sa8sx9XM5SVqET1rT
[info] OnHeartbeatInit: WB4Sa8sx9XM5SVqET1rT
[info] ... subscription no longer exists
[info] OnInit:
[info] OnCreated: sj1ZsQwhj3CjcblVNHar
[info] OnConnect: sj1ZsQwhj3CjcblVNHar
[info] OnSubscribe: sj1ZsQwhj3CjcblVNHar
[info] OnHeartbeatInit: WB4Sa8sx9XM5SVqET1rT
[info] ... subscription no longer exists
See, we have an Unsubscribe…
OnUnsubscribe: WB4Sa8sx9XM5SVqET1rT
…and then new subscriptionId are generated, and the heartbeat is still trying to get the first subscriptionId
Do you have an idea?
Note that my code is doing nothing, I am just starting the app and tracing the events client and server side
My real project is Vue2.7 (with composition API and Pinia)… I can reproduce it from your Jamstack template with just the changes above and calling my backend directly
Thierry