diff --git a/jest.config.js b/jest.config.js index 91a2d2c..eef6b07 100644 --- a/jest.config.js +++ b/jest.config.js @@ -1,4 +1,4 @@ module.exports = { preset: 'ts-jest', - testEnvironment: 'node', -}; \ No newline at end of file + testEnvironment: 'node' +}; diff --git a/nodemon.json b/nodemon.json index c5add9f..d734304 100644 --- a/nodemon.json +++ b/nodemon.json @@ -2,4 +2,4 @@ "watch": ["src"], "ext": "ts", "exec": "node --inspect -r ts-node/register ./src/server.ts" -} \ No newline at end of file +} diff --git a/readme-typescript-nodejs.md b/readme-typescript-nodejs.md index eba2b20..17fd27a 100644 --- a/readme-typescript-nodejs.md +++ b/readme-typescript-nodejs.md @@ -1,4 +1,4 @@ -# typescript-nodejs +# typescript-nodejs Requirements: node.js >= 18.15.0 ( LTS ) @@ -14,4 +14,4 @@ Template for TypeScript nodejs projects. It includes: 1. Update package.json with your project details 2. Install dependencies with `npm install` -3. Start coding! \ No newline at end of file +3. Start coding! diff --git a/src/api.ts b/src/api.ts index 9f5e143..3e4a003 100644 --- a/src/api.ts +++ b/src/api.ts @@ -14,7 +14,11 @@ export interface HealthcheckOptions { title: string; } -const healthcheck: FastifyPluginCallback = (fastify, opts, next) => { +const healthcheck: FastifyPluginCallback = ( + fastify, + opts, + next +) => { fastify.get<{ Reply: Static }>( '/', { @@ -62,4 +66,4 @@ export default (opts: ApiOptions) => { // register other API routes here return api; -} +}; diff --git a/src/api_productions.ts b/src/api_productions.ts new file mode 100644 index 0000000..1122869 --- /dev/null +++ b/src/api_productions.ts @@ -0,0 +1,123 @@ +import { Static, Type } from '@sinclair/typebox'; +import { FastifyPluginCallback, FastifyRequest } from 'fastify'; +import { AllocateProduction, Production, SdpOffer, SdpAnswer } from './models'; + +const apiChannel: FastifyPluginCallback = (fastify, opts, next) => { + fastify.post<{ + Body: typeof AllocateProduction; + Reply: typeof Production; + }>( + '/production', + { + schema: { + description: 'Create a new Production resource.', + body: AllocateProduction, + response: { + 200: Production + } + } + }, + async (request, reply) => { + try { + //prod = new Production(name, [line1, line2, line3]) + //Production construction will make calls to smb and set up each line. + } catch (err) { + //error handling + } + } + ); + + fastify.get<{ + Params: { id: string }; + Reply: any; + }>( + '/production', + { + schema: { + description: 'Retrieves all Production resource.', + response: { + 200: Type.Array(Production) + } + } + }, + async (request, reply) => { + try { + //request logic + } catch (err) { + //error handling + } + } + ); + + fastify.get<{ + Params: { id: string }; + Reply: any; + }>( + '/productions/:id', + { + schema: { + description: 'Retrieves a Production resource.', + response: { + 200: Production + } + } + }, + async (request, reply) => { + try { + //request logic + } catch (err) { + //error handling + } + } + ); + + fastify.delete<{ + Params: { id: string }; + Reply: string; + }>( + '/productions/:id', + { + schema: { + description: 'Delete a Production resource.', + response: { + 200: Type.Object({ + message: Type.Literal('removed') + }) + } + } + }, + async (request, reply) => { + try { + //request logic + } catch (err) { + //error handling + } + } + ); + + fastify.post<{ + Params: { id: string; name: string }; + Body: typeof SdpOffer; + Reply: typeof SdpAnswer; + }>( + '/productions/:id/lines/:name', + { + schema: { + description: 'Join a Production line.', + body: SdpOffer, + response: { + 200: SdpAnswer + } + } + }, + async (request, reply) => { + try { + //Generate SDP, and anything other information required to set up a connection. + } catch (err) { + //error handling + } + } + ); + + next(); +}; diff --git a/src/models.ts b/src/models.ts new file mode 100644 index 0000000..24ff208 --- /dev/null +++ b/src/models.ts @@ -0,0 +1,243 @@ +import { Type } from '@sinclair/typebox'; + +//Webrtc +export const SdpOffer = Type.String(); +export const SdpAnswer = Type.String(); + +//Allocation Models +export const AllocateProduction = Type.Object({ + name: Type.String(), + lines: Type.Array( + Type.Object({ + name: Type.String() + }) + ) +}); + +export const AllocateConference = Type.Object({ + 'last-n': Type.Integer(), + 'global-port': Type.Boolean() +}); + +export const AllocateEndpoint = Type.Object({ + action: Type.Literal('allocate'), + 'bundle-transport': Type.Object({ + 'ice-controlling': Type.Boolean(), + ice: Type.Literal(true), + dtls: Type.Boolean(), + sdes: Type.Boolean() + }), + audio: Type.Object({ + 'relay-type': Type.Array( + Type.Union([ + Type.Literal('forwarder'), + Type.Literal('mixed'), + Type.Literal('ssrc-rewrite') + ]) + ) + }), + video: Type.Object({ + 'relay-type': Type.Union([ + Type.Literal('forwarder'), + Type.Literal('ssrc-rewrite') + ]) + }), + data: Type.Object({}), + idleTimeout: Type.Integer() +}); + +//Production +export const Production = Type.Object({ + id: Type.String(), + name: Type.String(), + lines: Type.Array( + Type.Object({ + name: Type.String() + }) + ) +}); + +//Conference +export const Conference = Type.Object({ + id: Type.Integer() +}); + +//Endpoint +const SOURCES = Type.Array( + Type.Object({ + main: Type.Integer(), + feedback: Type.Integer() + }) +); + +const DTLS = Type.Object({ + setup: Type.String(), + type: Type.String(), + hash: Type.String() +}); + +const SDES = Type.Array( + Type.Object({ + profile: Type.String(), + key: Type.String() + }) +); + +const CANDIDATES = Type.Array( + Type.Object({ + foundation: Type.String(), + component: Type.Integer(), + protocol: Type.String(), + priority: Type.Integer(), + ip: Type.String(), + port: Type.Integer(), + type: Type.String(), + generation: Type.Integer(), + network: Type.Integer() + }) +); + +const AUDIO_PAYLOAD_TYPE = Type.Object({ + id: Type.Integer(), + parameters: Type.Object({ + minptime: Type.String(), + useinbandfec: Type.String() + }), + 'rtcp-fbs': Type.Array(Type.Any()), + name: Type.String(), + clockrate: Type.Integer(), + channels: Type.Integer() +}); + +const RTP_HDREXT = Type.Array( + Type.Object({ + id: Type.Integer(), + uri: Type.String() + }) +); + +const PARAMS = Type.Object({ + apt: Type.String() +}); + +const VIDEO_STREAM = Type.Array( + Type.Object({ + content: Type.String(), + sources: SOURCES + }) +); + +const VIDEO_STREAMS = Type.Array(VIDEO_STREAM); + +const VIDEO_PAYLOAD_TYPES = Type.Array( + Type.Object({ + id: Type.Number(), + parameters: Type.Object({}), + rtcp_fbs: Type.Array( + Type.Object({ + type: Type.String(), + subtype: Type.Optional(Type.String()) + }) + ), + name: Type.String(), + clockrate: Type.Number() + }) +); + +const RTP_HDR_EXTS = Type.Array( + Type.Object({ + id: Type.Number(), + uri: Type.String() + }) +); + +const RECONFIGURED_STREAMS = Type.Array( + Type.Object({ + sources: SOURCES, + id: Type.String(), + content: Type.String() + }) +); + +export const Endpoint = Type.Object({ + bundleTransport: Type.Object({ + dtls: DTLS, + sdes: SDES, + ice: Type.Object({ + ufrag: Type.String(), + pwd: Type.String(), + candidates: CANDIDATES + }) + }), + audio: Type.Object({ + 'payload-type': AUDIO_PAYLOAD_TYPE, + ssrcs: Type.Array(Type.Integer()), + 'rtp-hdrexts': RTP_HDREXT + }), + video: Type.Object({ + 'payload-types': Type.Array( + Type.Object({ + id: Type.Integer(), + parameters: PARAMS, + 'rtcp-fbs': Type.Array( + Type.Object({ + type: Type.String(), + subtype: Type.String() + }) + ), + name: Type.String(), + clockrate: Type.Integer() + }) + ), + streams: VIDEO_STREAM, + 'rtp-hdrexts': RTP_HDREXT + }), + data: Type.Object({ + port: Type.Integer() + }) +}); + +//Configuration Models +export const ConfigureEndpoint = Type.Object({ + action: Type.String({ enum: ['configure'] }), + bundle_transport: Type.Object({ + dtls: DTLS, + sdes: SDES, + ice: Type.Object({ + ufrag: Type.String(), + pwd: Type.String(), + candidates: CANDIDATES + }) + }), + audio: Type.Object({ + payload_type: AUDIO_PAYLOAD_TYPE, + ssrcs: Type.Array(Type.Integer()), + rtp_hdr_exts: RTP_HDR_EXTS + }), + video: Type.Object({ + payload_types: VIDEO_PAYLOAD_TYPES, + streams: VIDEO_STREAMS, + rtp_hdr_exts: RTP_HDR_EXTS + }), + data: Type.Object({ + port: Type.Integer() + }), + neighbours: Type.Object({ + groups: Type.Array(Type.String()) + }) +}); + +//Reconfiguration Models +const RECONFIGURED_AUDIO = Type.Object({ + ssrcs: Type.Array(Type.Integer()) +}); + +const RECONFIGURED_VIDEO = Type.Object({ + streams: RECONFIGURED_STREAMS +}); + +export const ReconfigureEndpoint = Type.Object({ + action: Type.Literal('reconfigure'), + audio: RECONFIGURED_AUDIO, + video: RECONFIGURED_VIDEO +}); diff --git a/tsconfig.base.json b/tsconfig.base.json index a7c1f1f..bbc7478 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -1,7 +1,6 @@ { "compilerOptions": { - /* Visit https://aka.ms/tsconfig to read more about this file */ - + /* Visit https://aka.ms/tsconfig to read more about this file /* JavaScript Support */ "allowJs": true /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */, "checkJs": true /* Enable error reporting in type-checked JavaScript files. */,