update legacy apis

This commit is contained in:
2025-09-01 21:21:45 -04:00
parent bbed095926
commit d0f3af72f6
12 changed files with 160 additions and 63 deletions

View File

@@ -2,7 +2,7 @@
"arrowParens": "avoid", "arrowParens": "avoid",
"bracketSameLine": true, "bracketSameLine": true,
"bracketSpacing": false, "bracketSpacing": false,
"printWidth": 160, "printWidth": 180,
"semi": true, "semi": true,
"singleAttributePerLine": false, "singleAttributePerLine": false,
"singleQuote": true, "singleQuote": true,

View File

@@ -1,4 +1,4 @@
import PCastApi from '../../src/index'; import PCastApi, {ReportKind} from '../../src/index';
const pcastUri = 'https://pcast-stg.phenixrts.com'; const pcastUri = 'https://pcast-stg.phenixrts.com';
const application = { const application = {
@@ -6,34 +6,11 @@ const application = {
secret: 'AMAsDzr.dIuGMZ.Zu52Dt~MQvP!DZwYg' secret: 'AMAsDzr.dIuGMZ.Zu52Dt~MQvP!DZwYg'
}; };
const pcastApi = PCastApi.create(pcastUri, application); const pcastApi = PCastApi.create(pcastUri, application);
const viewingReportRealTime = await pcastApi.generateViewingReport('RealTime', new Date('2025-08-02'), new Date('2025-09-01'), {});
const publishingReport = await pcastApi.reporting.generateReport(ReportKind.Publishing, {
start: '2025-08-02',
end: '2025-09-01'
});
console.log('pcastApi [%o]', pcastApi); // console.log(viewingReportRealTime);
console.log(); console.log(publishingReport);
console.log('ChannelsApi [%o]', pcastApi.channels);
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);

View File

@@ -1,6 +1,6 @@
{ {
"name": "@techniker-me/pcast-api", "name": "@techniker-me/pcast-api",
"version": "2025.1.4", "version": "2025.1.5",
"type": "module", "type": "module",
"scripts": { "scripts": {
"ci-build": "bun run build", "ci-build": "bun run build",
@@ -9,7 +9,7 @@
"test:coverage": "bun test --coverage", "test:coverage": "bun test --coverage",
"preformat": "bun install", "preformat": "bun install",
"format": "prettier --write ./", "format": "prettier --write ./",
"prelint": "bun format", "prelint:fix": "bun format",
"lint": "eslint src/**/*.ts", "lint": "eslint src/**/*.ts",
"lint:fix": "eslint src/**/*.ts --fix", "lint:fix": "eslint src/**/*.ts --fix",
"build:node": "bun build src/index.ts --outdir dist/node --target node --format esm --production", "build:node": "bun build src/index.ts --outdir dist/node --target node --format esm --production",

View File

@@ -1,4 +1,5 @@
import {Channels, Streams, type ApplicationCredentials, PCastHttpRequests, Reporting} from './apis'; import {Channels, Streams, type ApplicationCredentials, PCastHttpRequests, Reporting, Channel, Member} from './apis';
import {ReportKind, ViewingReportKindMapping, type ViewingReportKindType} from './apis/Reporting';
export class PCastApi { export class PCastApi {
private readonly _channels: Channels; private readonly _channels: Channels;
@@ -12,7 +13,8 @@ export class PCastApi {
} }
public static create(pcastUri: string, applicationCredentials: ApplicationCredentials): PCastApi { public static create(pcastUri: string, applicationCredentials: ApplicationCredentials): PCastApi {
const pcastHttpRequests = new PCastHttpRequests(pcastUri.replace(/\/+$/, '').endsWith('/pcast') ? pcastUri : `${pcastUri}/pcast`, applicationCredentials); const pcastUriWithSuffix = pcastUri.replace(/\/+$/, '').endsWith('/pcast') ? pcastUri : `${pcastUri}/pcast`;
const pcastHttpRequests = new PCastHttpRequests(pcastUriWithSuffix, applicationCredentials);
const channels = new Channels(pcastHttpRequests); const channels = new Channels(pcastHttpRequests);
const streams = new Streams(pcastHttpRequests); const streams = new Streams(pcastHttpRequests);
const reporting = new Reporting(pcastHttpRequests); const reporting = new Reporting(pcastHttpRequests);
@@ -31,4 +33,55 @@ export class PCastApi {
get reporting(): Reporting { get reporting(): Reporting {
return this._reporting; return this._reporting;
} }
// ====== Legacy API =======
// @deprecated - use pcastApi.channels.create instead
public async createChannel(channelName: string, channelDescription: string, channelOptions: string[] = []): Promise<Channel> {
return this._channels.create(channelName, channelDescription, channelOptions);
}
// @deprecated - use pcastApi.channels.get instead
public async getChannelInfoByAlias(alias: string): Promise<Channel | undefined> {
return this._channels.get({alias});
}
// @deprecated - use pcastApi.channels.get instead
public async getChannelInfoByChannelId(channelId: string): Promise<Channel | undefined> {
return this._channels.get({channelId});
}
// @deprecated - use pcastApi.channels.getPublisherCount instead
public async getChannelPublisherCount(channelId: string): Promise<number> {
return this._channels.getPublisherCount(channelId);
}
// @deprecated - use pcastApi.channels.getMembers instead
public async getChannelMembers(channelId: string): Promise<Member[]> {
return this._channels.getMembers(channelId);
}
// @deprecated - use pcastApi.channels.getMembersByChannelAlias instead
public async getChannelMembersByChannelAlias(alias: string): Promise<Member[]> {
return this._channels.getMembersByChannelAlias(alias);
}
// @deprecated - use pcastApi.channels.delete instead
public async deleteChannel(channelId: string): Promise<Channel> {
return this._channels.delete({channelId});
}
// @deprecated - use pcastApi.reporting.generateReport instead
public async generateViewingReport(
kind: ViewingReportKindType,
reportStartTimestamp: Date,
reportEndTimestamp: Date,
options: Record<string, string | string[]> = {}
): Promise<string> {
return this._reporting.generateReport(ReportKind.Viewing, {
kind: ViewingReportKindMapping.convertViewingReportKindTypeToViewingReportKind(kind),
start: reportStartTimestamp.toISOString(),
end: reportEndTimestamp.toISOString(),
...options
});
}
} }

View File

@@ -55,7 +55,7 @@ export class Channels {
this.initialize(); this.initialize();
} }
public async createChannel(name: string, description: string, channelOptions: string[] = []): Promise<Channel> { public async create(name: string, description: string, channelOptions: string[] = []): Promise<Channel> {
if (!name || name.trim().length === 0) { if (!name || name.trim().length === 0) {
throw new ChannelError('Channel name cannot be empty', 'INVALID_NAME'); throw new ChannelError('Channel name cannot be empty', 'INVALID_NAME');
} }
@@ -186,13 +186,41 @@ export class Channels {
return deletedChannel; return deletedChannel;
} }
public clearCache(): void { async getPublishSourceStreamId(channelId: string, retryCount: number = 3): Promise<string | null> {
this._channelsByAlias.clear(); const retryCountRemaining = retryCount || 3;
const channelMembers = await this.getMembers(channelId);
if (channelMembers.length === 0) {
if (retryCountRemaining > 0) {
return this.getPublishSourceStreamId(channelId, retryCountRemaining - 1);
}
return null;
}
const presenter = channelMembers.find(member => member.role === 'Presenter');
if (!presenter) {
if (retryCountRemaining > 0) {
return this.getPublishSourceStreamId(channelId, retryCountRemaining - 1);
}
return null;
}
const publishSourceStreamIdRegExp = /pcast:\/\/.*\/([^?]*)/;
return presenter.streams[0].uri.match(publishSourceStreamIdRegExp)?.[1] ?? null;
} }
public getCacheSize(): number { // TODO(AZ): Implement this
return this._channelsByAlias.size; // public async fork(channelId: string): Promise<Channel>
}
// TODO(AZ): Implement this
// public async killChannel(channelId: string): Promise<Channel>
// TODO(AZ): Implement this
// public async publishViaUrl(mediaUriToPublish: string, token: string): Promise<Channel>
private async initialize(): Promise<void> { private async initialize(): Promise<void> {
try { try {

View File

@@ -1,11 +1,12 @@
import assertUnreachable from '../lang/assertUnreachable'; import assertUnreachable from '../../lang/assertUnreachable';
export enum ReportKind { export enum ReportKind {
Publishing = 0, Publishing = 0,
Viewing = 1 Viewing = 1,
Ingest = 2
} }
export type ReportKindType = 'Publishing' | 'Viewing'; export type ReportKindType = 'Publishing' | 'Viewing' | 'IngestReport';
export class ReportKindMapping { export class ReportKindMapping {
public static convertReportKindTypeToReportKind(reportKindType: ReportKindType): ReportKind { public static convertReportKindTypeToReportKind(reportKindType: ReportKindType): ReportKind {
@@ -14,6 +15,8 @@ export class ReportKindMapping {
return ReportKind.Publishing; return ReportKind.Publishing;
case 'Viewing': case 'Viewing':
return ReportKind.Viewing; return ReportKind.Viewing;
case 'IngestReport':
return ReportKind.Ingest;
default: default:
assertUnreachable(reportKindType); assertUnreachable(reportKindType);
@@ -26,6 +29,8 @@ export class ReportKindMapping {
return 'Publishing'; return 'Publishing';
case ReportKind.Viewing: case ReportKind.Viewing:
return 'Viewing'; return 'Viewing';
case ReportKind.Ingest:
return 'IngestReport';
default: default:
assertUnreachable(reportKind); assertUnreachable(reportKind);

View File

@@ -1,6 +1,6 @@
import {HttpMethod} from '../net/http/HttpMethod'; import {HttpMethod} from '../../net/http/HttpMethod';
import type {PCastHttpRequests} from './PCastRequests'; import type {PCastHttpRequests} from '../PCastRequests';
import assertUnreachable from '../lang/assertUnreachable'; import assertUnreachable from '../../lang/assertUnreachable';
import {ReportKind} from './ReportKind'; import {ReportKind} from './ReportKind';
import {ViewingReportKind, ViewingReportKindMapping} from './ViewingReportKind'; import {ViewingReportKind, ViewingReportKindMapping} from './ViewingReportKind';
@@ -32,6 +32,17 @@ export type ViewingReportOptions = {
end: string; end: string;
}; };
export type IngestBufferUnderrunOptions = {
kind: 'BufferUnderrun'; // Are there more kinds? https://phenixrts.com/docs/sdk_ref/rest-api/reporting/#generate-an-ingest-report
applicationIds?: string[];
streamIds?: string[];
channelIds?: string[];
channelAliases?: string[];
ingestIds?: string[];
start: string;
end: string;
};
export class Reporting { export class Reporting {
private readonly _httpRequests: PCastHttpRequests; private readonly _httpRequests: PCastHttpRequests;
@@ -39,12 +50,17 @@ export class Reporting {
this._httpRequests = httpRequests; this._httpRequests = httpRequests;
} }
public async generateReport<ReportOptions extends PublishingReportOptions | ViewingReportOptions>(kind: ReportKind, options: ReportOptions): Promise<string> { public async generateReport<ReportOptions extends PublishingReportOptions | ViewingReportOptions | IngestBufferUnderrunOptions>(
kind: ReportKind,
options: ReportOptions
): Promise<string> {
switch (kind) { switch (kind) {
case ReportKind.Publishing: case ReportKind.Publishing:
return this.requestPublishingReport(options as PublishingReportOptions); return this.requestPublishingReport(options as PublishingReportOptions);
case ReportKind.Viewing: case ReportKind.Viewing:
return this.requestViewingReport(options as ViewingReportOptions); return this.requestViewingReport(options as ViewingReportOptions);
case ReportKind.Ingest:
return this.requestIngestBufferUnderrun(options as IngestBufferUnderrunOptions);
default: default:
assertUnreachable(kind); assertUnreachable(kind);
} }
@@ -54,12 +70,12 @@ export class Reporting {
if (!options.start || !options.end) { if (!options.start || !options.end) {
throw new Error('[Reporting] [requestPublishingReport] requires a start and end Date'); throw new Error('[Reporting] [requestPublishingReport] requires a start and end Date');
} }
const publishingReportOptions = { const publishingReport = {
...options ...options
}; };
const requestPublishingOptions = { const requestPublishingOptions = {
body: JSON.stringify({publishingReport: publishingReportOptions}) body: JSON.stringify({publishingReport: publishingReport})
}; };
const response = await this._httpRequests.request<string>(HttpMethod.PUT, '/reporting/publishing', requestPublishingOptions); const response = await this._httpRequests.request<string>(HttpMethod.PUT, '/reporting/publishing', requestPublishingOptions);
@@ -67,17 +83,36 @@ export class Reporting {
} }
private async requestViewingReport(options: ViewingReportOptions): Promise<string> { private async requestViewingReport(options: ViewingReportOptions): Promise<string> {
const viewingReportOptions = { const viewingReport = {
...options, ...options,
tags: options.tags ?? [],
originTags: options.originTags ?? [],
kind: ViewingReportKindMapping.convertViewingReportKindToViewingReportKindType(options.kind) kind: ViewingReportKindMapping.convertViewingReportKindToViewingReportKindType(options.kind)
}; };
console.log(viewingReport);
const requestViewingOptions = { const requestViewingOptions = {
body: JSON.stringify({viewingReport: viewingReportOptions}) body: JSON.stringify({viewingReport: viewingReport})
}; };
const response = await this._httpRequests.request<string>(HttpMethod.PUT, '/reporting/viewing', requestViewingOptions); const response = await this._httpRequests.request<string>(HttpMethod.PUT, '/reporting/viewing', requestViewingOptions);
return response; return response;
} }
private async requestIngestBufferUnderrun(options: IngestBufferUnderrunOptions): Promise<string> {
const ingestReport = {
...options,
kind: 'BufferUnderrun'
};
const requestViewingOptions = {
body: JSON.stringify({ingestReport})
};
const response = await this._httpRequests.request<string>(HttpMethod.PUT, '/reporting/ingest', requestViewingOptions);
return response;
}
} }

View File

@@ -1,4 +1,4 @@
import assertUnreachable from '../lang/assertUnreachable'; import assertUnreachable from '../../lang/assertUnreachable';
export enum ViewingReportKind { export enum ViewingReportKind {
RealTime = 0, RealTime = 0,

View File

@@ -0,0 +1,3 @@
export * from './Reporting';
export * from './ReportKind';
export * from './ViewingReportKind';

View File

@@ -2,5 +2,4 @@ export * from './Channels';
export * from './Reporting'; export * from './Reporting';
export * from './PCastRequests'; export * from './PCastRequests';
export * from './Stream'; export * from './Stream';
export * from './ReportKind'; export * from './Reporting';
export * from './ViewingReportKind';

View File

@@ -1,16 +1,13 @@
import {PCastApi} from './PCastApi'; import {PCastApi} from './PCastApi';
import type {Channels, Streams, Reporting, ReportKind, ViewingReportKind} from './apis'; import {Channels, Streams, Reporting, PublishingReportOptions, ViewingReportOptions, ReportKindType, ViewingReportKindType, ViewingReportKind, ReportKind} from './apis';
import type {ChannelId, Channel, ChannelAlias, Member, ChannelError} from './apis/Channels'; import type {ChannelId, Channel, ChannelAlias, Member, ChannelError} from './apis/Channels';
import type {HttpMethod} from './net/http/HttpMethod'; import type {HttpMethod} from './net/http/HttpMethod';
import type {HttpRequestError} from './net/http/HttpRequests'; import type {HttpRequestError} from './net/http/HttpRequests';
import type {ChannelResponse, ChannelsResponse, MembersResponse} from './apis/IResponse'; import type {ChannelResponse, ChannelsResponse, MembersResponse} from './apis/IResponse';
import type {ApplicationCredentials} from './apis/PCastRequests'; import type {ApplicationCredentials} from './apis/PCastRequests';
import type {PublishingReportOptions, ViewingReportOptions} from './apis/Reporting';
import type {ReportKindType} from './apis/ReportKind';
import type {ViewingReportKindType} from './apis/ViewingReportKind';
export type {Channels, Streams, Reporting, ReportKind, ViewingReportKind}; export type {Channels, Streams, Reporting, ViewingReportKind};
export type {ChannelId, Channel, ChannelAlias, Member, ChannelError}; export type {ChannelId, Channel, ChannelAlias, Member, ChannelError};
export type {HttpMethod, HttpRequestError}; export type {HttpMethod, HttpRequestError};
export type {ChannelResponse, ChannelsResponse, MembersResponse}; export type {ChannelResponse, ChannelsResponse, MembersResponse};
@@ -18,5 +15,5 @@ export type {ApplicationCredentials};
export type {PublishingReportOptions, ViewingReportOptions}; export type {PublishingReportOptions, ViewingReportOptions};
export type {ReportKindType, ViewingReportKindType}; export type {ReportKindType, ViewingReportKindType};
export {PCastApi}; export {PCastApi, ReportKind};
export default PCastApi; export default PCastApi;

View File

@@ -11,7 +11,7 @@
// Bundler mode // Bundler mode
"moduleResolution": "bundler", "moduleResolution": "bundler",
"allowImportingTsExtensions": true, "allowImportingTsExtensions": true,
"verbatimModuleSyntax": true, "verbatimModuleSyntax": false,
"emitDeclarationOnly": true, "emitDeclarationOnly": true,
"declaration": true, "declaration": true,
"declarationDir": "./dist/types", "declarationDir": "./dist/types",