From 2709ea4818d06f36310e20acd362964a03361a66 Mon Sep 17 00:00:00 2001 From: Adrian Czerwiec Date: Tue, 22 Sep 2026 12:49:25 +0200 Subject: [PATCH 1/4] add template recordings methods --- .../src/generated/.openapi-generator/FILES | 2 + .../src/generated/apis/RecordingsApi.ts | 566 +++++++++--------- .../src/generated/models/PeerOptionsVapi.ts | 29 +- .../src/generated/models/RecordingSource.ts | 102 ++-- .../src/generated/models/TemplateSource.ts | 87 +++ .../models/TemplateSourceResolution.ts | 78 +++ .../src/generated/models/index.ts | 2 + packages/fishjam-proto/protos | 2 +- packages/js-server-sdk/src/client.ts | 36 +- packages/js-server-sdk/src/index.ts | 2 + packages/js-server-sdk/src/recordings.ts | 23 + packages/js-server-sdk/src/types.ts | 8 + .../js-server-sdk/tests/fixtures/template.js | 1 + .../js-server-sdk/tests/recordings.test.ts | 90 +++ 14 files changed, 678 insertions(+), 350 deletions(-) create mode 100644 packages/fishjam-openapi/src/generated/models/TemplateSource.ts create mode 100644 packages/fishjam-openapi/src/generated/models/TemplateSourceResolution.ts create mode 100644 packages/js-server-sdk/src/recordings.ts create mode 100644 packages/js-server-sdk/tests/fixtures/template.js create mode 100644 packages/js-server-sdk/tests/recordings.test.ts diff --git a/packages/fishjam-openapi/src/generated/.openapi-generator/FILES b/packages/fishjam-openapi/src/generated/.openapi-generator/FILES index dacc5d7d..bf33ffe7 100644 --- a/packages/fishjam-openapi/src/generated/.openapi-generator/FILES +++ b/packages/fishjam-openapi/src/generated/.openapi-generator/FILES @@ -54,6 +54,8 @@ models/StreamsListingResponse.ts models/SubscribeMode.ts models/SubscribeTracksRequest.ts models/Subscriptions.ts +models/TemplateSource.ts +models/TemplateSourceResolution.ts models/Track.ts models/TrackForwarding.ts models/TrackForwardingInfo.ts diff --git a/packages/fishjam-openapi/src/generated/apis/RecordingsApi.ts b/packages/fishjam-openapi/src/generated/apis/RecordingsApi.ts index 9646865c..500f6a27 100644 --- a/packages/fishjam-openapi/src/generated/apis/RecordingsApi.ts +++ b/packages/fishjam-openapi/src/generated/apis/RecordingsApi.ts @@ -2,7 +2,7 @@ /* eslint-disable */ /** * Fishjam API - * API for managing Fishjam real-time media resources. ## Authentication ## Credentials (Fishjam ID, Fishjam Management Token) can be obtained at https://fishjam.io/app. All requests require HTTP Bearer authorization using the Fishjam Management Token. ## Fishjam SDKs ## For TypeScript and Python users, we provide SDKs that simplify using this API. We recommend using them instead of manually consuming the API in these languages. You can learn more about our SDKs in our [SDK Docs](http://fishjam.swmansion.com/docs/how-to/backend/server-setup) + * API for managing Fishjam real-time media resources. ## Authentication ## Credentials (Fishjam ID, Fishjam Management Token) can be obtained at https://fishjam.io/app. All requests require HTTP Bearer authorization using the Fishjam Management Token. ## Fishjam SDKs ## For TypeScript and Python users, we provide SDKs that simplify using this API. We recommend using them instead of manually consuming the API in these languages. You can learn more about our SDKs in our [SDK Docs](http://fishjam.swmansion.com/docs/how-to/backend/server-setup) * * The version of the OpenAPI document: 0.30.0 * Contact: contact@fishjam.io @@ -13,329 +13,349 @@ */ import * as runtime from '../runtime'; +import { type RecordingConfig, RecordingConfigFromJSON, RecordingConfigToJSON } from '../models/RecordingConfig'; import { - type RecordingConfig, - RecordingConfigFromJSON, - RecordingConfigToJSON, -} from '../models/RecordingConfig'; -import { - type RecordingDetailsResponse, - RecordingDetailsResponseFromJSON, - RecordingDetailsResponseToJSON, + type RecordingDetailsResponse, + RecordingDetailsResponseFromJSON, + RecordingDetailsResponseToJSON, } from '../models/RecordingDetailsResponse'; import { - type RecordingListResponse, - RecordingListResponseFromJSON, - RecordingListResponseToJSON, + type RecordingListResponse, + RecordingListResponseFromJSON, + RecordingListResponseToJSON, } from '../models/RecordingListResponse'; -import { - type RecordingStatus, - RecordingStatusFromJSON, - RecordingStatusToJSON, -} from '../models/RecordingStatus'; +import { type RecordingStatus, RecordingStatusFromJSON, RecordingStatusToJSON } from '../models/RecordingStatus'; -export interface CreateRecordingRequest { - recordingConfig: RecordingConfig; +export interface CreateRecordingOperationRequest { + recordingConfig: RecordingConfig; } export interface DeleteRecordingRequest { - recordingId: string; + recordingId: string; } export interface GetRecordingRequest { - recordingId: string; + recordingId: string; } export interface ListRecordingsRequest { - metadata?: { [key: string]: any; }; - status?: RecordingStatus; + metadata?: { [key: string]: any }; + status?: RecordingStatus; } export interface StopRecordingRequest { - recordingId: string; + recordingId: string; } /** - * + * */ export class RecordingsApi extends runtime.BaseAPI { - - /** - * Creates request options for createRecording without sending the request - */ - async createRecordingRequestOpts(requestParameters: CreateRecordingRequest): Promise { - if (requestParameters['recordingConfig'] == null) { - throw new runtime.RequiredError( - 'recordingConfig', - 'Required parameter "recordingConfig" was null or undefined when calling createRecording().' - ); - } - - const queryParameters: any = {}; - - const headerParameters: runtime.HTTPHeaders = {}; - - headerParameters['Content-Type'] = 'application/json'; - - if (this.configuration && this.configuration.accessToken) { - const token = this.configuration.accessToken; - const tokenString = await token("management_token", []); - - if (tokenString) { - headerParameters["Authorization"] = `Bearer ${tokenString}`; - } - } - - let urlPath = `/recordings`; - - return { - path: urlPath, - method: 'POST', - headers: headerParameters, - query: queryParameters, - body: RecordingConfigToJSON(requestParameters['recordingConfig']), - }; + /** + * Creates request options for createRecording without sending the request + */ + async createRecordingRequestOpts(requestParameters: CreateRecordingOperationRequest): Promise { + if (requestParameters['recordingConfig'] == null) { + throw new runtime.RequiredError( + 'recordingConfig', + 'Required parameter "recordingConfig" was null or undefined when calling createRecording().' + ); } - /** - * Create a recording resource. Capturing starts synchronously, so it is returned with status `active`. - * Create a recording - */ - async createRecordingRaw(requestParameters: CreateRecordingRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise> { - const requestOptions = await this.createRecordingRequestOpts(requestParameters); - const response = await this.request(requestOptions, initOverrides); + const queryParameters: any = {}; - return new runtime.JSONApiResponse(response, (jsonValue) => RecordingDetailsResponseFromJSON(jsonValue)); - } + const headerParameters: runtime.HTTPHeaders = {}; + + headerParameters['Content-Type'] = 'application/json'; + + if (this.configuration && this.configuration.accessToken) { + const token = this.configuration.accessToken; + const tokenString = await token('management_token', []); - /** - * Create a recording resource. Capturing starts synchronously, so it is returned with status `active`. - * Create a recording - */ - async createRecording(requestParameters: CreateRecordingRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise { - const response = await this.createRecordingRaw(requestParameters, initOverrides); - return await response.value(); + if (tokenString) { + headerParameters['Authorization'] = `Bearer ${tokenString}`; + } } - /** - * Creates request options for deleteRecording without sending the request - */ - async deleteRecordingRequestOpts(requestParameters: DeleteRecordingRequest): Promise { - if (requestParameters['recordingId'] == null) { - throw new runtime.RequiredError( - 'recordingId', - 'Required parameter "recordingId" was null or undefined when calling deleteRecording().' - ); - } - - const queryParameters: any = {}; - - const headerParameters: runtime.HTTPHeaders = {}; - - if (this.configuration && this.configuration.accessToken) { - const token = this.configuration.accessToken; - const tokenString = await token("management_token", []); - - if (tokenString) { - headerParameters["Authorization"] = `Bearer ${tokenString}`; - } - } - - let urlPath = `/recordings/{recording_id}`; - urlPath = urlPath.replace('{recording_id}', encodeURIComponent(String(requestParameters['recordingId']))); - - return { - path: urlPath, - method: 'DELETE', - headers: headerParameters, - query: queryParameters, - }; + let urlPath = `/recordings`; + + return { + path: urlPath, + method: 'POST', + headers: headerParameters, + query: queryParameters, + body: RecordingConfigToJSON(requestParameters['recordingConfig']), + }; + } + + /** + * Create a recording resource. Capturing starts synchronously, so it is returned with status `active`. A source naming an `outputId` records an output the composition already has and is sent as JSON. A template source renders its own scene and has to be sent as `multipart/form-data`: the configuration in a `config` part, the React bundle in a `template` part. + * Create a recording + */ + async createRecordingRaw( + requestParameters: CreateRecordingOperationRequest, + initOverrides?: RequestInit | runtime.InitOverrideFunction + ): Promise> { + const requestOptions = await this.createRecordingRequestOpts(requestParameters); + const response = await this.request(requestOptions, initOverrides); + + return new runtime.JSONApiResponse(response, (jsonValue) => RecordingDetailsResponseFromJSON(jsonValue)); + } + + /** + * Create a recording resource. Capturing starts synchronously, so it is returned with status `active`. A source naming an `outputId` records an output the composition already has and is sent as JSON. A template source renders its own scene and has to be sent as `multipart/form-data`: the configuration in a `config` part, the React bundle in a `template` part. + * Create a recording + */ + async createRecording( + requestParameters: CreateRecordingOperationRequest, + initOverrides?: RequestInit | runtime.InitOverrideFunction + ): Promise { + const response = await this.createRecordingRaw(requestParameters, initOverrides); + return await response.value(); + } + + /** + * Creates request options for deleteRecording without sending the request + */ + async deleteRecordingRequestOpts(requestParameters: DeleteRecordingRequest): Promise { + if (requestParameters['recordingId'] == null) { + throw new runtime.RequiredError( + 'recordingId', + 'Required parameter "recordingId" was null or undefined when calling deleteRecording().' + ); } - /** - * Delete a recording by id. The recording disappears from the API immediately; its stored media is removed asynchronously by a background job. A recording that is still `active` cannot be deleted — wait for it to finish or fail. - * Delete a recording - */ - async deleteRecordingRaw(requestParameters: DeleteRecordingRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise> { - const requestOptions = await this.deleteRecordingRequestOpts(requestParameters); - const response = await this.request(requestOptions, initOverrides); + const queryParameters: any = {}; - return new runtime.VoidApiResponse(response); - } + const headerParameters: runtime.HTTPHeaders = {}; + + if (this.configuration && this.configuration.accessToken) { + const token = this.configuration.accessToken; + const tokenString = await token('management_token', []); - /** - * Delete a recording by id. The recording disappears from the API immediately; its stored media is removed asynchronously by a background job. A recording that is still `active` cannot be deleted — wait for it to finish or fail. - * Delete a recording - */ - async deleteRecording(requestParameters: DeleteRecordingRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise { - await this.deleteRecordingRaw(requestParameters, initOverrides); + if (tokenString) { + headerParameters['Authorization'] = `Bearer ${tokenString}`; + } } - /** - * Creates request options for getRecording without sending the request - */ - async getRecordingRequestOpts(requestParameters: GetRecordingRequest): Promise { - if (requestParameters['recordingId'] == null) { - throw new runtime.RequiredError( - 'recordingId', - 'Required parameter "recordingId" was null or undefined when calling getRecording().' - ); - } - - const queryParameters: any = {}; - - const headerParameters: runtime.HTTPHeaders = {}; - - if (this.configuration && this.configuration.accessToken) { - const token = this.configuration.accessToken; - const tokenString = await token("management_token", []); - - if (tokenString) { - headerParameters["Authorization"] = `Bearer ${tokenString}`; - } - } - - let urlPath = `/recordings/{recording_id}`; - urlPath = urlPath.replace('{recording_id}', encodeURIComponent(String(requestParameters['recordingId']))); - - return { - path: urlPath, - method: 'GET', - headers: headerParameters, - query: queryParameters, - }; + let urlPath = `/recordings/{recording_id}`; + urlPath = urlPath.replace('{recording_id}', encodeURIComponent(String(requestParameters['recordingId']))); + + return { + path: urlPath, + method: 'DELETE', + headers: headerParameters, + query: queryParameters, + }; + } + + /** + * Delete a recording by id. The recording disappears from the API immediately; its stored media is removed asynchronously by a background job. A recording that is still `active` cannot be deleted — wait for it to finish or fail. + * Delete a recording + */ + async deleteRecordingRaw( + requestParameters: DeleteRecordingRequest, + initOverrides?: RequestInit | runtime.InitOverrideFunction + ): Promise> { + const requestOptions = await this.deleteRecordingRequestOpts(requestParameters); + const response = await this.request(requestOptions, initOverrides); + + return new runtime.VoidApiResponse(response); + } + + /** + * Delete a recording by id. The recording disappears from the API immediately; its stored media is removed asynchronously by a background job. A recording that is still `active` cannot be deleted — wait for it to finish or fail. + * Delete a recording + */ + async deleteRecording( + requestParameters: DeleteRecordingRequest, + initOverrides?: RequestInit | runtime.InitOverrideFunction + ): Promise { + await this.deleteRecordingRaw(requestParameters, initOverrides); + } + + /** + * Creates request options for getRecording without sending the request + */ + async getRecordingRequestOpts(requestParameters: GetRecordingRequest): Promise { + if (requestParameters['recordingId'] == null) { + throw new runtime.RequiredError( + 'recordingId', + 'Required parameter "recordingId" was null or undefined when calling getRecording().' + ); } - /** - * Get a recording by id. - * Get a recording - */ - async getRecordingRaw(requestParameters: GetRecordingRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise> { - const requestOptions = await this.getRecordingRequestOpts(requestParameters); - const response = await this.request(requestOptions, initOverrides); + const queryParameters: any = {}; + + const headerParameters: runtime.HTTPHeaders = {}; + + if (this.configuration && this.configuration.accessToken) { + const token = this.configuration.accessToken; + const tokenString = await token('management_token', []); - return new runtime.JSONApiResponse(response, (jsonValue) => RecordingDetailsResponseFromJSON(jsonValue)); + if (tokenString) { + headerParameters['Authorization'] = `Bearer ${tokenString}`; + } } - /** - * Get a recording by id. - * Get a recording - */ - async getRecording(requestParameters: GetRecordingRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise { - const response = await this.getRecordingRaw(requestParameters, initOverrides); - return await response.value(); + let urlPath = `/recordings/{recording_id}`; + urlPath = urlPath.replace('{recording_id}', encodeURIComponent(String(requestParameters['recordingId']))); + + return { + path: urlPath, + method: 'GET', + headers: headerParameters, + query: queryParameters, + }; + } + + /** + * Get a recording by id. + * Get a recording + */ + async getRecordingRaw( + requestParameters: GetRecordingRequest, + initOverrides?: RequestInit | runtime.InitOverrideFunction + ): Promise> { + const requestOptions = await this.getRecordingRequestOpts(requestParameters); + const response = await this.request(requestOptions, initOverrides); + + return new runtime.JSONApiResponse(response, (jsonValue) => RecordingDetailsResponseFromJSON(jsonValue)); + } + + /** + * Get a recording by id. + * Get a recording + */ + async getRecording( + requestParameters: GetRecordingRequest, + initOverrides?: RequestInit | runtime.InitOverrideFunction + ): Promise { + const response = await this.getRecordingRaw(requestParameters, initOverrides); + return await response.value(); + } + + /** + * Creates request options for listRecordings without sending the request + */ + async listRecordingsRequestOpts(requestParameters: ListRecordingsRequest): Promise { + const queryParameters: any = {}; + + if (requestParameters['metadata'] != null) { + for (let key of Object.keys(requestParameters['metadata'])) { + queryParameters[key] = requestParameters['metadata'][key]; + } } - /** - * Creates request options for listRecordings without sending the request - */ - async listRecordingsRequestOpts(requestParameters: ListRecordingsRequest): Promise { - const queryParameters: any = {}; - - if (requestParameters['metadata'] != null) { - for (let key of Object.keys(requestParameters['metadata'])) { - queryParameters[key] = requestParameters['metadata'][key]; - } - } - - if (requestParameters['status'] != null) { - queryParameters['status'] = requestParameters['status']; - } - - const headerParameters: runtime.HTTPHeaders = {}; - - if (this.configuration && this.configuration.accessToken) { - const token = this.configuration.accessToken; - const tokenString = await token("management_token", []); - - if (tokenString) { - headerParameters["Authorization"] = `Bearer ${tokenString}`; - } - } - - let urlPath = `/recordings`; - - return { - path: urlPath, - method: 'GET', - headers: headerParameters, - query: queryParameters, - }; + if (requestParameters['status'] != null) { + queryParameters['status'] = requestParameters['status']; } - /** - * List recordings for the tenant, optionally filtered by metadata and status. - * List recordings - */ - async listRecordingsRaw(requestParameters: ListRecordingsRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise> { - const requestOptions = await this.listRecordingsRequestOpts(requestParameters); - const response = await this.request(requestOptions, initOverrides); + const headerParameters: runtime.HTTPHeaders = {}; - return new runtime.JSONApiResponse(response, (jsonValue) => RecordingListResponseFromJSON(jsonValue)); - } + if (this.configuration && this.configuration.accessToken) { + const token = this.configuration.accessToken; + const tokenString = await token('management_token', []); - /** - * List recordings for the tenant, optionally filtered by metadata and status. - * List recordings - */ - async listRecordings(requestParameters: ListRecordingsRequest = {}, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise { - const response = await this.listRecordingsRaw(requestParameters, initOverrides); - return await response.value(); + if (tokenString) { + headerParameters['Authorization'] = `Bearer ${tokenString}`; + } } - /** - * Creates request options for stopRecording without sending the request - */ - async stopRecordingRequestOpts(requestParameters: StopRecordingRequest): Promise { - if (requestParameters['recordingId'] == null) { - throw new runtime.RequiredError( - 'recordingId', - 'Required parameter "recordingId" was null or undefined when calling stopRecording().' - ); - } - - const queryParameters: any = {}; - - const headerParameters: runtime.HTTPHeaders = {}; - - if (this.configuration && this.configuration.accessToken) { - const token = this.configuration.accessToken; - const tokenString = await token("management_token", []); - - if (tokenString) { - headerParameters["Authorization"] = `Bearer ${tokenString}`; - } - } - - let urlPath = `/recordings/{recording_id}/stop`; - urlPath = urlPath.replace('{recording_id}', encodeURIComponent(String(requestParameters['recordingId']))); - - return { - path: urlPath, - method: 'POST', - headers: headerParameters, - query: queryParameters, - }; + let urlPath = `/recordings`; + + return { + path: urlPath, + method: 'GET', + headers: headerParameters, + query: queryParameters, + }; + } + + /** + * List recordings for the tenant, optionally filtered by metadata and status. + * List recordings + */ + async listRecordingsRaw( + requestParameters: ListRecordingsRequest, + initOverrides?: RequestInit | runtime.InitOverrideFunction + ): Promise> { + const requestOptions = await this.listRecordingsRequestOpts(requestParameters); + const response = await this.request(requestOptions, initOverrides); + + return new runtime.JSONApiResponse(response, (jsonValue) => RecordingListResponseFromJSON(jsonValue)); + } + + /** + * List recordings for the tenant, optionally filtered by metadata and status. + * List recordings + */ + async listRecordings( + requestParameters: ListRecordingsRequest = {}, + initOverrides?: RequestInit | runtime.InitOverrideFunction + ): Promise { + const response = await this.listRecordingsRaw(requestParameters, initOverrides); + return await response.value(); + } + + /** + * Creates request options for stopRecording without sending the request + */ + async stopRecordingRequestOpts(requestParameters: StopRecordingRequest): Promise { + if (requestParameters['recordingId'] == null) { + throw new runtime.RequiredError( + 'recordingId', + 'Required parameter "recordingId" was null or undefined when calling stopRecording().' + ); } - /** - * Request the recorder to stop capturing. Finalization is asynchronous: the recording stays `active` until the capture is finalized, then becomes `finished`. Stopping a recording that is no longer active is a no-op. - * Stop a recording - */ - async stopRecordingRaw(requestParameters: StopRecordingRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise> { - const requestOptions = await this.stopRecordingRequestOpts(requestParameters); - const response = await this.request(requestOptions, initOverrides); + const queryParameters: any = {}; - return new runtime.JSONApiResponse(response, (jsonValue) => RecordingDetailsResponseFromJSON(jsonValue)); - } + const headerParameters: runtime.HTTPHeaders = {}; + + if (this.configuration && this.configuration.accessToken) { + const token = this.configuration.accessToken; + const tokenString = await token('management_token', []); - /** - * Request the recorder to stop capturing. Finalization is asynchronous: the recording stays `active` until the capture is finalized, then becomes `finished`. Stopping a recording that is no longer active is a no-op. - * Stop a recording - */ - async stopRecording(requestParameters: StopRecordingRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise { - const response = await this.stopRecordingRaw(requestParameters, initOverrides); - return await response.value(); + if (tokenString) { + headerParameters['Authorization'] = `Bearer ${tokenString}`; + } } + let urlPath = `/recordings/{recording_id}/stop`; + urlPath = urlPath.replace('{recording_id}', encodeURIComponent(String(requestParameters['recordingId']))); + + return { + path: urlPath, + method: 'POST', + headers: headerParameters, + query: queryParameters, + }; + } + + /** + * Request the recorder to stop capturing. Finalization is asynchronous: the recording stays `active` until the capture is finalized, then becomes `finished`. Stopping a recording that is no longer active is a no-op. + * Stop a recording + */ + async stopRecordingRaw( + requestParameters: StopRecordingRequest, + initOverrides?: RequestInit | runtime.InitOverrideFunction + ): Promise> { + const requestOptions = await this.stopRecordingRequestOpts(requestParameters); + const response = await this.request(requestOptions, initOverrides); + + return new runtime.JSONApiResponse(response, (jsonValue) => RecordingDetailsResponseFromJSON(jsonValue)); + } + + /** + * Request the recorder to stop capturing. Finalization is asynchronous: the recording stays `active` until the capture is finalized, then becomes `finished`. Stopping a recording that is no longer active is a no-op. + * Stop a recording + */ + async stopRecording( + requestParameters: StopRecordingRequest, + initOverrides?: RequestInit | runtime.InitOverrideFunction + ): Promise { + const response = await this.stopRecordingRaw(requestParameters, initOverrides); + return await response.value(); + } } diff --git a/packages/fishjam-openapi/src/generated/models/PeerOptionsVapi.ts b/packages/fishjam-openapi/src/generated/models/PeerOptionsVapi.ts index 28075f45..113387fd 100644 --- a/packages/fishjam-openapi/src/generated/models/PeerOptionsVapi.ts +++ b/packages/fishjam-openapi/src/generated/models/PeerOptionsVapi.ts @@ -2,7 +2,7 @@ /* eslint-disable */ /** * Fishjam API - * API for managing Fishjam real-time media resources. ## Authentication ## Credentials (Fishjam ID, Fishjam Management Token) can be obtained at https://fishjam.io/app. All requests require HTTP Bearer authorization using the Fishjam Management Token. ## Fishjam SDKs ## For TypeScript and Python users, we provide SDKs that simplify using this API. We recommend using them instead of manually consuming the API in these languages. You can learn more about our SDKs in our [SDK Docs](http://fishjam.swmansion.com/docs/how-to/backend/server-setup) + * API for managing Fishjam real-time media resources. ## Authentication ## Credentials (Fishjam ID, Fishjam Management Token) can be obtained at https://fishjam.io/app. All requests require HTTP Bearer authorization using the Fishjam Management Token. ## Fishjam SDKs ## For TypeScript and Python users, we provide SDKs that simplify using this API. We recommend using them instead of manually consuming the API in these languages. You can learn more about our SDKs in our [SDK Docs](http://fishjam.swmansion.com/docs/how-to/backend/server-setup) * * The version of the OpenAPI document: 0.30.0 * Contact: contact@fishjam.io @@ -15,10 +15,10 @@ import { mapValues } from '../runtime'; import type { SubscribeMode } from './SubscribeMode'; import { - SubscribeModeFromJSON, - SubscribeModeFromJSONTyped, - SubscribeModeToJSON, - SubscribeModeToJSONTyped, + SubscribeModeFromJSON, + SubscribeModeFromJSONTyped, + SubscribeModeToJSON, + SubscribeModeToJSONTyped, } from './SubscribeMode'; /** @@ -53,19 +53,17 @@ export interface PeerOptionsVapi { subscribeMode?: SubscribeMode; } - - /** * Check if a given object implements the PeerOptionsVapi interface. */ export function instanceOfPeerOptionsVapi(value: object): value is PeerOptionsVapi { - if (!('apiKey' in value) || value['apiKey'] === undefined) return false; - if (!('callId' in value) || value['callId'] === undefined) return false; - return true; + if (!('apiKey' in value) || value['apiKey'] === undefined) return false; + if (!('callId' in value) || value['callId'] === undefined) return false; + return true; } export function PeerOptionsVapiFromJSON(json: any): PeerOptionsVapi { - return PeerOptionsVapiFromJSONTyped(json, false); + return PeerOptionsVapiFromJSONTyped(json, false); } export function PeerOptionsVapiFromJSONTyped(json: any, ignoreDiscriminator: boolean): PeerOptionsVapi { @@ -82,13 +80,13 @@ export function PeerOptionsVapiFromJSONTyped(json: any, ignoreDiscriminator: boo } export function PeerOptionsVapiToJSON(json: any): PeerOptionsVapi { - return PeerOptionsVapiToJSONTyped(json, false); + return PeerOptionsVapiToJSONTyped(json, false); } export function PeerOptionsVapiToJSONTyped(value?: PeerOptionsVapi | null, ignoreDiscriminator: boolean = false): any { - if (value == null) { - return value; - } + if (value == null) { + return value; + } return { @@ -98,4 +96,3 @@ export function PeerOptionsVapiToJSONTyped(value?: PeerOptionsVapi | null, ignor 'subscribeMode': SubscribeModeToJSON(value['subscribeMode']), }; } - diff --git a/packages/fishjam-openapi/src/generated/models/RecordingSource.ts b/packages/fishjam-openapi/src/generated/models/RecordingSource.ts index 95889377..70e0dbd7 100644 --- a/packages/fishjam-openapi/src/generated/models/RecordingSource.ts +++ b/packages/fishjam-openapi/src/generated/models/RecordingSource.ts @@ -2,7 +2,7 @@ /* eslint-disable */ /** * Fishjam API - * API for managing Fishjam real-time media resources. ## Authentication ## Credentials (Fishjam ID, Fishjam Management Token) can be obtained at https://fishjam.io/app. All requests require HTTP Bearer authorization using the Fishjam Management Token. ## Fishjam SDKs ## For TypeScript and Python users, we provide SDKs that simplify using this API. We recommend using them instead of manually consuming the API in these languages. You can learn more about our SDKs in our [SDK Docs](http://fishjam.swmansion.com/docs/how-to/backend/server-setup) + * API for managing Fishjam real-time media resources. ## Authentication ## Credentials (Fishjam ID, Fishjam Management Token) can be obtained at https://fishjam.io/app. All requests require HTTP Bearer authorization using the Fishjam Management Token. ## Fishjam SDKs ## For TypeScript and Python users, we provide SDKs that simplify using this API. We recommend using them instead of manually consuming the API in these languages. You can learn more about our SDKs in our [SDK Docs](http://fishjam.swmansion.com/docs/how-to/backend/server-setup) * * The version of the OpenAPI document: 0.30.0 * Contact: contact@fishjam.io @@ -12,72 +12,64 @@ * Do not edit the class manually. */ -import { mapValues } from '../runtime'; +import type { CompositionSource } from './CompositionSource'; +import { + instanceOfCompositionSource, + CompositionSourceFromJSON, + CompositionSourceFromJSONTyped, + CompositionSourceToJSON, +} from './CompositionSource'; +import type { TemplateSource } from './TemplateSource'; +import { + instanceOfTemplateSource, + TemplateSourceFromJSON, + TemplateSourceFromJSONTyped, + TemplateSourceToJSON, +} from './TemplateSource'; + /** + * @type RecordingSource * The source for the recording * @export - * @interface RecordingSource - */ -export interface RecordingSource { - /** - * URL of the composition to record - * @type {string} - * @memberof RecordingSource - */ - compositionURL: string; - /** - * Id of the output being recorded - * @type {string} - * @memberof RecordingSource - */ - outputId: string; - /** - * Scale factor for the recording relative to the source output (>0; may upscale or downscale). Defaults to 1. - * @type {number} - * @memberof RecordingSource - */ - scaleRatio?: number; -} - -/** - * Check if a given object implements the RecordingSource interface. */ -export function instanceOfRecordingSource(value: object): value is RecordingSource { - if (!('compositionURL' in value) || value['compositionURL'] === undefined) return false; - if (!('outputId' in value) || value['outputId'] === undefined) return false; - return true; -} +export type RecordingSource = CompositionSource | TemplateSource; export function RecordingSourceFromJSON(json: any): RecordingSource { - return RecordingSourceFromJSONTyped(json, false); + return RecordingSourceFromJSONTyped(json, false); } export function RecordingSourceFromJSONTyped(json: any, ignoreDiscriminator: boolean): RecordingSource { - if (json == null) { - return json; - } - return { - - 'compositionURL': json['compositionURL'], - 'outputId': json['outputId'], - 'scaleRatio': json['scaleRatio'] == null ? undefined : json['scaleRatio'], - }; + if (json == null) { + return json; + } + if (typeof json !== 'object') { + return json; + } + if (instanceOfCompositionSource(json)) { + return CompositionSourceFromJSONTyped(json, true); + } + if (instanceOfTemplateSource(json)) { + return TemplateSourceFromJSONTyped(json, true); + } + return {} as any; } -export function RecordingSourceToJSON(json: any): RecordingSource { - return RecordingSourceToJSONTyped(json, false); +export function RecordingSourceToJSON(json: any): any { + return RecordingSourceToJSONTyped(json, false); } export function RecordingSourceToJSONTyped(value?: RecordingSource | null, ignoreDiscriminator: boolean = false): any { - if (value == null) { - return value; - } - - return { - - 'compositionURL': value['compositionURL'], - 'outputId': value['outputId'], - 'scaleRatio': value['scaleRatio'], - }; + if (value == null) { + return value; + } + if (typeof value !== 'object') { + return value; + } + if (instanceOfCompositionSource(value)) { + return CompositionSourceToJSON(value as CompositionSource); + } + if (instanceOfTemplateSource(value)) { + return TemplateSourceToJSON(value as TemplateSource); + } + return {}; } - diff --git a/packages/fishjam-openapi/src/generated/models/TemplateSource.ts b/packages/fishjam-openapi/src/generated/models/TemplateSource.ts new file mode 100644 index 00000000..241161df --- /dev/null +++ b/packages/fishjam-openapi/src/generated/models/TemplateSource.ts @@ -0,0 +1,87 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Fishjam API + * API for managing Fishjam real-time media resources. ## Authentication ## Credentials (Fishjam ID, Fishjam Management Token) can be obtained at https://fishjam.io/app. All requests require HTTP Bearer authorization using the Fishjam Management Token. ## Fishjam SDKs ## For TypeScript and Python users, we provide SDKs that simplify using this API. We recommend using them instead of manually consuming the API in these languages. You can learn more about our SDKs in our [SDK Docs](http://fishjam.swmansion.com/docs/how-to/backend/server-setup) + * + * The version of the OpenAPI document: 0.30.0 + * Contact: contact@fishjam.io + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +import { mapValues } from '../runtime'; +import type { TemplateSourceResolution } from './TemplateSourceResolution'; +import { + TemplateSourceResolutionFromJSON, + TemplateSourceResolutionFromJSONTyped, + TemplateSourceResolutionToJSON, + TemplateSourceResolutionToJSONTyped, +} from './TemplateSourceResolution'; + +/** + * Recording source that renders its own scene from a template, rather than cloning an existing output of the composition + * @export + * @interface TemplateSource + */ +export interface TemplateSource { + /** + * Whether the recording captures audio. Defaults to true. + * @type {boolean} + * @memberof TemplateSource + */ + audio?: boolean; + /** + * URL of the composition to record + * @type {string} + * @memberof TemplateSource + */ + compositionURL: string; + /** + * + * @type {TemplateSourceResolution} + * @memberof TemplateSource + */ + resolution?: TemplateSourceResolution; +} + +/** + * Check if a given object implements the TemplateSource interface. + */ +export function instanceOfTemplateSource(value: object): value is TemplateSource { + if (!('compositionURL' in value) || value['compositionURL'] === undefined) return false; + return true; +} + +export function TemplateSourceFromJSON(json: any): TemplateSource { + return TemplateSourceFromJSONTyped(json, false); +} + +export function TemplateSourceFromJSONTyped(json: any, ignoreDiscriminator: boolean): TemplateSource { + if (json == null) { + return json; + } + return { + audio: json['audio'] == null ? undefined : json['audio'], + compositionURL: json['compositionURL'], + resolution: json['resolution'] == null ? undefined : TemplateSourceResolutionFromJSON(json['resolution']), + }; +} + +export function TemplateSourceToJSON(json: any): TemplateSource { + return TemplateSourceToJSONTyped(json, false); +} + +export function TemplateSourceToJSONTyped(value?: TemplateSource | null, ignoreDiscriminator: boolean = false): any { + if (value == null) { + return value; + } + + return { + audio: value['audio'], + compositionURL: value['compositionURL'], + resolution: TemplateSourceResolutionToJSON(value['resolution']), + }; +} diff --git a/packages/fishjam-openapi/src/generated/models/TemplateSourceResolution.ts b/packages/fishjam-openapi/src/generated/models/TemplateSourceResolution.ts new file mode 100644 index 00000000..ae76661b --- /dev/null +++ b/packages/fishjam-openapi/src/generated/models/TemplateSourceResolution.ts @@ -0,0 +1,78 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Fishjam API + * API for managing Fishjam real-time media resources. ## Authentication ## Credentials (Fishjam ID, Fishjam Management Token) can be obtained at https://fishjam.io/app. All requests require HTTP Bearer authorization using the Fishjam Management Token. ## Fishjam SDKs ## For TypeScript and Python users, we provide SDKs that simplify using this API. We recommend using them instead of manually consuming the API in these languages. You can learn more about our SDKs in our [SDK Docs](http://fishjam.swmansion.com/docs/how-to/backend/server-setup) + * + * The version of the OpenAPI document: 0.30.0 + * Contact: contact@fishjam.io + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +import { mapValues } from '../runtime'; +/** + * Resolution the scene is rendered at. Defaults to 1280x720. + * @export + * @interface TemplateSourceResolution + */ +export interface TemplateSourceResolution { + /** + * + * @type {number} + * @memberof TemplateSourceResolution + */ + height: number; + /** + * + * @type {number} + * @memberof TemplateSourceResolution + */ + width: number; +} + +/** + * Check if a given object implements the TemplateSourceResolution interface. + */ +export function instanceOfTemplateSourceResolution(value: object): value is TemplateSourceResolution { + if (!('height' in value) || value['height'] === undefined) return false; + if (!('width' in value) || value['width'] === undefined) return false; + return true; +} + +export function TemplateSourceResolutionFromJSON(json: any): TemplateSourceResolution { + return TemplateSourceResolutionFromJSONTyped(json, false); +} + +export function TemplateSourceResolutionFromJSONTyped( + json: any, + ignoreDiscriminator: boolean +): TemplateSourceResolution { + if (json == null) { + return json; + } + return { + height: json['height'], + width: json['width'], + }; +} + +export function TemplateSourceResolutionToJSON(json: any): TemplateSourceResolution { + return TemplateSourceResolutionToJSONTyped(json, false); +} + +export function TemplateSourceResolutionToJSONTyped( + value?: TemplateSourceResolution | null, + ignoreDiscriminator: boolean = false +): any { + if (value == null) { + return value; + } + + return { + height: value['height'], + width: value['width'], + }; +} diff --git a/packages/fishjam-openapi/src/generated/models/index.ts b/packages/fishjam-openapi/src/generated/models/index.ts index 52c434ce..b21f2e0d 100644 --- a/packages/fishjam-openapi/src/generated/models/index.ts +++ b/packages/fishjam-openapi/src/generated/models/index.ts @@ -46,6 +46,8 @@ export * from './StreamsListingResponse'; export * from './SubscribeMode'; export * from './SubscribeTracksRequest'; export * from './Subscriptions'; +export * from './TemplateSource'; +export * from './TemplateSourceResolution'; export * from './Track'; export * from './TrackForwarding'; export * from './TrackForwardingInfo'; diff --git a/packages/fishjam-proto/protos b/packages/fishjam-proto/protos index da96ef19..5f84ff1e 160000 --- a/packages/fishjam-proto/protos +++ b/packages/fishjam-proto/protos @@ -1 +1 @@ -Subproject commit da96ef19f67cefd9ebb818d5f9b8c4c5bece74fa +Subproject commit 5f84ff1ebcada392e43fb766dde0622e469076fd diff --git a/packages/js-server-sdk/src/client.ts b/packages/js-server-sdk/src/client.ts index 68ca7258..ebd988b5 100644 --- a/packages/js-server-sdk/src/client.ts +++ b/packages/js-server-sdk/src/client.ts @@ -12,12 +12,22 @@ import { PeerOptionsAgent, MoqAccessConfig, type Middleware, - RecordingsApi, RecordingConfig, } from '@fishjam-cloud/fishjam-openapi'; -import type { AgentCallbacks, FishjamConfig, PeerId, Recording, RecordingId, Room, RoomId, Peer } from './types'; +import type { + AgentCallbacks, + FishjamConfig, + PeerId, + Recording, + RecordingId, + Room, + RoomId, + Peer, + TemplateRecordingConfig, +} from './types'; import { mapException } from './exceptions/mapper'; -import { getFishjamUrl, getLivestreamWhipUrl } from './utils'; +import { RecordingsApiWithTemplates } from './recordings'; +import { getFishjamUrl, getLivestreamWhipUrl, toBlob } from './utils'; import { FishjamAgent, TrackId } from './agent'; import packageJson from '../package.json'; @@ -32,7 +42,7 @@ export class FishjamClient { private readonly viewerApi: ViewersApi; private readonly streamerApi: StreamersApi; private readonly credentialsApi: CredentialsApi; - private readonly recordingsApi: RecordingsApi; + private readonly recordingsApi: RecordingsApiWithTemplates; private readonly trackForwardingsApi: TrackForwardingsApi; private readonly fishjamConfig: FishjamConfig; private deprecationWarningShown: boolean = false; @@ -74,7 +84,7 @@ export class FishjamClient { this.viewerApi = new ViewersApi(apiConfig); this.streamerApi = new StreamersApi(apiConfig); this.credentialsApi = new CredentialsApi(apiConfig); - this.recordingsApi = new RecordingsApi(apiConfig); + this.recordingsApi = new RecordingsApiWithTemplates(apiConfig); this.trackForwardingsApi = new TrackForwardingsApi(apiConfig); this.fishjamConfig = config; } @@ -359,6 +369,22 @@ export class FishjamClient { } } + /** + * Start a new recording that renders its own scene from a template. + * + * The template bundle is either a `Blob` or a path to read it from, and can weigh at most 1 MiB; + * Only valid template bundles are accepted. + * + */ + async createTemplateRecording(config: TemplateRecordingConfig, template: Blob | string): Promise { + try { + const { data } = await this.recordingsApi.createTemplateRecording(config, await toBlob(template)); + return data as Recording; + } catch (error) { + throw await mapException(error, 'recording'); + } + } + /** * Get details about a given recording. */ diff --git a/packages/js-server-sdk/src/index.ts b/packages/js-server-sdk/src/index.ts index 37473800..c43438d7 100644 --- a/packages/js-server-sdk/src/index.ts +++ b/packages/js-server-sdk/src/index.ts @@ -25,6 +25,8 @@ export { MoqAccessConfig, RecordingConfig, RecordingSource, + CompositionSource, + TemplateSource, } from '@fishjam-cloud/fishjam-openapi'; export type { diff --git a/packages/js-server-sdk/src/recordings.ts b/packages/js-server-sdk/src/recordings.ts new file mode 100644 index 00000000..0766584f --- /dev/null +++ b/packages/js-server-sdk/src/recordings.ts @@ -0,0 +1,23 @@ +import { + RecordingsApi, + RecordingConfigToJSON, + RecordingDetailsResponseFromJSON, + type RecordingConfig, + type RecordingDetailsResponse, +} from '@fishjam-cloud/fishjam-openapi'; + +/** + * Adds the one recording call the generated client does not cover: creating a recording from a + * template bundle, which is sent as `multipart/form-data` with the config and the bundle as parts. + */ +export class RecordingsApiWithTemplates extends RecordingsApi { + async createTemplateRecording(config: RecordingConfig, template: Blob): Promise { + const body = new FormData(); + body.append('config', new Blob([JSON.stringify(RecordingConfigToJSON(config))], { type: 'application/json' })); + body.append('template', template); + + const response = await this.request({ path: '/recordings', method: 'POST', headers: {}, body }); + + return RecordingDetailsResponseFromJSON(await response.json()); + } +} diff --git a/packages/js-server-sdk/src/types.ts b/packages/js-server-sdk/src/types.ts index 9a181127..79e0845d 100644 --- a/packages/js-server-sdk/src/types.ts +++ b/packages/js-server-sdk/src/types.ts @@ -3,7 +3,9 @@ import { Peer as OpenApiPeer, PeerType as OpenApiPeerType, Recording as OpenApiRecording, + RecordingConfig, RoomConfig, + TemplateSource, TrackType as OpenApiTrackType, } from '@fishjam-cloud/fishjam-openapi'; @@ -75,6 +77,12 @@ export type RecordingStatus = 'active' | 'finished' | 'available' | 'failed'; */ export type Recording = Override; +/** + * Configuration of a recording rendering its own scene from a template bundle, + * as started with {@link FishjamClient.createTemplateRecording}. + */ +export type TemplateRecordingConfig = Override; + /** * ID of a composition. * Composition can be created with {@link CompositionClient.createComposition}. diff --git a/packages/js-server-sdk/tests/fixtures/template.js b/packages/js-server-sdk/tests/fixtures/template.js new file mode 100644 index 00000000..461f67a0 --- /dev/null +++ b/packages/js-server-sdk/tests/fixtures/template.js @@ -0,0 +1 @@ +export default () => null; diff --git a/packages/js-server-sdk/tests/recordings.test.ts b/packages/js-server-sdk/tests/recordings.test.ts new file mode 100644 index 00000000..5004ae56 --- /dev/null +++ b/packages/js-server-sdk/tests/recordings.test.ts @@ -0,0 +1,90 @@ +import { afterEach, describe, expect, it, vi } from 'vitest'; +import { FishjamClient } from '../src/client'; +import { BadRequestException } from '../src/exceptions'; +import type { RecordingId } from '../src/types'; + +const COMPOSITION_URL = 'https://rtc.fishjam.test/api/composition/comp-1'; + +const client = () => new FishjamClient({ fishjamId: 'https://fishjam.test/api/v1/connect/x', managementToken: 'tok' }); + +const templateRecording = { + id: 'rec-1', + status: 'active', + files: [], + source: { compositionURL: COMPOSITION_URL, resolution: { width: 1920, height: 1080 }, audio: false }, +}; + +const stubFetch = (body: unknown, status = 201) => { + const fetch = vi + .fn() + .mockResolvedValue(new Response(JSON.stringify(body), { status, headers: { 'content-type': 'application/json' } })); + vi.stubGlobal('fetch', fetch); + return fetch; +}; + +afterEach(() => { + vi.unstubAllGlobals(); +}); + +describe('createTemplateRecording', () => { + it('uploads the bundle alongside the recording config', async () => { + const fetch = stubFetch({ data: templateRecording }); + + const recording = await client().createTemplateRecording( + { source: { compositionURL: COMPOSITION_URL, resolution: { width: 1920, height: 1080 }, audio: false } }, + new Blob(['bundle']) + ); + + const [url, init] = fetch.mock.calls[0]; + expect(String(url)).toMatch(/\/recordings$/); + expect(init.method).toBe('POST'); + expect(init.headers['Authorization']).toBe('Bearer tok'); + expect(init.headers['Content-Type']).toBeUndefined(); + + const form = init.body as FormData; + expect(await (form.get('template') as Blob).text()).toBe('bundle'); + expect(JSON.parse(await (form.get('config') as Blob).text())).toEqual({ + source: { compositionURL: COMPOSITION_URL, resolution: { width: 1920, height: 1080 }, audio: false }, + }); + + expect(recording.id).toBe('rec-1'); + expect(recording.status).toBe('active'); + }); + + it('reads the bundle from a path', async () => { + const fetch = stubFetch({ data: templateRecording }); + + await client().createTemplateRecording( + { source: { compositionURL: COMPOSITION_URL } }, + `${import.meta.dirname}/fixtures/template.js` + ); + + const form = fetch.mock.calls[0][1].body as FormData; + expect(await (form.get('template') as Blob).text()).toBe('export default () => null;\n'); + expect(JSON.parse(await (form.get('config') as Blob).text())).toEqual({ + source: { compositionURL: COMPOSITION_URL }, + }); + }); + + it('throws BadRequestException when the server rejects the bundle', async () => { + stubFetch({ errors: 'the template has no default export' }, 400); + + await expect( + client().createTemplateRecording({ source: { compositionURL: COMPOSITION_URL } }, new Blob(['bundle'])) + ).rejects.toBeInstanceOf(BadRequestException); + }); +}); + +describe('reading a template recording', () => { + it('parses the source the server echoes back', async () => { + stubFetch({ data: templateRecording }, 200); + + const recording = await client().getRecording('rec-1' as RecordingId); + + expect(recording.source).toEqual({ + compositionURL: COMPOSITION_URL, + resolution: { width: 1920, height: 1080 }, + audio: false, + }); + }); +}); From 986e68e9cfc351c30ab42c9dee8969ebfeae4a87 Mon Sep 17 00:00:00 2001 From: Adrian Czerwiec Date: Tue, 22 Sep 2026 13:42:48 +0200 Subject: [PATCH 2/4] simplify jsdoc --- packages/js-server-sdk/src/types.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/js-server-sdk/src/types.ts b/packages/js-server-sdk/src/types.ts index 79e0845d..5cd29719 100644 --- a/packages/js-server-sdk/src/types.ts +++ b/packages/js-server-sdk/src/types.ts @@ -78,8 +78,7 @@ export type RecordingStatus = 'active' | 'finished' | 'available' | 'failed'; export type Recording = Override; /** - * Configuration of a recording rendering its own scene from a template bundle, - * as started with {@link FishjamClient.createTemplateRecording}. + * Configuration of a recording that renders its own scene from a template. */ export type TemplateRecordingConfig = Override; From b5f33202c7ba4b2809c64c46e23b83c9cbcc4063 Mon Sep 17 00:00:00 2001 From: Adrian Czerwiec Date: Thu, 24 Sep 2026 09:21:55 +0200 Subject: [PATCH 3/4] format code --- .prettierignore | 3 +- .../src/generated/apis/RecordingsApi.ts | 564 +++++++++--------- .../src/generated/models/PeerOptionsVapi.ts | 29 +- .../src/generated/models/RecordingSource.ts | 75 +-- .../src/generated/models/TemplateSource.ts | 89 +-- .../models/TemplateSourceResolution.ts | 77 ++- 6 files changed, 411 insertions(+), 426 deletions(-) diff --git a/.prettierignore b/.prettierignore index ba55e859..246cfe00 100644 --- a/.prettierignore +++ b/.prettierignore @@ -5,4 +5,5 @@ protos/ src/openapi/ README.md tsconfig.json -docker-compose-dev.yaml \ No newline at end of file +docker-compose-dev.yaml +packages/fishjam-openapi/src/generated/ diff --git a/packages/fishjam-openapi/src/generated/apis/RecordingsApi.ts b/packages/fishjam-openapi/src/generated/apis/RecordingsApi.ts index 500f6a27..eaa67b54 100644 --- a/packages/fishjam-openapi/src/generated/apis/RecordingsApi.ts +++ b/packages/fishjam-openapi/src/generated/apis/RecordingsApi.ts @@ -2,7 +2,7 @@ /* eslint-disable */ /** * Fishjam API - * API for managing Fishjam real-time media resources. ## Authentication ## Credentials (Fishjam ID, Fishjam Management Token) can be obtained at https://fishjam.io/app. All requests require HTTP Bearer authorization using the Fishjam Management Token. ## Fishjam SDKs ## For TypeScript and Python users, we provide SDKs that simplify using this API. We recommend using them instead of manually consuming the API in these languages. You can learn more about our SDKs in our [SDK Docs](http://fishjam.swmansion.com/docs/how-to/backend/server-setup) + * API for managing Fishjam real-time media resources. ## Authentication ## Credentials (Fishjam ID, Fishjam Management Token) can be obtained at https://fishjam.io/app. All requests require HTTP Bearer authorization using the Fishjam Management Token. ## Fishjam SDKs ## For TypeScript and Python users, we provide SDKs that simplify using this API. We recommend using them instead of manually consuming the API in these languages. You can learn more about our SDKs in our [SDK Docs](http://fishjam.swmansion.com/docs/how-to/backend/server-setup) * * The version of the OpenAPI document: 0.30.0 * Contact: contact@fishjam.io @@ -13,349 +13,329 @@ */ import * as runtime from '../runtime'; -import { type RecordingConfig, RecordingConfigFromJSON, RecordingConfigToJSON } from '../models/RecordingConfig'; import { - type RecordingDetailsResponse, - RecordingDetailsResponseFromJSON, - RecordingDetailsResponseToJSON, + type RecordingConfig, + RecordingConfigFromJSON, + RecordingConfigToJSON, +} from '../models/RecordingConfig'; +import { + type RecordingDetailsResponse, + RecordingDetailsResponseFromJSON, + RecordingDetailsResponseToJSON, } from '../models/RecordingDetailsResponse'; import { - type RecordingListResponse, - RecordingListResponseFromJSON, - RecordingListResponseToJSON, + type RecordingListResponse, + RecordingListResponseFromJSON, + RecordingListResponseToJSON, } from '../models/RecordingListResponse'; -import { type RecordingStatus, RecordingStatusFromJSON, RecordingStatusToJSON } from '../models/RecordingStatus'; +import { + type RecordingStatus, + RecordingStatusFromJSON, + RecordingStatusToJSON, +} from '../models/RecordingStatus'; export interface CreateRecordingOperationRequest { - recordingConfig: RecordingConfig; + recordingConfig: RecordingConfig; } export interface DeleteRecordingRequest { - recordingId: string; + recordingId: string; } export interface GetRecordingRequest { - recordingId: string; + recordingId: string; } export interface ListRecordingsRequest { - metadata?: { [key: string]: any }; - status?: RecordingStatus; + metadata?: { [key: string]: any; }; + status?: RecordingStatus; } export interface StopRecordingRequest { - recordingId: string; + recordingId: string; } /** - * + * */ export class RecordingsApi extends runtime.BaseAPI { - /** - * Creates request options for createRecording without sending the request - */ - async createRecordingRequestOpts(requestParameters: CreateRecordingOperationRequest): Promise { - if (requestParameters['recordingConfig'] == null) { - throw new runtime.RequiredError( - 'recordingConfig', - 'Required parameter "recordingConfig" was null or undefined when calling createRecording().' - ); - } - const queryParameters: any = {}; - - const headerParameters: runtime.HTTPHeaders = {}; - - headerParameters['Content-Type'] = 'application/json'; + /** + * Creates request options for createRecording without sending the request + */ + async createRecordingRequestOpts(requestParameters: CreateRecordingOperationRequest): Promise { + if (requestParameters['recordingConfig'] == null) { + throw new runtime.RequiredError( + 'recordingConfig', + 'Required parameter "recordingConfig" was null or undefined when calling createRecording().' + ); + } + + const queryParameters: any = {}; + + const headerParameters: runtime.HTTPHeaders = {}; + + headerParameters['Content-Type'] = 'application/json'; + + if (this.configuration && this.configuration.accessToken) { + const token = this.configuration.accessToken; + const tokenString = await token("management_token", []); + + if (tokenString) { + headerParameters["Authorization"] = `Bearer ${tokenString}`; + } + } + + let urlPath = `/recordings`; + + return { + path: urlPath, + method: 'POST', + headers: headerParameters, + query: queryParameters, + body: RecordingConfigToJSON(requestParameters['recordingConfig']), + }; + } - if (this.configuration && this.configuration.accessToken) { - const token = this.configuration.accessToken; - const tokenString = await token('management_token', []); + /** + * Create a recording resource. Capturing starts synchronously, so it is returned with status `active`. A source naming an `outputId` records an output the composition already has and is sent as JSON. A template source renders its own scene and has to be sent as `multipart/form-data`: the configuration in a `config` part, the React bundle in a `template` part. + * Create a recording + */ + async createRecordingRaw(requestParameters: CreateRecordingOperationRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise> { + const requestOptions = await this.createRecordingRequestOpts(requestParameters); + const response = await this.request(requestOptions, initOverrides); - if (tokenString) { - headerParameters['Authorization'] = `Bearer ${tokenString}`; - } + return new runtime.JSONApiResponse(response, (jsonValue) => RecordingDetailsResponseFromJSON(jsonValue)); } - let urlPath = `/recordings`; - - return { - path: urlPath, - method: 'POST', - headers: headerParameters, - query: queryParameters, - body: RecordingConfigToJSON(requestParameters['recordingConfig']), - }; - } - - /** - * Create a recording resource. Capturing starts synchronously, so it is returned with status `active`. A source naming an `outputId` records an output the composition already has and is sent as JSON. A template source renders its own scene and has to be sent as `multipart/form-data`: the configuration in a `config` part, the React bundle in a `template` part. - * Create a recording - */ - async createRecordingRaw( - requestParameters: CreateRecordingOperationRequest, - initOverrides?: RequestInit | runtime.InitOverrideFunction - ): Promise> { - const requestOptions = await this.createRecordingRequestOpts(requestParameters); - const response = await this.request(requestOptions, initOverrides); - - return new runtime.JSONApiResponse(response, (jsonValue) => RecordingDetailsResponseFromJSON(jsonValue)); - } - - /** - * Create a recording resource. Capturing starts synchronously, so it is returned with status `active`. A source naming an `outputId` records an output the composition already has and is sent as JSON. A template source renders its own scene and has to be sent as `multipart/form-data`: the configuration in a `config` part, the React bundle in a `template` part. - * Create a recording - */ - async createRecording( - requestParameters: CreateRecordingOperationRequest, - initOverrides?: RequestInit | runtime.InitOverrideFunction - ): Promise { - const response = await this.createRecordingRaw(requestParameters, initOverrides); - return await response.value(); - } - - /** - * Creates request options for deleteRecording without sending the request - */ - async deleteRecordingRequestOpts(requestParameters: DeleteRecordingRequest): Promise { - if (requestParameters['recordingId'] == null) { - throw new runtime.RequiredError( - 'recordingId', - 'Required parameter "recordingId" was null or undefined when calling deleteRecording().' - ); + /** + * Create a recording resource. Capturing starts synchronously, so it is returned with status `active`. A source naming an `outputId` records an output the composition already has and is sent as JSON. A template source renders its own scene and has to be sent as `multipart/form-data`: the configuration in a `config` part, the React bundle in a `template` part. + * Create a recording + */ + async createRecording(requestParameters: CreateRecordingOperationRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise { + const response = await this.createRecordingRaw(requestParameters, initOverrides); + return await response.value(); } - const queryParameters: any = {}; - - const headerParameters: runtime.HTTPHeaders = {}; + /** + * Creates request options for deleteRecording without sending the request + */ + async deleteRecordingRequestOpts(requestParameters: DeleteRecordingRequest): Promise { + if (requestParameters['recordingId'] == null) { + throw new runtime.RequiredError( + 'recordingId', + 'Required parameter "recordingId" was null or undefined when calling deleteRecording().' + ); + } + + const queryParameters: any = {}; + + const headerParameters: runtime.HTTPHeaders = {}; + + if (this.configuration && this.configuration.accessToken) { + const token = this.configuration.accessToken; + const tokenString = await token("management_token", []); + + if (tokenString) { + headerParameters["Authorization"] = `Bearer ${tokenString}`; + } + } + + let urlPath = `/recordings/{recording_id}`; + urlPath = urlPath.replace('{recording_id}', encodeURIComponent(String(requestParameters['recordingId']))); + + return { + path: urlPath, + method: 'DELETE', + headers: headerParameters, + query: queryParameters, + }; + } - if (this.configuration && this.configuration.accessToken) { - const token = this.configuration.accessToken; - const tokenString = await token('management_token', []); + /** + * Delete a recording by id. The recording disappears from the API immediately; its stored media is removed asynchronously by a background job. A recording that is still `active` cannot be deleted — wait for it to finish or fail. + * Delete a recording + */ + async deleteRecordingRaw(requestParameters: DeleteRecordingRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise> { + const requestOptions = await this.deleteRecordingRequestOpts(requestParameters); + const response = await this.request(requestOptions, initOverrides); - if (tokenString) { - headerParameters['Authorization'] = `Bearer ${tokenString}`; - } + return new runtime.VoidApiResponse(response); } - let urlPath = `/recordings/{recording_id}`; - urlPath = urlPath.replace('{recording_id}', encodeURIComponent(String(requestParameters['recordingId']))); - - return { - path: urlPath, - method: 'DELETE', - headers: headerParameters, - query: queryParameters, - }; - } - - /** - * Delete a recording by id. The recording disappears from the API immediately; its stored media is removed asynchronously by a background job. A recording that is still `active` cannot be deleted — wait for it to finish or fail. - * Delete a recording - */ - async deleteRecordingRaw( - requestParameters: DeleteRecordingRequest, - initOverrides?: RequestInit | runtime.InitOverrideFunction - ): Promise> { - const requestOptions = await this.deleteRecordingRequestOpts(requestParameters); - const response = await this.request(requestOptions, initOverrides); - - return new runtime.VoidApiResponse(response); - } - - /** - * Delete a recording by id. The recording disappears from the API immediately; its stored media is removed asynchronously by a background job. A recording that is still `active` cannot be deleted — wait for it to finish or fail. - * Delete a recording - */ - async deleteRecording( - requestParameters: DeleteRecordingRequest, - initOverrides?: RequestInit | runtime.InitOverrideFunction - ): Promise { - await this.deleteRecordingRaw(requestParameters, initOverrides); - } - - /** - * Creates request options for getRecording without sending the request - */ - async getRecordingRequestOpts(requestParameters: GetRecordingRequest): Promise { - if (requestParameters['recordingId'] == null) { - throw new runtime.RequiredError( - 'recordingId', - 'Required parameter "recordingId" was null or undefined when calling getRecording().' - ); + /** + * Delete a recording by id. The recording disappears from the API immediately; its stored media is removed asynchronously by a background job. A recording that is still `active` cannot be deleted — wait for it to finish or fail. + * Delete a recording + */ + async deleteRecording(requestParameters: DeleteRecordingRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise { + await this.deleteRecordingRaw(requestParameters, initOverrides); } - const queryParameters: any = {}; - - const headerParameters: runtime.HTTPHeaders = {}; + /** + * Creates request options for getRecording without sending the request + */ + async getRecordingRequestOpts(requestParameters: GetRecordingRequest): Promise { + if (requestParameters['recordingId'] == null) { + throw new runtime.RequiredError( + 'recordingId', + 'Required parameter "recordingId" was null or undefined when calling getRecording().' + ); + } + + const queryParameters: any = {}; + + const headerParameters: runtime.HTTPHeaders = {}; + + if (this.configuration && this.configuration.accessToken) { + const token = this.configuration.accessToken; + const tokenString = await token("management_token", []); + + if (tokenString) { + headerParameters["Authorization"] = `Bearer ${tokenString}`; + } + } + + let urlPath = `/recordings/{recording_id}`; + urlPath = urlPath.replace('{recording_id}', encodeURIComponent(String(requestParameters['recordingId']))); + + return { + path: urlPath, + method: 'GET', + headers: headerParameters, + query: queryParameters, + }; + } - if (this.configuration && this.configuration.accessToken) { - const token = this.configuration.accessToken; - const tokenString = await token('management_token', []); + /** + * Get a recording by id. + * Get a recording + */ + async getRecordingRaw(requestParameters: GetRecordingRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise> { + const requestOptions = await this.getRecordingRequestOpts(requestParameters); + const response = await this.request(requestOptions, initOverrides); - if (tokenString) { - headerParameters['Authorization'] = `Bearer ${tokenString}`; - } + return new runtime.JSONApiResponse(response, (jsonValue) => RecordingDetailsResponseFromJSON(jsonValue)); } - let urlPath = `/recordings/{recording_id}`; - urlPath = urlPath.replace('{recording_id}', encodeURIComponent(String(requestParameters['recordingId']))); - - return { - path: urlPath, - method: 'GET', - headers: headerParameters, - query: queryParameters, - }; - } - - /** - * Get a recording by id. - * Get a recording - */ - async getRecordingRaw( - requestParameters: GetRecordingRequest, - initOverrides?: RequestInit | runtime.InitOverrideFunction - ): Promise> { - const requestOptions = await this.getRecordingRequestOpts(requestParameters); - const response = await this.request(requestOptions, initOverrides); - - return new runtime.JSONApiResponse(response, (jsonValue) => RecordingDetailsResponseFromJSON(jsonValue)); - } - - /** - * Get a recording by id. - * Get a recording - */ - async getRecording( - requestParameters: GetRecordingRequest, - initOverrides?: RequestInit | runtime.InitOverrideFunction - ): Promise { - const response = await this.getRecordingRaw(requestParameters, initOverrides); - return await response.value(); - } - - /** - * Creates request options for listRecordings without sending the request - */ - async listRecordingsRequestOpts(requestParameters: ListRecordingsRequest): Promise { - const queryParameters: any = {}; - - if (requestParameters['metadata'] != null) { - for (let key of Object.keys(requestParameters['metadata'])) { - queryParameters[key] = requestParameters['metadata'][key]; - } + /** + * Get a recording by id. + * Get a recording + */ + async getRecording(requestParameters: GetRecordingRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise { + const response = await this.getRecordingRaw(requestParameters, initOverrides); + return await response.value(); } - if (requestParameters['status'] != null) { - queryParameters['status'] = requestParameters['status']; + /** + * Creates request options for listRecordings without sending the request + */ + async listRecordingsRequestOpts(requestParameters: ListRecordingsRequest): Promise { + const queryParameters: any = {}; + + if (requestParameters['metadata'] != null) { + for (let key of Object.keys(requestParameters['metadata'])) { + queryParameters[key] = requestParameters['metadata'][key]; + } + } + + if (requestParameters['status'] != null) { + queryParameters['status'] = requestParameters['status']; + } + + const headerParameters: runtime.HTTPHeaders = {}; + + if (this.configuration && this.configuration.accessToken) { + const token = this.configuration.accessToken; + const tokenString = await token("management_token", []); + + if (tokenString) { + headerParameters["Authorization"] = `Bearer ${tokenString}`; + } + } + + let urlPath = `/recordings`; + + return { + path: urlPath, + method: 'GET', + headers: headerParameters, + query: queryParameters, + }; } - const headerParameters: runtime.HTTPHeaders = {}; + /** + * List recordings for the tenant, optionally filtered by metadata and status. + * List recordings + */ + async listRecordingsRaw(requestParameters: ListRecordingsRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise> { + const requestOptions = await this.listRecordingsRequestOpts(requestParameters); + const response = await this.request(requestOptions, initOverrides); - if (this.configuration && this.configuration.accessToken) { - const token = this.configuration.accessToken; - const tokenString = await token('management_token', []); - - if (tokenString) { - headerParameters['Authorization'] = `Bearer ${tokenString}`; - } + return new runtime.JSONApiResponse(response, (jsonValue) => RecordingListResponseFromJSON(jsonValue)); } - let urlPath = `/recordings`; - - return { - path: urlPath, - method: 'GET', - headers: headerParameters, - query: queryParameters, - }; - } - - /** - * List recordings for the tenant, optionally filtered by metadata and status. - * List recordings - */ - async listRecordingsRaw( - requestParameters: ListRecordingsRequest, - initOverrides?: RequestInit | runtime.InitOverrideFunction - ): Promise> { - const requestOptions = await this.listRecordingsRequestOpts(requestParameters); - const response = await this.request(requestOptions, initOverrides); - - return new runtime.JSONApiResponse(response, (jsonValue) => RecordingListResponseFromJSON(jsonValue)); - } - - /** - * List recordings for the tenant, optionally filtered by metadata and status. - * List recordings - */ - async listRecordings( - requestParameters: ListRecordingsRequest = {}, - initOverrides?: RequestInit | runtime.InitOverrideFunction - ): Promise { - const response = await this.listRecordingsRaw(requestParameters, initOverrides); - return await response.value(); - } - - /** - * Creates request options for stopRecording without sending the request - */ - async stopRecordingRequestOpts(requestParameters: StopRecordingRequest): Promise { - if (requestParameters['recordingId'] == null) { - throw new runtime.RequiredError( - 'recordingId', - 'Required parameter "recordingId" was null or undefined when calling stopRecording().' - ); + /** + * List recordings for the tenant, optionally filtered by metadata and status. + * List recordings + */ + async listRecordings(requestParameters: ListRecordingsRequest = {}, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise { + const response = await this.listRecordingsRaw(requestParameters, initOverrides); + return await response.value(); } - const queryParameters: any = {}; + /** + * Creates request options for stopRecording without sending the request + */ + async stopRecordingRequestOpts(requestParameters: StopRecordingRequest): Promise { + if (requestParameters['recordingId'] == null) { + throw new runtime.RequiredError( + 'recordingId', + 'Required parameter "recordingId" was null or undefined when calling stopRecording().' + ); + } + + const queryParameters: any = {}; + + const headerParameters: runtime.HTTPHeaders = {}; + + if (this.configuration && this.configuration.accessToken) { + const token = this.configuration.accessToken; + const tokenString = await token("management_token", []); + + if (tokenString) { + headerParameters["Authorization"] = `Bearer ${tokenString}`; + } + } + + let urlPath = `/recordings/{recording_id}/stop`; + urlPath = urlPath.replace('{recording_id}', encodeURIComponent(String(requestParameters['recordingId']))); + + return { + path: urlPath, + method: 'POST', + headers: headerParameters, + query: queryParameters, + }; + } - const headerParameters: runtime.HTTPHeaders = {}; + /** + * Request the recorder to stop capturing. Finalization is asynchronous: the recording stays `active` until the capture is finalized, then becomes `finished`. Stopping a recording that is no longer active is a no-op. + * Stop a recording + */ + async stopRecordingRaw(requestParameters: StopRecordingRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise> { + const requestOptions = await this.stopRecordingRequestOpts(requestParameters); + const response = await this.request(requestOptions, initOverrides); - if (this.configuration && this.configuration.accessToken) { - const token = this.configuration.accessToken; - const tokenString = await token('management_token', []); + return new runtime.JSONApiResponse(response, (jsonValue) => RecordingDetailsResponseFromJSON(jsonValue)); + } - if (tokenString) { - headerParameters['Authorization'] = `Bearer ${tokenString}`; - } + /** + * Request the recorder to stop capturing. Finalization is asynchronous: the recording stays `active` until the capture is finalized, then becomes `finished`. Stopping a recording that is no longer active is a no-op. + * Stop a recording + */ + async stopRecording(requestParameters: StopRecordingRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise { + const response = await this.stopRecordingRaw(requestParameters, initOverrides); + return await response.value(); } - let urlPath = `/recordings/{recording_id}/stop`; - urlPath = urlPath.replace('{recording_id}', encodeURIComponent(String(requestParameters['recordingId']))); - - return { - path: urlPath, - method: 'POST', - headers: headerParameters, - query: queryParameters, - }; - } - - /** - * Request the recorder to stop capturing. Finalization is asynchronous: the recording stays `active` until the capture is finalized, then becomes `finished`. Stopping a recording that is no longer active is a no-op. - * Stop a recording - */ - async stopRecordingRaw( - requestParameters: StopRecordingRequest, - initOverrides?: RequestInit | runtime.InitOverrideFunction - ): Promise> { - const requestOptions = await this.stopRecordingRequestOpts(requestParameters); - const response = await this.request(requestOptions, initOverrides); - - return new runtime.JSONApiResponse(response, (jsonValue) => RecordingDetailsResponseFromJSON(jsonValue)); - } - - /** - * Request the recorder to stop capturing. Finalization is asynchronous: the recording stays `active` until the capture is finalized, then becomes `finished`. Stopping a recording that is no longer active is a no-op. - * Stop a recording - */ - async stopRecording( - requestParameters: StopRecordingRequest, - initOverrides?: RequestInit | runtime.InitOverrideFunction - ): Promise { - const response = await this.stopRecordingRaw(requestParameters, initOverrides); - return await response.value(); - } } diff --git a/packages/fishjam-openapi/src/generated/models/PeerOptionsVapi.ts b/packages/fishjam-openapi/src/generated/models/PeerOptionsVapi.ts index 113387fd..28075f45 100644 --- a/packages/fishjam-openapi/src/generated/models/PeerOptionsVapi.ts +++ b/packages/fishjam-openapi/src/generated/models/PeerOptionsVapi.ts @@ -2,7 +2,7 @@ /* eslint-disable */ /** * Fishjam API - * API for managing Fishjam real-time media resources. ## Authentication ## Credentials (Fishjam ID, Fishjam Management Token) can be obtained at https://fishjam.io/app. All requests require HTTP Bearer authorization using the Fishjam Management Token. ## Fishjam SDKs ## For TypeScript and Python users, we provide SDKs that simplify using this API. We recommend using them instead of manually consuming the API in these languages. You can learn more about our SDKs in our [SDK Docs](http://fishjam.swmansion.com/docs/how-to/backend/server-setup) + * API for managing Fishjam real-time media resources. ## Authentication ## Credentials (Fishjam ID, Fishjam Management Token) can be obtained at https://fishjam.io/app. All requests require HTTP Bearer authorization using the Fishjam Management Token. ## Fishjam SDKs ## For TypeScript and Python users, we provide SDKs that simplify using this API. We recommend using them instead of manually consuming the API in these languages. You can learn more about our SDKs in our [SDK Docs](http://fishjam.swmansion.com/docs/how-to/backend/server-setup) * * The version of the OpenAPI document: 0.30.0 * Contact: contact@fishjam.io @@ -15,10 +15,10 @@ import { mapValues } from '../runtime'; import type { SubscribeMode } from './SubscribeMode'; import { - SubscribeModeFromJSON, - SubscribeModeFromJSONTyped, - SubscribeModeToJSON, - SubscribeModeToJSONTyped, + SubscribeModeFromJSON, + SubscribeModeFromJSONTyped, + SubscribeModeToJSON, + SubscribeModeToJSONTyped, } from './SubscribeMode'; /** @@ -53,17 +53,19 @@ export interface PeerOptionsVapi { subscribeMode?: SubscribeMode; } + + /** * Check if a given object implements the PeerOptionsVapi interface. */ export function instanceOfPeerOptionsVapi(value: object): value is PeerOptionsVapi { - if (!('apiKey' in value) || value['apiKey'] === undefined) return false; - if (!('callId' in value) || value['callId'] === undefined) return false; - return true; + if (!('apiKey' in value) || value['apiKey'] === undefined) return false; + if (!('callId' in value) || value['callId'] === undefined) return false; + return true; } export function PeerOptionsVapiFromJSON(json: any): PeerOptionsVapi { - return PeerOptionsVapiFromJSONTyped(json, false); + return PeerOptionsVapiFromJSONTyped(json, false); } export function PeerOptionsVapiFromJSONTyped(json: any, ignoreDiscriminator: boolean): PeerOptionsVapi { @@ -80,13 +82,13 @@ export function PeerOptionsVapiFromJSONTyped(json: any, ignoreDiscriminator: boo } export function PeerOptionsVapiToJSON(json: any): PeerOptionsVapi { - return PeerOptionsVapiToJSONTyped(json, false); + return PeerOptionsVapiToJSONTyped(json, false); } export function PeerOptionsVapiToJSONTyped(value?: PeerOptionsVapi | null, ignoreDiscriminator: boolean = false): any { - if (value == null) { - return value; - } + if (value == null) { + return value; + } return { @@ -96,3 +98,4 @@ export function PeerOptionsVapiToJSONTyped(value?: PeerOptionsVapi | null, ignor 'subscribeMode': SubscribeModeToJSON(value['subscribeMode']), }; } + diff --git a/packages/fishjam-openapi/src/generated/models/RecordingSource.ts b/packages/fishjam-openapi/src/generated/models/RecordingSource.ts index 70e0dbd7..ad33e866 100644 --- a/packages/fishjam-openapi/src/generated/models/RecordingSource.ts +++ b/packages/fishjam-openapi/src/generated/models/RecordingSource.ts @@ -2,7 +2,7 @@ /* eslint-disable */ /** * Fishjam API - * API for managing Fishjam real-time media resources. ## Authentication ## Credentials (Fishjam ID, Fishjam Management Token) can be obtained at https://fishjam.io/app. All requests require HTTP Bearer authorization using the Fishjam Management Token. ## Fishjam SDKs ## For TypeScript and Python users, we provide SDKs that simplify using this API. We recommend using them instead of manually consuming the API in these languages. You can learn more about our SDKs in our [SDK Docs](http://fishjam.swmansion.com/docs/how-to/backend/server-setup) + * API for managing Fishjam real-time media resources. ## Authentication ## Credentials (Fishjam ID, Fishjam Management Token) can be obtained at https://fishjam.io/app. All requests require HTTP Bearer authorization using the Fishjam Management Token. ## Fishjam SDKs ## For TypeScript and Python users, we provide SDKs that simplify using this API. We recommend using them instead of manually consuming the API in these languages. You can learn more about our SDKs in our [SDK Docs](http://fishjam.swmansion.com/docs/how-to/backend/server-setup) * * The version of the OpenAPI document: 0.30.0 * Contact: contact@fishjam.io @@ -14,17 +14,17 @@ import type { CompositionSource } from './CompositionSource'; import { - instanceOfCompositionSource, - CompositionSourceFromJSON, - CompositionSourceFromJSONTyped, - CompositionSourceToJSON, + instanceOfCompositionSource, + CompositionSourceFromJSON, + CompositionSourceFromJSONTyped, + CompositionSourceToJSON, } from './CompositionSource'; import type { TemplateSource } from './TemplateSource'; import { - instanceOfTemplateSource, - TemplateSourceFromJSON, - TemplateSourceFromJSONTyped, - TemplateSourceToJSON, + instanceOfTemplateSource, + TemplateSourceFromJSON, + TemplateSourceFromJSONTyped, + TemplateSourceToJSON, } from './TemplateSource'; /** @@ -35,41 +35,42 @@ import { export type RecordingSource = CompositionSource | TemplateSource; export function RecordingSourceFromJSON(json: any): RecordingSource { - return RecordingSourceFromJSONTyped(json, false); + return RecordingSourceFromJSONTyped(json, false); } export function RecordingSourceFromJSONTyped(json: any, ignoreDiscriminator: boolean): RecordingSource { - if (json == null) { - return json; - } - if (typeof json !== 'object') { - return json; - } - if (instanceOfCompositionSource(json)) { - return CompositionSourceFromJSONTyped(json, true); - } - if (instanceOfTemplateSource(json)) { - return TemplateSourceFromJSONTyped(json, true); - } - return {} as any; + if (json == null) { + return json; + } + if (typeof json !== 'object') { + return json; + } + if (instanceOfCompositionSource(json)) { + return CompositionSourceFromJSONTyped(json, true); + } + if (instanceOfTemplateSource(json)) { + return TemplateSourceFromJSONTyped(json, true); + } + return {} as any; } export function RecordingSourceToJSON(json: any): any { - return RecordingSourceToJSONTyped(json, false); + return RecordingSourceToJSONTyped(json, false); } export function RecordingSourceToJSONTyped(value?: RecordingSource | null, ignoreDiscriminator: boolean = false): any { - if (value == null) { - return value; - } - if (typeof value !== 'object') { - return value; - } - if (instanceOfCompositionSource(value)) { - return CompositionSourceToJSON(value as CompositionSource); - } - if (instanceOfTemplateSource(value)) { - return TemplateSourceToJSON(value as TemplateSource); - } - return {}; + if (value == null) { + return value; + } + if (typeof value !== 'object') { + return value; + } + if (instanceOfCompositionSource(value)) { + return CompositionSourceToJSON(value as CompositionSource); + } + if (instanceOfTemplateSource(value)) { + return TemplateSourceToJSON(value as TemplateSource); + } + return {}; } + diff --git a/packages/fishjam-openapi/src/generated/models/TemplateSource.ts b/packages/fishjam-openapi/src/generated/models/TemplateSource.ts index 241161df..f0d6adf3 100644 --- a/packages/fishjam-openapi/src/generated/models/TemplateSource.ts +++ b/packages/fishjam-openapi/src/generated/models/TemplateSource.ts @@ -2,7 +2,7 @@ /* eslint-disable */ /** * Fishjam API - * API for managing Fishjam real-time media resources. ## Authentication ## Credentials (Fishjam ID, Fishjam Management Token) can be obtained at https://fishjam.io/app. All requests require HTTP Bearer authorization using the Fishjam Management Token. ## Fishjam SDKs ## For TypeScript and Python users, we provide SDKs that simplify using this API. We recommend using them instead of manually consuming the API in these languages. You can learn more about our SDKs in our [SDK Docs](http://fishjam.swmansion.com/docs/how-to/backend/server-setup) + * API for managing Fishjam real-time media resources. ## Authentication ## Credentials (Fishjam ID, Fishjam Management Token) can be obtained at https://fishjam.io/app. All requests require HTTP Bearer authorization using the Fishjam Management Token. ## Fishjam SDKs ## For TypeScript and Python users, we provide SDKs that simplify using this API. We recommend using them instead of manually consuming the API in these languages. You can learn more about our SDKs in our [SDK Docs](http://fishjam.swmansion.com/docs/how-to/backend/server-setup) * * The version of the OpenAPI document: 0.30.0 * Contact: contact@fishjam.io @@ -15,10 +15,10 @@ import { mapValues } from '../runtime'; import type { TemplateSourceResolution } from './TemplateSourceResolution'; import { - TemplateSourceResolutionFromJSON, - TemplateSourceResolutionFromJSONTyped, - TemplateSourceResolutionToJSON, - TemplateSourceResolutionToJSONTyped, + TemplateSourceResolutionFromJSON, + TemplateSourceResolutionFromJSONTyped, + TemplateSourceResolutionToJSON, + TemplateSourceResolutionToJSONTyped, } from './TemplateSourceResolution'; /** @@ -27,61 +27,64 @@ import { * @interface TemplateSource */ export interface TemplateSource { - /** - * Whether the recording captures audio. Defaults to true. - * @type {boolean} - * @memberof TemplateSource - */ - audio?: boolean; - /** - * URL of the composition to record - * @type {string} - * @memberof TemplateSource - */ - compositionURL: string; - /** - * - * @type {TemplateSourceResolution} - * @memberof TemplateSource - */ - resolution?: TemplateSourceResolution; + /** + * Whether the recording captures audio. Defaults to true. + * @type {boolean} + * @memberof TemplateSource + */ + audio?: boolean; + /** + * URL of the composition to record + * @type {string} + * @memberof TemplateSource + */ + compositionURL: string; + /** + * + * @type {TemplateSourceResolution} + * @memberof TemplateSource + */ + resolution?: TemplateSourceResolution; } /** * Check if a given object implements the TemplateSource interface. */ export function instanceOfTemplateSource(value: object): value is TemplateSource { - if (!('compositionURL' in value) || value['compositionURL'] === undefined) return false; - return true; + if (!('compositionURL' in value) || value['compositionURL'] === undefined) return false; + return true; } export function TemplateSourceFromJSON(json: any): TemplateSource { - return TemplateSourceFromJSONTyped(json, false); + return TemplateSourceFromJSONTyped(json, false); } export function TemplateSourceFromJSONTyped(json: any, ignoreDiscriminator: boolean): TemplateSource { - if (json == null) { - return json; - } - return { - audio: json['audio'] == null ? undefined : json['audio'], - compositionURL: json['compositionURL'], - resolution: json['resolution'] == null ? undefined : TemplateSourceResolutionFromJSON(json['resolution']), - }; + if (json == null) { + return json; + } + return { + + 'audio': json['audio'] == null ? undefined : json['audio'], + 'compositionURL': json['compositionURL'], + 'resolution': json['resolution'] == null ? undefined : TemplateSourceResolutionFromJSON(json['resolution']), + }; } export function TemplateSourceToJSON(json: any): TemplateSource { - return TemplateSourceToJSONTyped(json, false); + return TemplateSourceToJSONTyped(json, false); } export function TemplateSourceToJSONTyped(value?: TemplateSource | null, ignoreDiscriminator: boolean = false): any { - if (value == null) { - return value; - } + if (value == null) { + return value; + } - return { - audio: value['audio'], - compositionURL: value['compositionURL'], - resolution: TemplateSourceResolutionToJSON(value['resolution']), - }; + return { + + 'audio': value['audio'], + 'compositionURL': value['compositionURL'], + 'resolution': TemplateSourceResolutionToJSON(value['resolution']), + }; } + diff --git a/packages/fishjam-openapi/src/generated/models/TemplateSourceResolution.ts b/packages/fishjam-openapi/src/generated/models/TemplateSourceResolution.ts index ae76661b..8ef4acf8 100644 --- a/packages/fishjam-openapi/src/generated/models/TemplateSourceResolution.ts +++ b/packages/fishjam-openapi/src/generated/models/TemplateSourceResolution.ts @@ -2,7 +2,7 @@ /* eslint-disable */ /** * Fishjam API - * API for managing Fishjam real-time media resources. ## Authentication ## Credentials (Fishjam ID, Fishjam Management Token) can be obtained at https://fishjam.io/app. All requests require HTTP Bearer authorization using the Fishjam Management Token. ## Fishjam SDKs ## For TypeScript and Python users, we provide SDKs that simplify using this API. We recommend using them instead of manually consuming the API in these languages. You can learn more about our SDKs in our [SDK Docs](http://fishjam.swmansion.com/docs/how-to/backend/server-setup) + * API for managing Fishjam real-time media resources. ## Authentication ## Credentials (Fishjam ID, Fishjam Management Token) can be obtained at https://fishjam.io/app. All requests require HTTP Bearer authorization using the Fishjam Management Token. ## Fishjam SDKs ## For TypeScript and Python users, we provide SDKs that simplify using this API. We recommend using them instead of manually consuming the API in these languages. You can learn more about our SDKs in our [SDK Docs](http://fishjam.swmansion.com/docs/how-to/backend/server-setup) * * The version of the OpenAPI document: 0.30.0 * Contact: contact@fishjam.io @@ -19,60 +19,57 @@ import { mapValues } from '../runtime'; * @interface TemplateSourceResolution */ export interface TemplateSourceResolution { - /** - * - * @type {number} - * @memberof TemplateSourceResolution - */ - height: number; - /** - * - * @type {number} - * @memberof TemplateSourceResolution - */ - width: number; + /** + * + * @type {number} + * @memberof TemplateSourceResolution + */ + height: number; + /** + * + * @type {number} + * @memberof TemplateSourceResolution + */ + width: number; } /** * Check if a given object implements the TemplateSourceResolution interface. */ export function instanceOfTemplateSourceResolution(value: object): value is TemplateSourceResolution { - if (!('height' in value) || value['height'] === undefined) return false; - if (!('width' in value) || value['width'] === undefined) return false; - return true; + if (!('height' in value) || value['height'] === undefined) return false; + if (!('width' in value) || value['width'] === undefined) return false; + return true; } export function TemplateSourceResolutionFromJSON(json: any): TemplateSourceResolution { - return TemplateSourceResolutionFromJSONTyped(json, false); + return TemplateSourceResolutionFromJSONTyped(json, false); } -export function TemplateSourceResolutionFromJSONTyped( - json: any, - ignoreDiscriminator: boolean -): TemplateSourceResolution { - if (json == null) { - return json; - } - return { - height: json['height'], - width: json['width'], - }; +export function TemplateSourceResolutionFromJSONTyped(json: any, ignoreDiscriminator: boolean): TemplateSourceResolution { + if (json == null) { + return json; + } + return { + + 'height': json['height'], + 'width': json['width'], + }; } export function TemplateSourceResolutionToJSON(json: any): TemplateSourceResolution { - return TemplateSourceResolutionToJSONTyped(json, false); + return TemplateSourceResolutionToJSONTyped(json, false); } -export function TemplateSourceResolutionToJSONTyped( - value?: TemplateSourceResolution | null, - ignoreDiscriminator: boolean = false -): any { - if (value == null) { - return value; - } +export function TemplateSourceResolutionToJSONTyped(value?: TemplateSourceResolution | null, ignoreDiscriminator: boolean = false): any { + if (value == null) { + return value; + } - return { - height: value['height'], - width: value['width'], - }; + return { + + 'height': value['height'], + 'width': value['width'], + }; } + From 111d79b9d586ab4e5e40ad0d7645f74171365d59 Mon Sep 17 00:00:00 2001 From: Adrian Czerwiec Date: Thu, 24 Sep 2026 10:14:31 +0200 Subject: [PATCH 4/4] deprecate createRecording and introudce createCompositionRecording --- packages/js-server-sdk/src/client.ts | 19 ++++++++++++++++--- packages/js-server-sdk/src/types.ts | 6 ++++++ .../js-server-sdk/tests/recordings.test.ts | 9 +++++++++ 3 files changed, 31 insertions(+), 3 deletions(-) diff --git a/packages/js-server-sdk/src/client.ts b/packages/js-server-sdk/src/client.ts index ebd988b5..cd36f957 100644 --- a/packages/js-server-sdk/src/client.ts +++ b/packages/js-server-sdk/src/client.ts @@ -12,10 +12,10 @@ import { PeerOptionsAgent, MoqAccessConfig, type Middleware, - RecordingConfig, } from '@fishjam-cloud/fishjam-openapi'; import type { AgentCallbacks, + CompositionRecordingConfig, FishjamConfig, PeerId, Recording, @@ -358,9 +358,22 @@ export class FishjamClient { } /** - * Create a new recording. Capturing starts synchronously, so the returned recording is `active`. + * @deprecated Use {@link createCompositionRecording} instead. + * Create a new recording that mirrors existing composition output. Capturing starts synchronously, so the returned recording is `active`. */ - async createRecording(config: RecordingConfig): Promise { + async createRecording(config: CompositionRecordingConfig): Promise { + try { + const { data } = await this.recordingsApi.createRecording({ recordingConfig: config }); + return data as Recording; + } catch (error) { + throw await mapException(error); + } + } + + /** + * Create a new recording that mirrors existing composition output. Capturing starts synchronously, so the returned recording is `active`. + */ + async createCompositionRecording(config: CompositionRecordingConfig): Promise { try { const { data } = await this.recordingsApi.createRecording({ recordingConfig: config }); return data as Recording; diff --git a/packages/js-server-sdk/src/types.ts b/packages/js-server-sdk/src/types.ts index 5cd29719..6b8a379e 100644 --- a/packages/js-server-sdk/src/types.ts +++ b/packages/js-server-sdk/src/types.ts @@ -2,6 +2,7 @@ import { CompositionCreatedResponse } from '@fishjam-cloud/composition-openapi'; import { Peer as OpenApiPeer, PeerType as OpenApiPeerType, + CompositionSource, Recording as OpenApiRecording, RecordingConfig, RoomConfig, @@ -77,6 +78,11 @@ export type RecordingStatus = 'active' | 'finished' | 'available' | 'failed'; */ export type Recording = Override; +/** + * Configuration of a recording that captures an existing composition. + */ +export type CompositionRecordingConfig = Override; + /** * Configuration of a recording that renders its own scene from a template. */ diff --git a/packages/js-server-sdk/tests/recordings.test.ts b/packages/js-server-sdk/tests/recordings.test.ts index 5004ae56..95b118d0 100644 --- a/packages/js-server-sdk/tests/recordings.test.ts +++ b/packages/js-server-sdk/tests/recordings.test.ts @@ -75,6 +75,15 @@ describe('createTemplateRecording', () => { }); }); +describe('createRecording', () => { + it('accepts only a composition source', async () => { + stubFetch({ data: templateRecording }); + + // @ts-expect-error a template source needs its bundle, so it goes through createTemplateRecording + await client().createRecording({ source: { compositionURL: COMPOSITION_URL } }); + }); +}); + describe('reading a template recording', () => { it('parses the source the server echoes back', async () => { stubFetch({ data: templateRecording }, 200);