obs-websocket-community-projects / obs-websocket-js

Consumes https://github.com/obsproject/obs-websocket
MIT License
679 stars 96 forks source link

Allow for `outputVariables` and `inputVariables` in `RequestBatchRequest` type. #313

Open FiniteSingularity opened 1 year ago

FiniteSingularity commented 1 year ago

Description:

One of the many significant changes to the OBS websocket v5 protocol is that source visibility requires the source's ID within the scene where it is being enabled/disabled. For something as simple (and common) as toggling on/off a source, this is cumbersome and can add considerable complexity to client code. The "recommended" way of doing this is to use a batched request that get's a source's id within a scene (given the scene name and source name), store its value in the outputVariables field of that request, then send a source enable request that specifies an inputVariable from the prior request's outputVariables as the sourceId. (An example is given in the issue here: https://github.com/obsproject/obs-websocket/issues/1014#issuecomment-1319774798 ). Unfortunately, the typescript typedef for RequestBatchRequest does not allow for the inputVariables or outputVariables fields.

Modifying the type.d.ts file to allow for an optional inputVariables: any and outputVariables: any allows this to work via obs-websocket-js. However it appears that this file is automatically generated from OBS Websocket's types?

Versions Used (if applicable):

FiniteSingularity commented 1 year ago

I just started putting together a PR that adds a properly typed outputVariables and a semi-typed inputVariables to the RequestBatchRequest type. I'll work a bit on the inputVariables and submit a PR soon, but if folks want to give it a try, you can find the branch here: https://github.com/FiniteSingularity/obs-websocket-js/tree/feature/adds-input-output-vars

FiniteSingularity commented 1 year ago

One issue I am seeing is that of enforcing type safety. About the best option I can come up with is this:

export declare type RequestBatchRequest<T = keyof OBSRequestTypes> = T extends keyof OBSRequestTypes ? OBSRequestTypes[T] extends never ? {
    requestType: T;
    requestId?: string;
    outputVariables?: Record<string, keyof OBSResponseTypes[T]>;
} : {
    requestType: T;
    requestId?: string;
    requestData: Partial<OBSRequestTypes[T]>;
    inputVariables?: Partial<OBSRequestTypes[T]>;
    outputVariables?: Record<string, keyof OBSResponseTypes[T]>;
} : never;

requestData to must be a Partial<OBSRequestTypes[T]> because the keys we replace using inputVariables will no longer exist in requestData. What we really need is for the union of requestData and the optional inputVariables to be an OBSRequestTypes[T]. I've tried (unsuccessfully) to do this in a few ways, for example, letting inputVariables be a Record<string, string>, then using Omit on those keys for requestData:

export declare type RequestBatchRequest<T = keyof OBSRequestTypes, S = Record<string, string>> = T extends keyof OBSRequestTypes ? OBSRequestTypes[T] extends never ? {
    requestType: T;
    requestId?: string;
    outputVariables?: Record<string, keyof OBSResponseTypes[T]>;
} : {
    requestType: T;
    requestId?: string;
    requestData: Omit<OBSRequestTypes[T], keyof S>;
    inputVariables?: S;
    outputVariables?: Record<string, keyof OBSResponseTypes[T]>;
} : never;

However, unless the template for S is explicitly set when implementing a RequestBatchRequest, the Omit on requestData sees S as never, and type checking is lost. e.g.- in order for this to work, one would have to do something like:

const req: RequestBatchRequest<'someRequest', {fieldName: string}> = {
  requestType: 'someRequest',
  requestData: { aField: 'value' },
  inputVariables: {fieldName: 'outputVariableName'},
}

If anyone has other ideas, I am all ears.