razshare / sveltekit-sse

Server Sent Events with SvelteKit
https://www.npmjs.com/package/sveltekit-sse
MIT License
301 stars 9 forks source link

Connecting/disconnecting stream in component lifecycle #41

Closed BearToCode closed 6 months ago

BearToCode commented 6 months ago

Hello again,

I'm trying to connect/disconnect to SSE inside onMount and onDestroy lifecycle callbacks, but it seems surprisingly hard to do. This is what I managed to do so far (demo here). I've adapted some code from #30.

// abstraction.js
import { onDestroy, onMount } from 'svelte';
import { writable } from 'svelte/store';
import { source } from 'sveltekit-sse';

export function sse(from, message) {
  const store = writable();

  function start() {
    store.set(source(from).select(message));
  }

  function stop() {
    source(from).close();
    store.set(null);
  }

  onMount(start);
  onDestroy(stop);

  return store;
}
// sse/+server.js
import { events } from 'sveltekit-sse';
import { get } from 'svelte/store';

function delay(milliseconds) {
  return new Promise(function run(resolve) {
    setTimeout(resolve, milliseconds);
  });
}

export function POST({ request }) {
  return events({
    request,
    async start({ emit, lock }) {
      while (get(lock)) { // <== If while(true) is used, the connection also stays on indefinitely 
        const date = new Date();
          console.log('Sending message: ' + date);
        emit(
          'message',
          `Hello, the time is ${date.getHours()}:${date.getMinutes()}:${date.getSeconds()}.`
        );
        await delay(1000);
      }
    }
  });
}
<!-- +Component.svelte -->
<script>
  import { sse } from './abstraction';

  let time;
  sse('/sse', 'message').subscribe(stream => {
      stream?.subscribe(message => {
          time = message;
      });
  })
</script>

{time}
<!-- +page.svelte -->
<script>
    import Component from './Component.svelte';

    let show = true;
</script>

<button on:click={() => { show = !show }}>
    Toggle
</button>

{#if show}
    <Component />
{/if}

The stream does not reconnect after closing the first time. Also, I noticed that using while(true) in the server side events, the connection stays open indefinitely, so I used while(get(lock)) instead.

razshare commented 6 months ago

Hello @BearToCode , First of all thank you for the issue and buona festa del lavoratore.

This is indeed a bug, please update to version 0.12.2 to get the fix, then your example will start working.

I would also add that what you're trying to do manually with abstraction.js is already done automatically by the library.

All your component code can be reduced to this

<!-- +Component.svelte -->
<script>
  import { source } from 'sveltekit-sse';
  const time = source('/sse').select('message');
</script>

{$time}

By default when all subscribers have unsubscribed the connection closes automatically, it leverages svelte's primitive store behavior (which can detect just that: the moment when the store has no more subscribers).\ This also takes into account subscribers across multiple components, so you don't run the risk of closing a connection that some other component is still using.

Here's a repository showcasing your example, but reduced to just using svelte store primitives https://github.com/tncrazvan/sveltekit-sse-issue-41

Let me know if this is addresses your issue.

BearToCode commented 6 months ago

After updating to latest version the following is working as I wanted to:

<script>
  // ...
  const stream = source('/sse');

  stream.select('message').subscribe(console.log);

  onDestroy(() => {
    stream.close();
  });
</script>

I still have to use the while(get(lock)) server-side.

Thanks a lot for the prompt and detailed response, and for the effort in quickly fixing the library.