From b12aef225acad2d60b2dc84f378006aca6055d19 Mon Sep 17 00:00:00 2001 From: Alexander Zinn Date: Wed, 20 Aug 2025 13:20:59 -0400 Subject: [PATCH] feat: add ESLint configuration, update package.json scripts, and create example files for Bun integration --- eslint.config.ts | 9 ++ .../use-bun-instead-of-node-vite-npm-pnpm.mdc | 111 ++++++++++++++++++ examples/.gitignore | 34 ++++++ examples/.npmrc | 4 + examples/.prettierrc | 12 ++ examples/README.md | 15 +++ examples/bun.lock | 29 +++++ examples/bunfig.toml | 5 + examples/example-1.js | 11 -- examples/package.json | 16 +++ examples/src/index.ts | 37 ++++++ examples/tsconfig.json | 29 +++++ package.json | 13 +- src/PCastApi.ts | 24 ++-- src/index.ts | 6 +- src/net/http/HttpRequests.ts | 5 + src/pcast/Channels.ts | 46 ++++---- src/pcast/IResponse.ts | 20 +--- src/pcast/PCastRequests.ts | 7 +- src/pcast/ReportKind.ts | 34 ++++++ src/pcast/Reporting.ts | 83 +++---------- src/pcast/Stream.ts | 2 +- src/pcast/ViewingReportKind.ts | 37 ++++++ src/pcast/index.ts | 3 +- 24 files changed, 464 insertions(+), 128 deletions(-) create mode 100644 eslint.config.ts create mode 100644 examples/.cursor/rules/use-bun-instead-of-node-vite-npm-pnpm.mdc create mode 100644 examples/.gitignore create mode 100644 examples/.npmrc create mode 100644 examples/.prettierrc create mode 100644 examples/README.md create mode 100644 examples/bun.lock create mode 100644 examples/bunfig.toml delete mode 100644 examples/example-1.js create mode 100644 examples/package.json create mode 100644 examples/src/index.ts create mode 100644 examples/tsconfig.json create mode 100644 src/pcast/ReportKind.ts create mode 100644 src/pcast/ViewingReportKind.ts diff --git a/eslint.config.ts b/eslint.config.ts new file mode 100644 index 0000000..54d89d6 --- /dev/null +++ b/eslint.config.ts @@ -0,0 +1,9 @@ +import js from "@eslint/js"; +import globals from "globals"; +import tseslint from "typescript-eslint"; +import { defineConfig } from "eslint/config"; + +export default defineConfig([ + { files: ["**/*.{js,mjs,cjs,ts,mts,cts}"], plugins: { js }, extends: ["js/recommended"], languageOptions: { globals: {...globals.browser, ...globals.node} } }, + tseslint.configs.recommended, +]); diff --git a/examples/.cursor/rules/use-bun-instead-of-node-vite-npm-pnpm.mdc b/examples/.cursor/rules/use-bun-instead-of-node-vite-npm-pnpm.mdc new file mode 100644 index 0000000..b8100b7 --- /dev/null +++ b/examples/.cursor/rules/use-bun-instead-of-node-vite-npm-pnpm.mdc @@ -0,0 +1,111 @@ +--- +description: Use Bun instead of Node.js, npm, pnpm, or vite. +globs: "*.ts, *.tsx, *.html, *.css, *.js, *.jsx, package.json" +alwaysApply: false +--- + +Default to using Bun instead of Node.js. + +- Use `bun ` instead of `node ` or `ts-node ` +- Use `bun test` instead of `jest` or `vitest` +- Use `bun build ` instead of `webpack` or `esbuild` +- Use `bun install` instead of `npm install` or `yarn install` or `pnpm install` +- Use `bun run + + +``` + +With the following `frontend.tsx`: + +```tsx#frontend.tsx +import React from "react"; + +// import .css files directly and it works +import './index.css'; + +import { createRoot } from "react-dom/client"; + +const root = createRoot(document.body); + +export default function Frontend() { + return

Hello, world!

; +} + +root.render(); +``` + +Then, run index.ts + +```sh +bun --hot ./index.ts +``` + +For more information, read the Bun API docs in `node_modules/bun-types/docs/**.md`. diff --git a/examples/.gitignore b/examples/.gitignore new file mode 100644 index 0000000..a14702c --- /dev/null +++ b/examples/.gitignore @@ -0,0 +1,34 @@ +# dependencies (bun install) +node_modules + +# output +out +dist +*.tgz + +# code coverage +coverage +*.lcov + +# logs +logs +_.log +report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json + +# dotenv environment variable files +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# caches +.eslintcache +.cache +*.tsbuildinfo + +# IntelliJ based IDEs +.idea + +# Finder (MacOS) folder config +.DS_Store diff --git a/examples/.npmrc b/examples/.npmrc new file mode 100644 index 0000000..a494ac2 --- /dev/null +++ b/examples/.npmrc @@ -0,0 +1,4 @@ +//registry-node.techniker.me/:_authToken="${NODE_REGISTRY_AUTH_TOKEN}" +@techniker-me:registry=https://registry-node.techniker.me +save-exact=true +package-lock=false \ No newline at end of file diff --git a/examples/.prettierrc b/examples/.prettierrc new file mode 100644 index 0000000..caa814d --- /dev/null +++ b/examples/.prettierrc @@ -0,0 +1,12 @@ +{ + "arrowParens": "avoid", + "bracketSameLine": true, + "bracketSpacing": false, + "printWidth": 160, + "semi": true, + "singleAttributePerLine": false, + "singleQuote": true, + "tabWidth": 2, + "trailingComma": "none", + "useTabs": false +} diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 0000000..9c4adf7 --- /dev/null +++ b/examples/README.md @@ -0,0 +1,15 @@ +# examples + +To install dependencies: + +```bash +bun install +``` + +To run: + +```bash +bun run index.ts +``` + +This project was created using `bun init` in bun v1.2.20. [Bun](https://bun.com) is a fast all-in-one JavaScript runtime. diff --git a/examples/bun.lock b/examples/bun.lock new file mode 100644 index 0000000..bbc706d --- /dev/null +++ b/examples/bun.lock @@ -0,0 +1,29 @@ +{ + "lockfileVersion": 1, + "workspaces": { + "": { + "name": "examples", + "devDependencies": { + "@types/bun": "latest", + }, + "peerDependencies": { + "typescript": "^5", + }, + }, + }, + "packages": { + "@types/bun": ["@types/bun@1.2.20", "", { "dependencies": { "bun-types": "1.2.20" } }, "sha512-dX3RGzQ8+KgmMw7CsW4xT5ITBSCrSbfHc36SNT31EOUg/LA9JWq0VDdEXDRSe1InVWpd2yLUM1FUF/kEOyTzYA=="], + + "@types/node": ["@types/node@24.3.0", "", { "dependencies": { "undici-types": "~7.10.0" } }, "sha512-aPTXCrfwnDLj4VvXrm+UUCQjNEvJgNA8s5F1cvwQU+3KNltTOkBm1j30uNLyqqPNe7gE3KFzImYoZEfLhp4Yow=="], + + "@types/react": ["@types/react@19.1.10", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-EhBeSYX0Y6ye8pNebpKrwFJq7BoQ8J5SO6NlvNwwHjSj6adXJViPQrKlsyPw7hLBLvckEMO1yxeGdR82YBBlDg=="], + + "bun-types": ["bun-types@1.2.20", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-pxTnQYOrKvdOwyiyd/7sMt9yFOenN004Y6O4lCcCUoKVej48FS5cvTw9geRaEcB9TsDZaJKAxPTVvi8tFsVuXA=="], + + "csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="], + + "typescript": ["typescript@5.9.2", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A=="], + + "undici-types": ["undici-types@7.10.0", "", {}, "sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag=="], + } +} diff --git a/examples/bunfig.toml b/examples/bunfig.toml new file mode 100644 index 0000000..ae22d90 --- /dev/null +++ b/examples/bunfig.toml @@ -0,0 +1,5 @@ +[install.lockfile] +save = false + +[install.scopes] +"@techniker-me" = "https://registry-node.techniker.me" diff --git a/examples/example-1.js b/examples/example-1.js deleted file mode 100644 index e8009df..0000000 --- a/examples/example-1.js +++ /dev/null @@ -1,11 +0,0 @@ -import {PCastApi} from '../dist/node/index.js'; - -const applicationCredentials = { - id: 'phenixrts.com-alex.zinn', - secret: 'AMAsDzr.dIuGMZ.Zu52Dt~MQvP!DZwYg' -}; - -const pcastUri = 'https://pcast-stg.phenixrts.com'; -const pcastApi = new PCastApi(pcastUri, applicationCredentials); - -pcastApi.channels.list().then(console.log); diff --git a/examples/package.json b/examples/package.json new file mode 100644 index 0000000..9130c79 --- /dev/null +++ b/examples/package.json @@ -0,0 +1,16 @@ +{ + "name": "examples", + "module": "index.ts", + "type": "module", + "private": true, + "devDependencies": { + "@types/bun": "latest" + }, + "peerDependencies": { + "typescript": "^5" + }, + "dependencies": { + "@techniker-me/csv": "0.0.1", + "moment": "2.30.1" + } +} diff --git a/examples/src/index.ts b/examples/src/index.ts new file mode 100644 index 0000000..af48a02 --- /dev/null +++ b/examples/src/index.ts @@ -0,0 +1,37 @@ +import PCastApi from "../../src/index"; + +const pacstUri = "https://pcast-stg.phenixrts.com" +const application = { + id: "phenixrts.com-alex.zinn", + secret: "AMAsDzr.dIuGMZ.Zu52Dt~MQvP!DZwYg" +} +const pcastApi = new PCastApi(pacstUri, application); + +const channelsList = await pcastApi.channels.list(); + +// const start = hrtime.bigint(); +// const publishingReportCsv = await pcastApi.reporting.generateReport(ReportKind.Publishing, { +// start: moment().subtract(1, 'day').toISOString(), +// end: moment().toISOString(), +// applicationIds: [application.id] +// }); +// const endPublishing = hrtime.bigint(); +// console.log(publishingReportCsv); +// console.log(`Time taken: ${Number(endPublishing - start) / 1_000_000_000} seconds`); + + +// const viewingReportCsv = await pcastApi.reporting.generateReport(ReportKind.Viewing, { +// kind: ViewingReportKind.HLS, +// start: moment().subtract(1, 'day').toISOString(), +// end: moment().toISOString(), +// applicationIds: [application.id] +// }); +// const endViewing = hrtime.bigint(); +// console.log(`Time taken: ${Number(endViewing - endPublishing) / 1_000_000_000} seconds`); +// // const viewingReport = await CsvParser.parse(viewingReportCsv); + +// console.log(viewingReportCsv); + +const channelMembers = await pcastApi.channels.getMembers(channelsList[1].channelId); +console.log('[%o]', channelMembers); +console.log('[%o]', channelMembers.streams); \ No newline at end of file diff --git a/examples/tsconfig.json b/examples/tsconfig.json new file mode 100644 index 0000000..bfa0fea --- /dev/null +++ b/examples/tsconfig.json @@ -0,0 +1,29 @@ +{ + "compilerOptions": { + // Environment setup & latest features + "lib": ["ESNext"], + "target": "ESNext", + "module": "Preserve", + "moduleDetection": "force", + "jsx": "react-jsx", + "allowJs": true, + + // Bundler mode + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "noEmit": true, + + // Best practices + "strict": true, + "skipLibCheck": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedIndexedAccess": true, + "noImplicitOverride": true, + + // Some stricter flags (disabled by default) + "noUnusedLocals": false, + "noUnusedParameters": false, + "noPropertyAccessFromIndexSignature": false + } +} diff --git a/package.json b/package.json index 32861a9..6979cb3 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,9 @@ "test:watch": "bun test --watch", "test:coverage": "bun test --coverage", "prebuild": "bun run clean", + "prelint": "bun install", + "lint": "eslint src/**/*.ts", + "lint:fix": "eslint src/**/*.ts --fix", "build": "bun run build:node && bun run build:browser && bun run build:types", "build:node": "bun build src/index.ts --outdir dist/node --target node --format esm --production", "build:browser": "bun build src/index.ts --outdir dist/browser --target browser --format esm --production", @@ -19,16 +22,22 @@ "build:types:dev": "tsc --emitDeclarationOnly --outDir dist/types", "prebuild:dev": "bun run clean", "build:dev": "bun run build:node:dev && bun run build:browser:dev && bun run build:types:dev", + "postclean": "bun run lint", "clean": "rm -rf dist" }, "devDependencies": { + "@eslint/js": "9.33.0", "@types/bun": "latest", "@types/node": "24.3.0", "chai": "5.3.1", "chai-as-promised": "8.0.1", + "eslint": "9.33.0", + "globals": "16.3.0", + "jiti": "2.5.1", "mocha": "11.7.1", "prettier": "3.6.2", - "typescript": "5.9.2" + "typescript": "5.9.2", + "typescript-eslint": "8.40.0" }, "dependencies": { "phenix-edge-auth": "1.2.7" @@ -50,4 +59,4 @@ "files": [ "dist" ] -} \ No newline at end of file +} diff --git a/src/PCastApi.ts b/src/PCastApi.ts index 944dd31..13bc57b 100644 --- a/src/PCastApi.ts +++ b/src/PCastApi.ts @@ -1,19 +1,23 @@ -import {Channels, Streams, type ApplicationCredentials, PCastHttpRequests} from './pcast'; +import {Channels, Streams, type ApplicationCredentials, PCastHttpRequests, Reporting} from './pcast'; export class PCastApi { private readonly _pcastUri: string; private readonly _applicationCredentials: ApplicationCredentials; private readonly _pcastHttpRequests: PCastHttpRequests; + private readonly _channels: Channels; + private readonly _streams: Streams; + private readonly _reporting: Reporting; constructor(pcastUri: string, applicationCredentials: ApplicationCredentials) { - if (pcastUri.split('/').at(-1) !== 'pcast') { - this._pcastUri = `${pcastUri}/pcast`; - } else { - this._pcastUri = pcastUri; - } + // in `src/PCastApi.ts` constructor + const normalized = pcastUri.replace(/\/+$/, ''); + this._pcastUri = normalized.endsWith('/pcast') ? normalized : `${normalized}/pcast`; this._applicationCredentials = applicationCredentials; this._pcastHttpRequests = new PCastHttpRequests(this._pcastUri, this._applicationCredentials); + this._channels = new Channels(this._pcastHttpRequests); + this._streams = new Streams(this._pcastHttpRequests); + this._reporting = new Reporting(this._pcastHttpRequests); } get pcastUri(): string { @@ -25,10 +29,14 @@ export class PCastApi { } get channels(): Channels { - return new Channels(this._pcastHttpRequests); + return this._channels; } get streams(): Streams { - return new Streams(this._pcastHttpRequests); + return this._streams; + } + + get reporting(): Reporting { + return this._reporting; } } diff --git a/src/index.ts b/src/index.ts index 9660d65..bf2fa86 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,7 @@ import {PCastApi} from './PCastApi'; +import {ReportKind} from './pcast/ReportKind'; +import {ViewingReportKind} from './pcast/ViewingReportKind'; -export * from './pcast'; +export type {ReportKind, ViewingReportKind}; export {PCastApi}; -export default {PCastApi}; +export default PCastApi; diff --git a/src/net/http/HttpRequests.ts b/src/net/http/HttpRequests.ts index f7623f0..602a91f 100644 --- a/src/net/http/HttpRequests.ts +++ b/src/net/http/HttpRequests.ts @@ -38,6 +38,10 @@ export class HttpRequests { if (httpMethodsThatMustNotHaveBody.includes(method)) { requestOptions.body = undefined; + } else { + if (requestOptions.body && typeof requestOptions.body !== 'string') { + requestOptions.body = JSON.stringify(requestOptions.body); + } } return this.makeRequest(path, requestOptions, abortController, this._requestTimeoutDuration); @@ -48,6 +52,7 @@ export class HttpRequests { try { const requestPath = `${this._baseUri}${path}`; + const response = await fetch(requestPath, options); if (!response.ok) { diff --git a/src/pcast/Channels.ts b/src/pcast/Channels.ts index 83d334d..12e81c3 100644 --- a/src/pcast/Channels.ts +++ b/src/pcast/Channels.ts @@ -14,6 +14,22 @@ export type Channel = { lastUpdated: string; channelId: string; }; +export type ChannelAlias = string; +export type Member = { + sessionId: string; + screenName: string; + role: string; + streams: [ + { + type: string; + uri: string; + audioState: string; + videoState: string; + } + ], + state: string; + lastUpdate: number; +} type GetChannelParams = { alias?: string; @@ -32,7 +48,7 @@ export class ChannelError extends Error { export class Channels { private readonly _httpRequests: PCastHttpRequests; - private readonly _channelsByAlias: Map = new Map(); + private readonly _channelsByAlias: Map = new Map(); private _initialized = false; constructor(pcastHttpRequests: PCastHttpRequests) { @@ -48,14 +64,11 @@ export class Channels { return; } - console.log('[Channels] [initialize] Populating local cache with [%o] channels', channelsList.length); - - for (let channel of channelsList) { + for (const channel of channelsList) { this._channelsByAlias.set(channel.alias, channel); } this._initialized = true; - console.log('[Channels] [initialize] Cache populated successfully'); } catch (error) { console.error('[Channels] Failed to initialize cache:', error); } @@ -103,8 +116,6 @@ export class Channels { throw new ChannelError('Invalid response format - missing channels data', 'INVALID_RESPONSE'); } - console.log('[Channels] [list] Response [%o]', response); - return response.channels; } @@ -123,35 +134,26 @@ export class Channels { return this._channelsByAlias.get(alias); } - const response = await this._httpRequests.request(HttpMethod.GET, `/channel/${encodeURIComponent(alias)}`); - - if (!response.channel) { - throw new ChannelError(`Invalid response format for channel: ${alias}`, 'INVALID_RESPONSE'); - } + const channelList = await this.list(); // Update cache - this._channelsByAlias.set(alias, response.channel); - return response.channel; + return channelList.find(channel => channel.alias === alias); } if (channelId) { - const response = await this._httpRequests.request(HttpMethod.GET, `/channel/${encodeURIComponent(channelId)}`); + const channelList = await this.list(); - if (!response.channel) { - throw new ChannelError(`Invalid response format for channel ID: ${channelId}`, 'INVALID_RESPONSE'); - } - - return response.channel; + return channelList.find(channel => channel.channelId === channelId); } } public async getChannelPublisherCount(channelId: string): Promise { const response = await this._httpRequests.request(HttpMethod.GET, `/channel/${encodeURIComponent(channelId)}/publishers/count`); - return parseInt(response); + return parseInt(response, 10); } - public async getMembers(channelId: string): Promise { + public async getMembers(channelId: string): Promise { if (!channelId || channelId.trim().length === 0) { throw new ChannelError('Channel ID cannot be empty', 'INVALID_CHANNEL_ID'); } diff --git a/src/pcast/IResponse.ts b/src/pcast/IResponse.ts index 67c9233..a18cf71 100644 --- a/src/pcast/IResponse.ts +++ b/src/pcast/IResponse.ts @@ -1,27 +1,19 @@ -import type {Channel} from './Channels'; +import type {Channel, Member} from './Channels'; -export default interface IResponse { +export default interface IResponse { status: string; [key: string]: T | string; } // More specific response types for better type safety -export interface ChannelResponse extends IResponse<'channel', Channel> { +export interface ChannelResponse extends IResponse { channel: Channel; } -export interface ChannelsResponse extends IResponse<'channels', Channel[]> { +export interface ChannelsResponse extends IResponse { channels: Channel[]; } -export interface MembersResponse extends IResponse<'members', Channel[]> { - members: Channel[]; -} - -export interface PublishingReportResponse extends IResponse<'publishingReport', string> { - publishingReport: string; -} - -export interface ViewingReportResponse extends IResponse<'viewingReport', string> { - viewingReport: string; +export interface MembersResponse extends IResponse { + members: Member[]; } diff --git a/src/pcast/PCastRequests.ts b/src/pcast/PCastRequests.ts index 594be2c..da93786 100644 --- a/src/pcast/PCastRequests.ts +++ b/src/pcast/PCastRequests.ts @@ -9,10 +9,15 @@ export class PCastHttpRequests extends HttpRequests { private readonly _tenancy: string; constructor(baseUri: string, applicationCredentials: ApplicationCredentials, options: {requestTimeoutDuration?: number} = {}) { + const credentials = `${applicationCredentials.id}:${applicationCredentials.secret}`; + const basic = typeof btoa === 'function' + ? btoa(credentials) + : Buffer.from(credentials, 'utf-8').toString('base64'); + const baseHeaders = new Headers({ 'Content-Type': 'application/json', Accept: 'application/json', - Authorization: `Basic ${btoa(`${applicationCredentials.id}:${applicationCredentials.secret}`)}` + Authorization: `Basic ${basic}` }); super(baseUri, baseHeaders, options); diff --git a/src/pcast/ReportKind.ts b/src/pcast/ReportKind.ts new file mode 100644 index 0000000..5157855 --- /dev/null +++ b/src/pcast/ReportKind.ts @@ -0,0 +1,34 @@ +import assertUnreachable from '../lang/assertUnreachable'; + +export enum ReportKind { + Publishing = 0, + Viewing = 1 +} + +export type ReportKindType = 'Publishing' | 'Viewing'; + +export class ReportKindMapping { + public static convertReportKindTypeToReportKind(reportKindType: ReportKindType): ReportKind { + switch (reportKindType) { + case 'Publishing': + return ReportKind.Publishing; + case 'Viewing': + return ReportKind.Viewing; + + default: + assertUnreachable(reportKindType); + } + } + + public static convertReportKindToReportKindType(reportKind: ReportKind): ReportKindType { + switch (reportKind) { + case ReportKind.Publishing: + return 'Publishing'; + case ReportKind.Viewing: + return 'Viewing'; + + default: + assertUnreachable(reportKind); + } + } +} diff --git a/src/pcast/Reporting.ts b/src/pcast/Reporting.ts index a9cf787..992bf14 100644 --- a/src/pcast/Reporting.ts +++ b/src/pcast/Reporting.ts @@ -1,40 +1,8 @@ import {HttpMethod} from '../net/http/HttpMethod'; import type {PCastHttpRequests} from './PCastRequests'; -import type {PublishingReportResponse, ViewingReportResponse} from './IResponse'; import assertUnreachable from '../lang/assertUnreachable'; - -export enum ReportKind { - Publishing = 0, - Viewing = 1 -} - -export type ReportKindType = 'Publishing' | 'Viewing'; - -export class ReportKindMapping { - public static convertReportKindTypeToReportKind(reportKindType: ReportKindType): ReportKind { - switch (reportKindType) { - case 'Publishing': - return ReportKind.Publishing; - case 'Viewing': - return ReportKind.Viewing; - - default: - assertUnreachable(reportKindType); - } - } - - public static convertReportKindToReportKindType(reportKind: ReportKind): ReportKindType { - switch (reportKind) { - case ReportKind.Publishing: - return 'Publishing'; - case ReportKind.Viewing: - return 'Viewing'; - - default: - assertUnreachable(reportKind); - } - } -} +import {ReportKind} from './ReportKind'; +import { ViewingReportKind, ViewingReportKindMapping } from './ViewingReportKind'; export type PublishingReportOptions = { applicationIds?: string[]; @@ -48,13 +16,6 @@ export type PublishingReportOptions = { end: string; }; -export enum ViewingReportKind { - RealTime = 0, - HLS = 1, - DASH = 2 -} - -export type ViewingReportKindType = 'RealTime' | 'HLS' | 'DASH'; export type ViewingReportOptions = { kind: ViewingReportKind; @@ -80,21 +41,18 @@ export class Reporting { } public async generateReport(kind: ReportKind, options: ReportOptions): Promise { - console.log('[Reporting] generateReport [%o]', ReportKindMapping.convertReportKindToReportKindType(kind)); - - if (kind === ReportKind.Publishing) { - return this.requestPublishingReport(options as PublishingReportOptions); + switch (kind) { + case ReportKind.Publishing: + return this.requestPublishingReport(options as PublishingReportOptions); + case ReportKind.Viewing: + return this.requestViewingReport(options as ViewingReportOptions); + default: + assertUnreachable(kind); } - - if (kind === ReportKind.Viewing) { - return this.requestViewingReport(options as ViewingReportOptions); - } - - throw new Error(`[Reporting] Unsupported report kind: ${kind}`); } public async requestPublishingReport(options: PublishingReportOptions): Promise { - if (!(options.start || options.end)) { + if (!options.start || !options.end) { throw new Error('[Reporting] [requestPublishingReport] requires a start and end Date'); } const publishingReportOptions = { @@ -104,30 +62,23 @@ export class Reporting { const requestPublishingOptions = { body: JSON.stringify({publishingReport: publishingReportOptions}) }; - const response = await this._httpRequests.request(HttpMethod.PUT, '/pcast/reporting/publishing', requestPublishingOptions); + const response = await this._httpRequests.request(HttpMethod.PUT, '/reporting/publishing', requestPublishingOptions); - if (!response.publishingReport) { - throw new Error('[Reporting] [requestPublishingReport] Invalid response format - missing publishingReport data'); - } - - return response.publishingReport; + return response; } private async requestViewingReport(options: ViewingReportOptions): Promise { const viewingReportOptions = { - ...options + ...options, + kind: ViewingReportKindMapping.convertViewingReportKindToViewingReportKindType(options.kind) }; const requestViewingOptions = { body: JSON.stringify({viewingReport: viewingReportOptions}) }; - const response = await this._httpRequests.request(HttpMethod.PUT, '/pcast/reporting/viewing', requestViewingOptions); - - if (!response.viewingReport) { - throw new Error('[Reporting] [requestViewingReport] Invalid response format - missing viewingReport data'); - } - - return response.viewingReport; + const response = await this._httpRequests.request(HttpMethod.PUT, '/reporting/viewing', requestViewingOptions); + + return response; } } diff --git a/src/pcast/Stream.ts b/src/pcast/Stream.ts index 8268b29..79cea8b 100644 --- a/src/pcast/Stream.ts +++ b/src/pcast/Stream.ts @@ -16,7 +16,7 @@ export class Streams { throw new Error('Invalid media URI no media type found'); } - const response = await this._httpRequests.request>(HttpMethod.PUT, `/stream/publish/uri/${mediaType}`, { + const response = await this._httpRequests.request>(HttpMethod.PUT, `/stream/publish/uri/${mediaType}`, { body: JSON.stringify({ token, uri: mediaUri, diff --git a/src/pcast/ViewingReportKind.ts b/src/pcast/ViewingReportKind.ts new file mode 100644 index 0000000..659750e --- /dev/null +++ b/src/pcast/ViewingReportKind.ts @@ -0,0 +1,37 @@ +import assertUnreachable from "../lang/assertUnreachable"; + +export enum ViewingReportKind { + RealTime = 0, + HLS = 1, + DASH = 2 +} + +export type ViewingReportKindType = 'RealTime' | 'HLS' | 'DASH'; + +export class ViewingReportKindMapping { + public static convertViewingReportKindTypeToViewingReportKind(viewingReportKindType: ViewingReportKindType): ViewingReportKind { + switch (viewingReportKindType) { + case 'RealTime': + return ViewingReportKind.RealTime; + case 'HLS': + return ViewingReportKind.HLS; + case 'DASH': + return ViewingReportKind.DASH; + default: + assertUnreachable(viewingReportKindType); + } + } + + public static convertViewingReportKindToViewingReportKindType(viewingReportKind: ViewingReportKind): ViewingReportKindType { + switch (viewingReportKind) { + case ViewingReportKind.RealTime: + return 'RealTime'; + case ViewingReportKind.HLS: + return 'HLS'; + case ViewingReportKind.DASH: + return 'DASH'; + default: + assertUnreachable(viewingReportKind); + } + } +} \ No newline at end of file diff --git a/src/pcast/index.ts b/src/pcast/index.ts index c144c68..a5a2109 100644 --- a/src/pcast/index.ts +++ b/src/pcast/index.ts @@ -1,3 +1,4 @@ export * from './Channels'; -export * from './Stream'; +export * from './Reporting'; export * from './PCastRequests'; +export * from './Stream';