From f85d1f9cebacd6fd1b595d4a2881f8de615e0d0a Mon Sep 17 00:00:00 2001 From: kwasniew Date: Mon, 2 Dec 2024 18:23:01 +0100 Subject: [PATCH] feat: streaming poc --- examples/variant.js | 9 +++++---- package.json | 2 ++ src/repository/index.ts | 24 ++++++++++++++++++++++-- src/unleash-config.ts | 1 + src/unleash.ts | 2 ++ yarn.lock | 10 ++++++++++ 6 files changed, 42 insertions(+), 6 deletions(-) diff --git a/examples/variant.js b/examples/variant.js index 4c3ea817..f4ee9e38 100644 --- a/examples/variant.js +++ b/examples/variant.js @@ -1,11 +1,12 @@ -const { initialize, getVariant } = require('../lib'); +const { initialize, isEnabled } = require('../lib'); const client = initialize({ appName: 'my-application', - url: 'https://unleash.herokuapp.com/api/', + url: 'http://localhost:4242/api/', customHeaders: { - Authorization: '3bd74da5b341d868443134377ba5d802ea1e6fa2d2a948276ade1f092bec8d92', + Authorization: '*:development.35a4fe08c112c8d98cc7a21bdf4d077796920c5e86b0f98eed467b23', }, + streaming: true, }); client.on('error', console.error); @@ -14,5 +15,5 @@ client.on('warn', console.log); console.log('Fetching toggles from: http://unleash.herokuapp.com'); setInterval(() => { - console.log('Variant config', getVariant('Test.variants', { userId: `${Math.random()}` })); + console.log('Enabled:', isEnabled('sadfsdaf', { userId: `${Math.random()}` })); }, 1000); diff --git a/package.json b/package.json index 537e64a1..8ac54dd5 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,7 @@ }, "homepage": "https://github.com/Unleash/unleash-client-node", "dependencies": { + "eventsource": "2.0.2", "http-proxy-agent": "^7.0.2", "https-proxy-agent": "^7.0.5", "ip-address": "^9.0.5", @@ -50,6 +51,7 @@ "@ava/typescript": "^4.0.0", "@tsconfig/node12": "^12.0.0", "@types/express": "^4.17.17", + "@types/eventsource": "^1.1.15", "@types/jsbn": "^1.2.33", "@types/make-fetch-happen": "^10.0.4", "@types/murmurhash3js": "^3.0.3", diff --git a/src/repository/index.ts b/src/repository/index.ts index ef3ca57d..b96d7f4e 100644 --- a/src/repository/index.ts +++ b/src/repository/index.ts @@ -2,7 +2,7 @@ import { EventEmitter } from 'events'; import { ClientFeaturesResponse, EnhancedFeatureInterface, FeatureInterface } from '../feature'; import { get } from '../request'; import { CustomHeaders, CustomHeadersFunction } from '../headers'; -import getUrl from '../url-utils'; +import getUrl, { resolveUrl } from '../url-utils'; import { HttpOptions } from '../http-options'; import { TagFilter } from '../tags'; import { BootstrapProvider } from './bootstrap-provider'; @@ -13,6 +13,7 @@ import { Segment, StrategyTransportInterface, } from '../strategy/strategy'; +const EventSource = require('eventsource'); export const SUPPORTED_SPEC_VERSION = '4.3.0'; @@ -39,6 +40,7 @@ export interface RepositoryOptions { bootstrapProvider: BootstrapProvider; bootstrapOverride?: boolean; storageProvider: StorageProvider; + streaming?: boolean; } interface FeatureToggleData { @@ -90,6 +92,10 @@ export default class Repository extends EventEmitter implements EventEmitter { private segments: Map; + private streaming: boolean = false; + + private eventSource: EventSource | undefined; + constructor({ url, appName, @@ -105,6 +111,7 @@ export default class Repository extends EventEmitter implements EventEmitter { bootstrapProvider, bootstrapOverride = true, storageProvider, + streaming = false, }: RepositoryOptions) { super(); this.url = url; @@ -122,10 +129,23 @@ export default class Repository extends EventEmitter implements EventEmitter { this.bootstrapOverride = bootstrapOverride; this.storageProvider = storageProvider; this.segments = new Map(); + this.streaming = streaming; + if (this.streaming) { + this.eventSource = new EventSource(resolveUrl(this.url, './client/streaming'), { + headers: this.headers, + } as any); + this.eventSource?.addEventListener('message', (event) => { + console.log('ES event', event); + this.fetch(); + }); + this.eventSource?.addEventListener('error', (error) => { + console.log('ES error', error); + }); + } } timedFetch(interval: number) { - if (interval > 0) { + if (interval > 0 && !this.streaming) { this.timer = setTimeout(() => this.fetch(), interval); if (process.env.NODE_ENV !== 'test' && typeof this.timer.unref === 'function') { this.timer.unref(); diff --git a/src/unleash-config.ts b/src/unleash-config.ts index ccba9a04..24b48a5a 100644 --- a/src/unleash-config.ts +++ b/src/unleash-config.ts @@ -32,4 +32,5 @@ export interface UnleashConfig { storageProvider?: StorageProvider; disableAutoStart?: boolean; skipInstanceCountWarning?: boolean; + streaming?: boolean; } diff --git a/src/unleash.ts b/src/unleash.ts index acaa1a91..d2b8fcd1 100644 --- a/src/unleash.ts +++ b/src/unleash.ts @@ -73,6 +73,7 @@ export class Unleash extends EventEmitter { storageProvider, disableAutoStart = false, skipInstanceCountWarning = false, + streaming = false, }: UnleashConfig) { super(); @@ -123,6 +124,7 @@ export class Unleash extends EventEmitter { tags, bootstrapProvider, bootstrapOverride, + streaming, storageProvider: storageProvider || new FileStorageProvider(backupPath), }); diff --git a/yarn.lock b/yarn.lock index fba4be7f..82e8d405 100644 --- a/yarn.lock +++ b/yarn.lock @@ -635,6 +635,11 @@ dependencies: "@types/node" "*" +"@types/eventsource@^1.1.15": + version "1.1.15" + resolved "https://registry.yarnpkg.com/@types/eventsource/-/eventsource-1.1.15.tgz#949383d3482e20557cbecbf3b038368d94b6be27" + integrity sha512-XQmGcbnxUNa06HR3VBVkc9+A2Vpi9ZyLJcdS5dwaQQ/4ZMWFO+5c90FnMUpbtMZwB/FChoYHwuVg8TvkECacTA== + "@types/express-serve-static-core@^4.17.33": version "4.17.41" resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.41.tgz#5077defa630c2e8d28aa9ffc2c01c157c305bef6" @@ -2178,6 +2183,11 @@ eventemitter3@^5.0.1: resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-5.0.1.tgz#53f5ffd0a492ac800721bb42c66b841de96423c4" integrity sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA== +eventsource@2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/eventsource/-/eventsource-2.0.2.tgz#76dfcc02930fb2ff339520b6d290da573a9e8508" + integrity sha512-IzUmBGPR3+oUG9dUeXynyNmf91/3zUSJg1lCktzKw47OXuhco54U3r9B7O4XX+Rb1Itm9OZ2b0RkTs10bICOxA== + execa@7.2.0, execa@^7.1.1: version "7.2.0" resolved "https://registry.yarnpkg.com/execa/-/execa-7.2.0.tgz#657e75ba984f42a70f38928cedc87d6f2d4fe4e9"