Skip to content

Commit

Permalink
feat: http url interpolation SOFIE-3310 (#342)
Browse files Browse the repository at this point in the history
Co-authored-by: Johan Nyman <[email protected]>
  • Loading branch information
Julusian and nytamin authored Aug 19, 2024
1 parent 2207439 commit 01593a3
Show file tree
Hide file tree
Showing 9 changed files with 103 additions and 20 deletions.
10 changes: 10 additions & 0 deletions packages/timeline-state-resolver-types/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -172,3 +172,13 @@ export enum ActionExecutionResultCode {
Error = 'ERROR',
Ok = 'OK',
}

/** This resolves to a string, where parts can be defined by the datastore */
export interface TemplateString {
/** The string template. Example: "http://google.com?q={{searchString}}" */
key: string
/** Values for the arguments in the key string. Example: { searchString: "TSR" } */
args?: {
[k: string]: any
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { DeviceType } from '..'
import { DeviceType, TemplateString } from '..'

export enum TimelineContentTypeCasparCg { // CasparCG-state
MEDIA = 'media',
Expand Down Expand Up @@ -122,7 +122,7 @@ export interface TimelineContentCCGInput extends TimelineContentCasparCGBase, Ti
export interface TimelineContentCCGHTMLPage extends TimelineContentCasparCGBase, TimelineContentCCGProducerBase {
type: TimelineContentTypeCasparCg.HTMLPAGE
/** The URL to load */
url: string
url: string | TemplateString
}
export interface TimelineContentCCGTemplate extends TimelineContentCasparCGBase, TimelineContentCCGProducerBase {
type: TimelineContentTypeCasparCg.TEMPLATE
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
import { DeviceType, HTTPSendCommandContent } from '..'
import { DeviceType, HTTPSendCommandContent, TemplateString } from '..'

export type TimelineContentHTTPSendAny = TimelineContentHTTPRequest
export interface TimelineContentHTTPSendBase {
deviceType: DeviceType.HTTPSEND
}

export type TimelineContentHTTPRequest = TimelineContentHTTPSendBase & HTTPSendCommandContent
export interface HTTPSendCommandContentExt extends Omit<HTTPSendCommandContent, 'url'> {
url: string | TemplateString
}

export type TimelineContentHTTPRequest = TimelineContentHTTPSendBase & HTTPSendCommandContentExt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { DeviceType } from '..'
import { DeviceType, TemplateString } from '..'

export enum TimelineContentTypeSofieChef {
URL = 'url',
Expand All @@ -13,5 +13,5 @@ export interface TimelineContentSofieChef {
export interface TimelineContentSofieChefScene extends TimelineContentSofieChef {
type: TimelineContentTypeSofieChef.URL

url: string
url: string | TemplateString
}
34 changes: 34 additions & 0 deletions packages/timeline-state-resolver/src/__tests__/lib.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { interpolateTemplateString, interpolateTemplateStringIfNeeded } from '../lib'

describe('interpolateTemplateString', () => {
test('basic input', () => {
expect(interpolateTemplateString('Hello there {{name}}', { name: 'Bob' })).toEqual('Hello there Bob')
})

test('missing arg', () => {
expect(interpolateTemplateString('Hello there {{name}}', {})).toEqual('Hello there name')
})

test('repeated arg', () => {
expect(interpolateTemplateString('Hello there {{name}} {{name}} {{name}}', { name: 'Bob' })).toEqual(
'Hello there Bob Bob Bob'
)
})
})

describe('interpolateTemplateStringIfNeeded', () => {
test('string input', () => {
const input = 'Hello there'

expect(interpolateTemplateStringIfNeeded(input)).toEqual(input)
})

test('object input', () => {
expect(
interpolateTemplateStringIfNeeded({
key: 'Hello there {{name}}',
args: { name: 'Bob' },
})
).toEqual('Hello there Bob')
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,15 @@ import { DoOnTime, SendMode } from '../../devices/doOnTime'
import got from 'got'
import { InternalTransitionHandler } from '../../devices/transitions/transitionHandler'
import Debug from 'debug'
import { actionNotFoundMessage, deepMerge, endTrace, literal, startTrace, t } from '../../lib'
import {
actionNotFoundMessage,
deepMerge,
endTrace,
interpolateTemplateStringIfNeeded,
literal,
startTrace,
t,
} from '../../lib'
import { ClsParameters } from 'casparcg-connection/dist/parameters'
const debug = Debug('timeline-state-resolver:casparcg')

Expand Down Expand Up @@ -392,7 +400,7 @@ export class CasparCGDevice extends DeviceWithState<State, DeviceOptionsCasparCG
id: layer.id,
layerNo: mapping.layer,
content: LayerContentType.HTMLPAGE,
media: content.url,
media: interpolateTemplateStringIfNeeded(content.url),

playTime: startTime || null,
playing: true,
Expand Down
25 changes: 14 additions & 11 deletions packages/timeline-state-resolver/src/integrations/httpSend/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
ActionExecutionResultCode,
DeviceStatus,
HTTPSendCommandContent,
HTTPSendCommandContentExt,
HTTPSendOptions,
HttpSendActions,
SendCommandResult,
Expand All @@ -15,7 +16,7 @@ import {
} from 'timeline-state-resolver-types'
import _ = require('underscore')
import got, { OptionsOfTextResponseBody, RequestError } from 'got'
import { t } from '../../lib'
import { interpolateTemplateStringIfNeeded, t } from '../../lib'
import { HttpProxyAgent, HttpsProxyAgent } from 'hpagent'
import CacheableLookup from 'cacheable-lookup'

Expand All @@ -24,7 +25,7 @@ export type HttpSendDeviceState = Timeline.TimelineState<TSRTimelineContent>
export interface HttpSendDeviceCommand extends CommandWithContext {
command: {
commandName: 'added' | 'changed' | 'removed' | 'retry' | 'manual'
content: HTTPSendCommandContent
content: HTTPSendCommandContentExt
layer: string
}
}
Expand Down Expand Up @@ -69,7 +70,7 @@ export class HTTPSendDevice extends Device<HTTPSendOptions, HttpSendDeviceState,
}

private async executeSendCommandAction(
cmd?: HTTPSendCommandContent
cmd?: HTTPSendCommandContentExt
): Promise<ActionExecutionResult<SendCommandResult>> {
if (!cmd)
return {
Expand Down Expand Up @@ -133,7 +134,7 @@ export class HTTPSendDevice extends Device<HTTPSendOptions, HttpSendDeviceState,
context: `added: ${newLayer.id}`,
command: {
commandName: 'added',
content: newLayer.content as HTTPSendCommandContent,
content: newLayer.content as HTTPSendCommandContentExt,
layer: layerKey,
},
})
Expand All @@ -146,7 +147,7 @@ export class HTTPSendDevice extends Device<HTTPSendOptions, HttpSendDeviceState,
context: `changed: ${newLayer.id} (previously: ${oldLayer.id})`,
command: {
commandName: 'changed',
content: newLayer.content as HTTPSendCommandContent,
content: newLayer.content as HTTPSendCommandContentExt,
layer: layerKey,
},
})
Expand All @@ -161,7 +162,7 @@ export class HTTPSendDevice extends Device<HTTPSendOptions, HttpSendDeviceState,
commands.push({
timelineObjId: oldLayer.id,
context: `removed: ${oldLayer.id}`,
command: { commandName: 'removed', content: oldLayer.content as HTTPSendCommandContent, layer: layerKey },
command: { commandName: 'removed', content: oldLayer.content as HTTPSendCommandContentExt, layer: layerKey },
})
}
})
Expand Down Expand Up @@ -220,15 +221,17 @@ export class HTTPSendDevice extends Device<HTTPSendOptions, HttpSendDeviceState,
headers: command.content.headers,
}

const url = new URL(command.content.url)
if (!this.options.noProxy?.includes(url.host)) {
if (url.protocol === 'http:' && this.options.httpProxy) {
const commandUrl: string = interpolateTemplateStringIfNeeded(command.content.url)

const parsedUrl = new URL(commandUrl)
if (!this.options.noProxy?.includes(parsedUrl.host)) {
if (parsedUrl.protocol === 'http:' && this.options.httpProxy) {
options.agent = {
http: new HttpProxyAgent({
proxy: this.options.httpProxy,
}),
}
} else if (url.protocol === 'https:' && this.options.httpsProxy) {
} else if (parsedUrl.protocol === 'https:' && this.options.httpsProxy) {
options.agent = {
https: new HttpsProxyAgent({
proxy: this.options.httpsProxy,
Expand All @@ -252,7 +255,7 @@ export class HTTPSendDevice extends Device<HTTPSendOptions, HttpSendDeviceState,
}
}

const response = await httpReq(command.content.url, options)
const response = await httpReq(commandUrl, options)

if (response.statusCode === 200) {
this.context.logger.debug(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
DeviceType,
} from 'timeline-state-resolver-types'
import type { SofieChefState } from '.'
import { interpolateTemplateStringIfNeeded } from '../../lib'

export function buildSofieChefState(
timelineState: Timeline.TimelineState<TSRTimelineContent>,
Expand All @@ -23,7 +24,7 @@ export function buildSofieChefState(

if (mapping && content.deviceType === DeviceType.SOFIE_CHEF) {
sofieChefState.windows[mapping.options.windowId] = {
url: content.url,
url: interpolateTemplateStringIfNeeded(content.url),
urlTimelineObjId: layerState.id,
}
}
Expand Down
23 changes: 23 additions & 0 deletions packages/timeline-state-resolver/src/lib.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
ActionExecutionResultCode,
TimelineDatastoreReferences,
ActionExecutionResult,
TemplateString,
} from 'timeline-state-resolver-types'
import * as _ from 'underscore'
import { PartialDeep } from 'type-fest'
Expand Down Expand Up @@ -307,3 +308,25 @@ export function actionNotFoundMessage(id: never): ActionExecutionResult<any> {
export function cloneDeep<T>(input: T): T {
return klona(input)
}

/**
* Interpolate a translation style string
*/
export function interpolateTemplateString(key: string, args: { [key: string]: any } | undefined): string {
if (!args || typeof key !== 'string') {
return String(key)
}

let interpolated = String(key)
for (const placeholder of key.match(/[^{}]+(?=})/g) || []) {
const value = args[placeholder] || placeholder
interpolated = interpolated.replace(`{{${placeholder}}}`, value)
}

return interpolated
}

export function interpolateTemplateStringIfNeeded(str: string | TemplateString): string {
if (typeof str === 'string') return str
return interpolateTemplateString(str.key, str.args)
}

0 comments on commit 01593a3

Please sign in to comment.