razshare / sveltekit-sse

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

How to start connection on button press? #30

Closed unknownsoldier08 closed 7 months ago

unknownsoldier08 commented 7 months ago

It seems like by default, once a component is loaded - it will automatically connect to the sse source

How can I make it, so that it will only connect on demand (ie. on button click)

Is there any example?

Thank you

razshare commented 7 months ago

Hi @unknownsoldier08 ,

Try this

// src/routes/events/+server.js
import { events } from 'sveltekit-sse';

/**
 * @param {number} milliseconds
 * @returns
 */
function delay(milliseconds) {
  return new Promise(function run(resolve) {
    setTimeout(resolve, milliseconds);
  });
}

export function POST({ request }) {
  return events({
    request,
    async start({ emit }) {
      // eslint-disable-next-line no-constant-condition
      while (true) {
        const date = new Date();
        emit(
          'time',
          `Hello, the time is ${date.getHours()}:${date.getMinutes()}:${date.getSeconds()}.`
        );
        await delay(1000);
      }
    }
  });
}
<!-- src/routes/+page.svelte -->
<script>
  import { source } from 'sveltekit-sse';

  /**
   * @type {?import('svelte/store').Readable<string>}
   */
  let time;

  function start() {
    time = source('/events').select('time');
  }
</script>

{#if !time}
  <button on:click={start}>
    <span>What time is it?</span>
  </button>
{:else}
  {$time}
{/if}

When you call source(), the underlying connection is being cached based on the path you're passing in, in that example '/events'.

Subsequent calls to source() will simply return the existing connection.

If you want to add a disconnect button then you simply do this

<!-- src/routes/+page.svelte -->
<script>
  import { source } from 'sveltekit-sse';

  /**
   * @type {?import('svelte/store').Readable<string>}
   */
  let time;

  function start() {
    time = source('/events').select('time');
  }

  function stop() {
    source('/events').close();
    time = null;
  }
</script>

{#if !time}
  <button on:click={start}>
    <span>What time is it?</span>
  </button>
{:else}
  {$time}<br />
  <button on:click={stop}>
    <span>Okay, thanks.</span>
  </button>
{/if}

Note the stop() function.

The final result will look like this

Peek 2024-03-20 04-48

You can find the example repo here https://github.com/tncrazvan/sveltekit-sse-issue-30-example

razshare commented 7 months ago

@unknownsoldier08 let me know if this is what you're looking for.

unknownsoldier08 commented 7 months ago

I've tried your snippet and it seems to function as expected, but how should I satisfy typescript with it?

I have the following

<script lang="ts">
    import { source } from 'sveltekit-sse';
    import type { Readable } from 'svelte/store';
    import { Button } from '$lib/components/ui/button';

    let store: Readable<{ message: string } | null>

    function start() {
        if (store) return;
        store = source('/api/test').select('message').json<{ message: string }>();
    }

    function stop() {
        source('/api/test').close();
        store = null; // tserror: Svelte: Type null is not assignable to type Readable<{ message: string; } | null>`
    }
</script>

{#if store}
    <Button variant="outline" on:click={stop}>Stop</Button>
{:else}
    <Button variant="outline" on:click={start}>Start</Button>
{/if}

{$store?.message ?? 'No message'}

I change store to allow undefined, but not sure if that is right

<script lang="ts">
    import { source } from 'sveltekit-sse';
    import type { Readable } from 'svelte/store';
    import { Button } from '$lib/components/ui/button';

    let store: Readable<{ message: string } | null> | undefined;

    function start() {
        if (store) return;
        store = source('/api/test').select('message').json<{ message: string }>();
    }

    function stop() {
        source('/api/test').close();
        store = undefined; // typescript doesn't complain now 
    }
</script>

{#if store}
    <Button variant="outline" on:click={stop}>Stop</Button>
{:else}
    <Button variant="outline" on:click={start}>Start</Button>
{/if}

{$store?.message ?? 'No message'}
razshare commented 7 months ago

@unknownsoldier08 this is what you're looking for

<script lang="ts">
  import { source } from 'sveltekit-sse';
  import type { Readable } from 'svelte/store';

  let store: Readable<{ message: string } | null> | undefined;

  function start() {
    if (store) return;
    store = source('/api/test').select('message').json<{ message: string }>();
  }

  function stop() {
    source('/api/test').close();
    store = undefined;
  }
</script>

{#if null === store}
  <button on:click={stop}>Stop</button>
  {'No message'}
{:else}
  <button on:click={start}>Start</button>
  {$store?.message ?? 'No message'}
{/if}

You can see the return type of .json() in your editor image

the .json() method itself can return null.

unknownsoldier08 commented 7 months ago

Awesome, will go with this. Thanks for your help!