From 77fc20cfff2a8d6995cb4a836dde281ec960bb77 Mon Sep 17 00:00:00 2001 From: WebStreamr <210764791+webstreamr@users.noreply.github.com> Date: Wed, 4 Jun 2025 10:29:50 +0000 Subject: [PATCH] refactor: extract env access into helpers --- src/controller/StreamController.ts | 4 ++-- src/index.ts | 6 +++--- src/landingTemplate.ts | 3 ++- src/utils/StreamResolver.ts | 5 +++-- src/utils/__mocks__/Fetcher.ts | 3 ++- src/utils/env.test.ts | 32 ++++++++++++++++++++++++++++++ src/utils/env.ts | 7 +++++++ src/utils/index.ts | 1 + src/utils/manifest.ts | 5 +++-- src/utils/tmdb.ts | 3 ++- 10 files changed, 57 insertions(+), 12 deletions(-) create mode 100644 src/utils/env.test.ts create mode 100644 src/utils/env.ts diff --git a/src/controller/StreamController.ts b/src/controller/StreamController.ts index 84bc41b..f27883b 100644 --- a/src/controller/StreamController.ts +++ b/src/controller/StreamController.ts @@ -2,7 +2,7 @@ import { Request, Response, Router } from 'express'; import winston from 'winston'; import { Handler } from '../handler'; import { Config, Context } from '../types'; -import { StreamResolver } from '../utils'; +import { envIsProd, StreamResolver } from '../utils'; export class StreamController { public readonly router: Router; @@ -38,7 +38,7 @@ export class StreamController { const { streams, ttl } = await this.streamResolver.resolve(ctx, handlers, type, id); - if (ttl && process.env['NODE_ENV'] === 'production') { + if (ttl && envIsProd()) { res.setHeader('Cache-Control', `max-age=${ttl / 1000}, public`); } diff --git a/src/index.ts b/src/index.ts index 019dd0a..0a094de 100644 --- a/src/index.ts +++ b/src/index.ts @@ -13,7 +13,7 @@ import { } from './handler'; import { ExtractorRegistry } from './extractor'; import { ConfigureController, ManifestController, StreamController } from './controller'; -import { Fetcher, StreamResolver } from './utils'; +import { envGet, envIsProd, Fetcher, StreamResolver } from './utils'; const logger = winston.createLogger({ transports: [ @@ -54,7 +54,7 @@ addon.use((_req: Request, res: Response, next: NextFunction) => { res.setHeader('Access-Control-Allow-Origin', '*'); res.setHeader('Access-Control-Allow-Headers', '*'); - if (process.env['NODE_ENV'] === 'production') { + if (envIsProd()) { res.setHeader('Cache-Control', 'max-age=10, public'); } @@ -69,7 +69,7 @@ addon.get('/', (_req: Request, res: Response) => { res.redirect('/configure'); }); -const port = parseInt(process.env['PORT'] || '51546'); +const port = parseInt(envGet('PORT') || '51546'); addon.listen(port, () => { logger.info(`Add-on Repository URL: http://127.0.0.1:${port}/manifest.json`); }); diff --git a/src/landingTemplate.ts b/src/landingTemplate.ts index c18f69b..a119981 100644 --- a/src/landingTemplate.ts +++ b/src/landingTemplate.ts @@ -1,5 +1,6 @@ // Adapted version of https://github.com/Stremio/stremio-addon-sdk/blob/v1.6.2/src/landingTemplate.js import { ManifestWithConfig } from './types'; +import { envGet } from './utils'; const STYLESHEET = ` * { @@ -274,7 +275,7 @@ export function landingTemplate(manifest: ManifestWithConfig) {
- ${process.env['CONFIGURATION_DESCRIPTION'] || ''} + ${envGet('CONFIGURATION_DESCRIPTION') || ''}
diff --git a/src/utils/StreamResolver.ts b/src/utils/StreamResolver.ts index e0d563d..9694933 100644 --- a/src/utils/StreamResolver.ts +++ b/src/utils/StreamResolver.ts @@ -6,6 +6,7 @@ import { Context, TIMEOUT, UrlResult } from '../types'; import { Handler } from '../handler'; import { BlockedError, NotFoundError, QueueIsFullError } from '../error'; import { languageFromCountryCode } from './languageFromCountryCode'; +import { envGetAppName } from './env'; interface ResolveResponse { streams: Stream[]; @@ -54,7 +55,7 @@ export class StreamResolver { handlerErrorOccurred = true; streams.push({ - name: process.env['MANIFEST_NAME'] || 'WebStreamr', + name: envGetAppName(), title: [`🔗 ${handler.label}`, this.logErrorAndReturnNiceString(ctx, handler.id, error)].join('\n'), ytId: 'E4WlUXrJgy4', }); @@ -130,7 +131,7 @@ export class StreamResolver { }; private readonly buildName = (ctx: Context, urlResult: UrlResult): string => { - let name = process.env['MANIFEST_NAME'] || 'WebStreamr'; + let name = envGetAppName(); name += urlResult.meta.height ? ` ${urlResult.meta.height}P` : ' N/A'; diff --git a/src/utils/__mocks__/Fetcher.ts b/src/utils/__mocks__/Fetcher.ts index 402b043..3fab513 100644 --- a/src/utils/__mocks__/Fetcher.ts +++ b/src/utils/__mocks__/Fetcher.ts @@ -2,6 +2,7 @@ import fs from 'node:fs'; import slugify from 'slugify'; import winston from 'winston'; import { Context } from '../../types'; +import { envGet } from '../env'; const { Fetcher } = jest.requireActual('../Fetcher'); class MockedFetcher { @@ -39,7 +40,7 @@ class MockedFetcher { } else { let response; try { - if (process.env['TEST_UPDATE_FIXTURES']) { + if (envGet('TEST_UPDATE_FIXTURES')) { response = await fetch(url, this.fetcher.getInit(ctx, url, init)); } else { console.error(`No fixture found at "${path}".`); diff --git a/src/utils/env.test.ts b/src/utils/env.test.ts new file mode 100644 index 0000000..4d467fb --- /dev/null +++ b/src/utils/env.test.ts @@ -0,0 +1,32 @@ +import { envGet, envGetAppId, envGetAppName, envIsProd } from './env'; + +describe('env', () => { + test('envGet', () => { + expect(envGet('NODE_ENV')).toBe('test'); + }); + + test('envGetAppId', () => { + expect(envGetAppId()).toBe('webstreamr'); + + process.env['MANIFEST_ID'] = 'webstreamr.dev'; + expect(envGetAppId()).toBe('webstreamr.dev'); + delete process.env['MANIFEST_ID']; + }); + + test('envGetAppName', () => { + expect(envGetAppName()).toBe('WebStreamr'); + + process.env['MANIFEST_NAME'] = 'WebStreamr | dev'; + expect(envGetAppName()).toBe('WebStreamr | dev'); + delete process.env['MANIFEST_NAME']; + }); + + test('envIsProd', () => { + expect(envIsProd()).toBeFalsy(); + + const previousNodeEnv = process.env['NODE_ENV']; + process.env['NODE_ENV'] = 'production'; + expect(envIsProd()).toBeTruthy(); + process.env['NODE_ENV'] = previousNodeEnv; + }); +}); diff --git a/src/utils/env.ts b/src/utils/env.ts new file mode 100644 index 0000000..3a43855 --- /dev/null +++ b/src/utils/env.ts @@ -0,0 +1,7 @@ +export const envGet = (name: string): string | undefined => process.env[name]; + +export const envGetAppId = (): string => process.env['MANIFEST_ID'] || 'webstreamr'; + +export const envGetAppName = (): string => process.env['MANIFEST_NAME'] || 'WebStreamr'; + +export const envIsProd = (): boolean => process.env['NODE_ENV'] === 'production'; diff --git a/src/utils/index.ts b/src/utils/index.ts index 4785172..0eec46f 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -1,3 +1,4 @@ +export * from './env'; export * from './Fetcher'; export * from './StreamResolver'; export * from './embed'; diff --git a/src/utils/manifest.ts b/src/utils/manifest.ts index 4d389b7..836fbe4 100644 --- a/src/utils/manifest.ts +++ b/src/utils/manifest.ts @@ -1,15 +1,16 @@ import { flag } from 'country-emoji'; import { Handler } from '../handler'; import { Config, CountryCode, ManifestWithConfig } from '../types'; +import { envGetAppId, envGetAppName } from './env'; import { languageFromCountryCode } from './languageFromCountryCode'; const typedEntries = (obj: T): [keyof T, T[keyof T]][] => (Object.entries(obj) as [keyof T, T[keyof T]][]); export const buildManifest = (handlers: Handler[], config: Config): ManifestWithConfig => { const manifest: ManifestWithConfig = { - id: process.env['MANIFEST_ID'] || 'webstreamr', + id: envGetAppId(), version: '0.22.9', // x-release-please-version - name: process.env['MANIFEST_NAME'] || 'WebStreamr', + name: envGetAppName(), description: 'Provides HTTP URLs from streaming websites.', resources: [ 'stream', diff --git a/src/utils/tmdb.ts b/src/utils/tmdb.ts index 44399b1..e6aa518 100644 --- a/src/utils/tmdb.ts +++ b/src/utils/tmdb.ts @@ -1,12 +1,13 @@ import { ImdbId } from './imdb'; import { Context } from '../types'; import { Fetcher } from './Fetcher'; +import { envGet } from './env'; export interface TmdbId { id: number; series: number | undefined; episode: number | undefined } export const getTmdbIdFromImdbId = async (ctx: Context, fetcher: Fetcher, imdbId: ImdbId): Promise => { const url = new URL(`https://api.themoviedb.org/3/find/${imdbId.id}?external_source=imdb_id`); - const config = { 'headers': { Authorization: 'Bearer ' + process.env['TMDB_ACCESS_TOKEN'] }, 'Content-Type': 'application/json' }; + const config = { 'headers': { Authorization: 'Bearer ' + envGet('TMDB_ACCESS_TOKEN') }, 'Content-Type': 'application/json' }; const response = JSON.parse(await fetcher.text(ctx, url, config)); const id = (imdbId.series ? response.tv_results[0] : response.movie_results[0])?.id;