Path: blob/main/src/format/reveal/format-reveal-multiplex.ts
6452 views
/*1* format-reveal-multiplex.ts2*3* Copyright (C) 2021-2022 Posit Software, PBC4*/56import { existsSync } from "../../deno_ral/fs.ts";7import { join } from "../../deno_ral/path.ts";8import { isSelfContainedOutput } from "../../command/render/render-info.ts";9import { kResourcePath } from "../../config/constants.ts";1011import { Format, FormatExtras, PandocFlags } from "../../config/types.ts";12import { pandocIngestSelfContainedContent } from "../../core/pandoc/self-contained.ts";13import { dirAndStem, pathWithForwardSlashes } from "../../core/path.ts";14import { formatResourcePath } from "../../core/resources.ts";15import { lines } from "../../core/text.ts";1617export const kRevealJsMultiplex = "multiplex";1819export function revealMultiplexPlugin(format: Format): string | undefined {20if (format.metadata[kRevealJsMultiplex]) {21return formatResourcePath("revealjs", join("plugins", "multiplex"));22} else {23return undefined;24}25}2627export function revealMuliplexPreviewFile(file: string) {28const speakerFile = revealSpeakerOutput(file);29if (existsSync(speakerFile) && existsSync(file)) {30// return speaker file if it's >= mod time of file31const modTime = Deno.statSync(file).mtime;32const speakerModTime = Deno.statSync(speakerFile).mtime;33if (modTime && speakerModTime && (speakerModTime > modTime)) {34return speakerFile;35}36}37return file;38}3940export function revealMultiplexExtras(41format: Format,42flags: PandocFlags,43): FormatExtras | undefined {44if (format.metadata[kRevealJsMultiplex]) {45// create speaker version w/ master46return {47postprocessors: [async (output: string) => {48// determine the multiplex secret and id (could be provided by the49// user, contained within existing speaker output, or created from50// scratch via a call to the multiplex url)51const token = await revealMultiplexToken(format, output);5253// read file54const content = await Deno.readTextFile(output);5556// generate speaker content57const speakerContent = withMultiplexToken(content, token);5859// generate client content (remove the secret and the speaker notes)60token.secret = null;61const clientContent = withMultiplexToken(content, token)62.replace(63/(\/\/ reveal\.js plugins\n\s*plugins: \[[^\[]+?)(RevealNotes,)([^\[]+?\])/,64"$1$3",65);6667// write client version68await Deno.writeTextFile(output, clientContent);6970// write speaker version71const speakerOutput = revealSpeakerOutput(output);72await Deno.writeTextFile(speakerOutput, speakerContent);7374// determine whether this is self-contained output75const selfContained = isSelfContainedOutput(76flags,77format,78speakerOutput,79);8081// If this is self contained, we should ingest dependencies82if (selfContained) {83await pandocIngestSelfContainedContent(84speakerOutput,85format.pandoc[kResourcePath],86);87}88}],89};90} else {91return undefined;92}93}9495function revealSpeakerOutput(output: string) {96const [dir, stem] = dirAndStem(output);97return join(dir, `${stem}-speaker.html`);98}99100interface RevealMultiplexToken {101secret: string | null;102id: string;103url: string;104}105106const kDefaultMultiplexUrl = "https://multiplex.up.railway.app/";107108async function revealMultiplexToken(109format: Format,110output: string,111): Promise<RevealMultiplexToken> {112// is it provided in config?113const multiplex = format.metadata[kRevealJsMultiplex] as RevealMultiplexToken;114if (115typeof multiplex === "object" && typeof (multiplex.secret) === "string" &&116typeof (multiplex.id) === "string"117) {118multiplex.url = multiplex.url || kDefaultMultiplexUrl;119return multiplex;120}121122// is it located inside the -speaker file?123const speakerOutput = revealSpeakerOutput(output);124if (existsSync(speakerOutput)) {125const speakerContent = await Deno.readTextFile(speakerOutput);126const initPos = speakerContent.lastIndexOf("Reveal.initialize({");127if (initPos !== -1) {128const initLines = lines(speakerContent.substring(initPos));129const kMultiplexStart = "'multiplex': {";130const kMultiplexEnd = "},";131for (const line of initLines) {132if (line.startsWith(kMultiplexStart) && line.endsWith(kMultiplexEnd)) {133const multiplexJson = "{ " +134line.substring(135kMultiplexStart.length,136line.length - kMultiplexEnd.length,137) + " }";138const multiplex = JSON.parse(multiplexJson) as RevealMultiplexToken;139if (!!multiplex.id && !!multiplex.secret) {140return multiplex;141}142}143}144}145}146147// provision a new token148const url = typeof multiplex === "object"149? (multiplex.url || kDefaultMultiplexUrl)150: kDefaultMultiplexUrl;151try {152const response = await fetch(pathWithForwardSlashes(join(url, "token")));153const jsonData = await response.json();154const multiplex = {155secret: jsonData.secret,156id: jsonData.socketId,157url,158};159return multiplex;160} catch (e) {161if (!(e instanceof Error)) {162throw e;163}164throw Error(165"Error attempting to provision multiplex token from '" + url + "': " +166e.message,167);168}169}170171function withMultiplexToken(content: string, token: RevealMultiplexToken) {172return content.replace(173/(Reveal.initialize\({[\s\S]*?'multiplex': )({.*?},)/g,174`$1${JSON.stringify(token)},`,175);176}177178179