-
Notifications
You must be signed in to change notification settings - Fork 74
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: streaming poc #679
base: main
Are you sure you want to change the base?
feat: streaming poc #679
Conversation
examples/variant.js
Outdated
}, | ||
streaming: true, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
opt into streaming
src/repository/index.ts
Outdated
@@ -13,6 +13,7 @@ import { | |||
Segment, | |||
StrategyTransportInterface, | |||
} from '../strategy/strategy'; | |||
const EventSource = require('eventsource'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
couldn't get it to work with import, only require worked in a quick PoC
src/repository/index.ts
Outdated
this.eventSource = new EventSource(resolveUrl(this.url, './client/streaming'), { | ||
headers: this.headers, | ||
} as any); | ||
this.eventSource?.addEventListener('message', (event) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
required EventSource is seen as potentially undefined. Not sure why that's what ? is handling here
src/repository/index.ts
Outdated
} | ||
|
||
timedFetch(interval: number) { | ||
if (interval > 0) { | ||
if (interval > 0 && !this.streaming) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this line is all it takes to stop intervals/timeouts and use ES client instead
customHeaders: { | ||
Authorization: '943ca9171e2c884c545c5d82417a655fb77cec970cc3b78a8ff87f4406b495d0', | ||
}, | ||
experimentalMode: { type: 'streaming' }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this has to be provided explicitly to opt into streaming. The reason I model it as {type: 'polling'} | {type: 'streaming'} union is to add streaming config option later {type: 'streaming', config: {refresh, backoff}}
experimentalMode: { type: 'streaming' }, | ||
skipInstanceCountWarning: true, | ||
}); | ||
client.on('changed', () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
changes are reflected instantly in the event emitter
@@ -13,6 +13,8 @@ import { | |||
Segment, | |||
StrategyTransportInterface, | |||
} from '../strategy/strategy'; | |||
// @ts-expect-error |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this library does not provide any types so we have to do this for now until we provide a type ourselves
@@ -105,6 +110,7 @@ export default class Repository extends EventEmitter implements EventEmitter { | |||
bootstrapProvider, | |||
bootstrapOverride = true, | |||
storageProvider, | |||
eventSource, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we make EventSource injectable so that it's easy to swap in testing for a simple mock
this.emit(UnleashEvents.Error, err); | ||
} | ||
}); | ||
this.eventSource.addEventListener('error', (error: unknown) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
for now I decided to translate event source errors to warnings in Unleash event API
} | ||
|
||
timedFetch(interval: number) { | ||
if (interval > 0) { | ||
if (interval > 0 && !this.eventSource) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
do intervals only when eventSource is not provided
@@ -398,6 +424,9 @@ Message: ${err.message}`, | |||
clearTimeout(this.timer); | |||
} | |||
this.removeAllListeners(); | |||
if (this.eventSource) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we have to remember about closing the event source when Unleash is closed
@@ -123,6 +126,17 @@ export class Unleash extends EventEmitter { | |||
tags, | |||
bootstrapProvider, | |||
bootstrapOverride, | |||
eventSource: | |||
experimentalMode?.type === 'streaming' | |||
? new EventSource(resolveUrl(unleashUrl, './client/streaming'), { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we can expose the low level config options to the public API over time, but for now I set those defaults internally
], | ||
}; | ||
const storageProvider: StorageProvider<ClientFeaturesResponse> = new InMemStorageProvider(); | ||
const eventSource = { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
in-memory mock is trivial to implement. It's just a thin layer on top of the EventEmitter to adjust the API semantic to SSE API
About the changes
This is a reference implementation for the streaming client using SSE (Server-Sent Events).
The changes include:
Chosen library:
because it provides extra options such as backoff with jitter, read timeout for inactive streams. Both libraries also support HTTP headers that are not part of the SSE spec itself but useful in practice to do auth
Main logic:
More comments with the important decisions inline
Important files
Discussion points