Skip to content
This repository was archived by the owner on Apr 27, 2021. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
172 changes: 172 additions & 0 deletions index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
import * as http from "http";
import * as https from "https";
import * as fs from "fs";
import * as path from "path";

import express from "express";
import * as escapeHTML from "escape-html";
import { ServerOptions } from "https";

const app = express();

let httpPort = 80;

const htmlTop = `<!doctype html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8">
<link href="/css/main.css" rel="stylesheet" media="screen" type="text/css">
<title>MDN Code Samples</title>
</head>
<body>
<div class="page-wrapper">
<div class="mdn-header">
<h1>
MDN Code Samples
</h1>
</div>
<div class="mdn-content">
<p>
This site hosts <a href="https://github.com/mdn/samples-server/"> code samples</a>
for MDN Web Docs that require server assistance to operate, such as examples for WebSocket,
WebRTC, and other APIs.
</p>
</div>
<div class="mdn-filelist">
`;

const htmlBottom = `</div>
<div class="mdn-footer">
All text content on MDN is offered under the
<a href="http://creativecommons.org/licenses/by-sa/2.5/">CC-SA-BY</a> license,
version 2.5 or later. All sample code offered on this site is provided under the
<a href="https://creativecommons.org/publicdomain/zero/1.0/">CC0 (Public
Domain)</a> license and may be reused or repurposed without attribution.
</div>
</div>
</body>
</html>`;

app.get("/", (request, response) => {
let menuHTML = buildMenu("s");
let html = htmlTop + "\r" + menuHTML + "\r" + htmlBottom;
response.send(html);
});

app.use("/s", express.static(path.join(__dirname, "s")));
app.use("/css", express.static(path.join(__dirname, "css")));

// Try to load the key and certificate for HTTPS

let httpsOptions: ServerOptions = {};

try {
httpsOptions.key = fs.readFileSync("/etc/pki/tls/private/mdn-samples.mozilla.org.key");
httpsOptions.cert = fs.readFileSync("/etc/pki/tls/certs/mdn-samples.mozilla.org.crt");
} catch(err) {
console.error("Unable to load HTTPS cert and/or key; available on HTTP only: " + err);
httpsOptions = null;
}

let httpServer = http.createServer(app);
httpServer.listen(httpPort);
httpServer.on("error", (err: Error & {code: string}) => {
if (err.code === "EADDRINUSE") {
httpPort = 8888;
httpServer = http.createServer(app);
httpServer.listen(httpPort);
console.log("Listening on port " + httpPort);
} else {
console.error("HTTP startup error: " + err);
}
});

if (httpsOptions) {
let httpServer = https.createServer(httpsOptions, app);
httpServer.listen(443);
console.log("HTTPS listening on port 443");
}

const readJSONFile = <T>(pathname: string): T|null => {
const options = {
encoding: "utf8"
};

try {
let data = fs.readFileSync(pathname, options);
return JSON.parse(data);
} catch(err) {
console.error(`Error loading JSON data for file ${pathname}: ${err}`);
}

return null;
};

const buildMenuEntry = (manifest): string => {
let {name, docsUrl, description, pathname} = manifest;
let docsLink = `[<a href="${docsUrl}">Documentation</a>]`;
let dt = `<dt><a href="${pathname}">${name}</a></dt>`;
let dd = `<dd>${escapeHTML(description)}&nbsp;${docsLink}</dd>`;
return dt+"\n"+dd+"\n";
};

const buildMenuHTML = manifestList => {
let output = "";

manifestList.forEach(entry => {
output += buildMenuEntry(entry);
});
return output;
};

function getManifestFromDirectory(pathname: string): {pathname: string, name: string} {
let manifestPath = `${pathname}${path.sep}manifest.json`;
return readJSONFile(manifestPath);
}

const compareManifests = (a, b) => {
if (a.name < b.name) {
return -1;
}
else if (a.name > b.name) {
return 1;
}
return 0;
};

const loadAllManifests = (files, pathname) => {
let manifestList = [];

files.forEach(entry => {
if (entry.isDirectory()) {
const entryPath = `${pathname}${path.sep}${entry.name}`;
let manifest = getManifestFromDirectory(entryPath);
if (manifest) {
manifest.pathname = entryPath;
manifestList.push(manifest);
}
}
});
return manifestList.sort(compareManifests);
};

function buildMenu(pathname) {
let output = "";
const readdirOptions: { encoding: BufferEncoding | null; withFileTypes?: false } = {
encoding: "utf8",
withFileTypes: false //true # MUST BE FALSE
};

try {
let files = fs.readdirSync(pathname, readdirOptions);
let manifestList = loadAllManifests(files, pathname);

output = buildMenuHTML(manifestList);
} catch(err) {
console.error("Error reading directory: " + err);
return null;
}

output = `<dl>\n${output}</dl>\n`;
return output;
}
5 changes: 5 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,10 @@
"dependencies": {
"escape-html": "^1.0.3",
"express": "^4.16.4"
},
"devDependencies": {
"@types/escape-html": "0.0.20",
"@types/node": "^13.11.1",
"@types/websocket": "^1.0.0"
}
}
97 changes: 97 additions & 0 deletions s/webrtc-capturestill/capture.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
(() => {
// The width and height of the captured photo. We will set the
// width to the value defined here, but the height will be
// calculated based on the aspect ratio of the input stream.

const width = 320; // We will scale the photo width to this
let height = 0; // This will be computed based on the input stream

// |streaming| indicates whether or not we're currently streaming
// video from the camera. Obviously, we start at false.

let streaming = false;

// The various HTML elements we need to configure or control. These
// will be set by the startup() function.

let video: HTMLVideoElement;
let canvas: HTMLCanvasElement;
let photo: HTMLImageElement;
let startbutton: HTMLButtonElement;

// Capture a photo by fetching the current contents of the video
// and drawing it into a canvas, then converting that to a PNG
// format data URL. By drawing it on an offscreen canvas and then
// drawing that to the screen, we can change its size and/or apply
// other changes before drawing it.
const takepicture = () => {
const context = canvas.getContext('2d')!;
if (width && height) {
canvas.width = width;
canvas.height = height;
context.drawImage(video, 0, 0, width, height);

const data = canvas.toDataURL('image/png');
photo.setAttribute('src', data);
} else {
clearphoto();
}
};

const startup = () => {
video = document.getElementById('video') as HTMLVideoElement;
canvas = document.getElementById('canvas') as HTMLCanvasElement;
photo = document.getElementById('photo') as HTMLImageElement;
startbutton = document.getElementById('startbutton') as HTMLButtonElement;

navigator.mediaDevices.getUserMedia({video: true, audio: false})
.then((stream) => {
video.srcObject = stream;
video.play();
})
.catch(err =>
console.error("An error occurred: ", err)
);

video.addEventListener('canplay', ev => {
if (!streaming) {
height = video.videoHeight / (video.videoWidth/width);

// Firefox currently has a bug where the height can't be read from
// the video, so we will make assumptions if this happens.

if (isNaN(height)) {
height = width / (4/3);
}

video.setAttribute('width', width.toString());
video.setAttribute('height', height.toString());
canvas.setAttribute('width', width.toString());
canvas.setAttribute('height', height.toString());
streaming = true;
}
}, false);

startbutton.addEventListener('click', ev => {
takepicture();
ev.preventDefault();
}, false);

clearphoto();
};

// Fill the photo with an indication that none has been
// captured.
const clearphoto = () => {
const context = canvas.getContext('2d')!;
context.fillStyle = "#AAA";
context.fillRect(0, 0, canvas.width, canvas.height);

const data = canvas.toDataURL('image/png');
photo.setAttribute('src', data);
};

// Set up our event listener to run the startup process
// once loading is complete.
window.addEventListener('load', startup, false);
})();
9 changes: 9 additions & 0 deletions s/webrtc-capturestill/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"compilerOptions": {
"target": "ES2019",
"module": "commonjs",
"strict": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true
}
}
Loading