"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const http_1 = require("http");
const mUrl = require("url");
const fs = require("fs");
const mPath = require("path");
const dgram = require("dgram");
const MediaService_1 = require("./MediaService");
const PingAgent_1 = require("./PingAgent");
const utils_1 = require("./utils");
const socketI = require("../sockt/index");
const socketS = require("../sockt/io-server");
const version = '1.9';
const kMainPort = 7017;
const kSignallingPort = kMainPort + 4;
const kSignallingMulticastAddress = '236.0.0.2';
const kScreenBroadcastPort = kMainPort + 2;
const kScreenBroadcastMulticastAddress = '236.0.0.2';
const kStreamSourcePort = kMainPort + 1;
const kStreamMulticastAddress = '';
const kScreenBroadcastMaxWidth = 1280;
const kScreenBroadcastMaxHeight = 720;
const kIncomingAudioPort = 7018;
const sseHeaders = {
    "Content-Type": "text/event-stream",
    "Cache-Control": "no-store",
};
let signallingSocket;
const clients = Object.create(null);
const updateinfos = Object.create(null);
let classInfo = { className: '?', students: [], devices: [], teams: [] };
const defaultClassroomState = {
    screenCapture: { type: 'ScreenCapture', on: false },
    screenBroadcast: { type: 'ScreenBroadcast', on: false, disableMrdp: true },
    remoteDesktop: { type: 'RemoteDesktop', on: false, disableMrdp: true },
    lockScreen: { type: 'LockScreen', on: false },
    receiveFiles: { type: 'ReceiveFiles', tasks: [], urlTasks: [] },
    race: { type: 'Race', on: false },
    quiz: { type: 'Quiz', on: false },
    createTeams: { type: 'CreateTeams', on: false },
    teams: { type: 'Teams', teams: [] },
    ping: { type: 'Ping', on: false },
    presenter: { type: 'Presenter', id: '' },
    lessonRecord: { type: 'LessonRecord', on: false, data: null },
    countDown: { type: 'CountDown', on: false },
    selectStudent: { type: 'SelectStudent', on: false },
    screenRecovder: { type: "ScreenRecovder", on: false },
    recordScreen: { type: 'RecordScreen', stop: true },
    studentDeviceControl: { type: 'StudentDeviceControl', powerOff: false },
    signIns: { type: 'SignIns', on: false },
    startUpdate: { type: 'startUpdate', on: false }
};
let defaultScreenCaptureOption = { type: 'ScreenCapture', on: true, fps: 0.25, maxWidth: 250, maxHeight: 150 };
let classroomState = null;
let classroomStateMessages = null;
let sendFileState = Object.create(null);
let answerSheets = Object.create(null);
let answerSheetMetadata = Object.create(null);
let userContents = Object.create(null);
const staticFileMap = {
    '/test.htm': mPath.join(__dirname, '../test/mbnetworktest.htm'),
    '/': mPath.join(__dirname, '../test/index.htm'),
};
let procFFmpeg = null;
let localIP = '127.0.0.1';
const mediaService = new MediaService_1.MediaService();
const inboundSockets = new Set();
const router = {
    __proto__: null,
    GET: {
        __proto__: null,
        messages: handleSSE,
        classInfo: handleGetClassInfo,
        screenCapture: handleGetScreenCapture,
        classroomState: handleGetClassroomState,
        screenBroadcast: handleGetScreenBroadcast,
        audioCast: handleGetAudioCast,
        receiveFiles: handleGetReceiveFiles,
        userContent: handleGetUserContent,
        statistics: handleGetStatistics,
        chunk: handleChunk,
        testAlive: (req, res) => {
            res.writeHead(200);
            res.write('version: ' + version);
            res.end();
        },
        time: (req, res) => {
            res.writeHead(200, { 'Content-Type': 'application/json' });
            res.write(JSON.stringify({ type: 'Time', value: Date.now() }));
            res.end();
        },
        updateApk: handleGetUpdateApk,
        updateInfo: (req, res) => {
            res.writeHead(200);
            const infos = [];
            if (updateinfos) {
                Object.keys(updateinfos).forEach(uid => {
                    const info = updateinfos[uid];
                    infos.push({ uid: uid, uname: clients[uid].userInfo.uname, version: info.currentVersion, updateUrl: info.updateUrl });
                });
            }
            res.write(JSON.stringify(infos));
            res.end();
        },
        '@default': handleStatic,
    },
    POST: {
        __proto__: null,
        broadcast: handleBroadcastMessage,
        unicast: handleUnicastMessage,
        screenCapture: handlePostScreenCapture,
        classroomControl: handleClassroomControl,
        finishRace: handlePostFinishRace,
        answerSheet: handlePostAnswerSheet,
        selectTeam: handlePostSelectTeam,
        presenter: handlePostPresenter,
        imageCast: handlePostPresenter,
        mediaServiceConfig: handlePostMediaServiceConfig,
        audioCast: handlePostAudioCast,
        productionDisplay: handlePostPresenter,
        statistics: handlePostStatistics,
        clearStatistics: handleClearStatistics,
        shutdown: handlePostShutdown,
        interuptSSE: handleInteruptSSE,
        lessonRecord: handleLessonRecord,
        commitSignIn: handlePostCommitSignIn,
        stuInfo: handleStudentInfo
    },
    PUT: {
        __proto__: null,
        userContent: handlePutUserContent,
    },
    OPTIONS: {
        '@default': handleCorsPreflight,
    }
};
let remoteKey = '';
function toSseData(data) {
    return (typeof data === 'string' ? data : JSON.stringify(data)).replace(/[\r\n]/g, ' ');
}
function sendSSE(res, data) {
    if (!res.writable)
        return;
    if (data != null) {
        const msg = toSseData(data);
        res.write('data:' + msg + '\n\n');
    }
    else {
        res.write(':\n\n');
    }
}
function killProcess(proc) {
    return new Promise((resolve, reject) => {
        proc.on('close', code => resolve(code));
        proc.kill();
        setTimeout(() => reject('killProcess:timeout:' + proc.pid), 3000);
    });
}
function isSpecialUid(uid) {
    return /^admin$|^device\-/.test(uid);
}
function onRequest(req, res) {
    res.socket = req.socket;
    console.log(req.method + ' ' + req.url + ' , ip=' +
        req.socket.remoteAddress + ', remotePort=' + req.socket.remotePort);
    const pathParts = utils_1.getPathParts(req.url);
    const pUrl = mUrl.parse(req.url);
    if (allowCors(req)) {
        res.setHeader('Access-Control-Allow-Origin', req.headers['origin']);
    }
    var subRouter = router[req.method];
    var handler = subRouter ? subRouter[pathParts[0]] : null;
    if (subRouter && !handler)
        handler = subRouter['@default'];
    if (!handler) {
        res.writeHead(404);
        res.end();
    }
    else {
        let p, err;
        try {
            p = handler(req, res);
        }
        catch (e) {
            err = e;
        }
        (p || Promise.resolve())
            .catch(e => err = e)
            .then(() => {
            if (err) {
                console.error('onRequest: error: url= '
                    + req.url + ' , method=' + req.method, err);
                res.writeHead(err ? 500 : 200);
                res.end(err ? err + '' : '');
            }
            if (p && !res.headersSent) {
                res.writeHead(200);
                res.end();
            }
        });
    }
}
function handleCorsPreflight(req, res) {
    if (allowCors(req) && req.headers['access-control-request-method']) {
        res.setHeader('Access-Control-Allow-Origin', req.headers['origin']);
        res.writeHead(200, {
            'Access-Control-Allow-Origin': 'null',
            'Access-Control-Allow-Methods': 'POST, GET',
            'Access-Control-Max-Age': 600,
            'Access-Control-Allow-Headers': 'Content-Type'
        });
        res.end();
    }
    else {
        res.writeHead(404);
        res.end();
    }
}
function allowCors(req) {
    const origin = req.headers['origin'];
    return (origin === 'null' || origin && origin.startsWith('resource:'))
        && utils_1.isLocalHost(utils_1.extractIpAddress(req.socket.localAddress));
}
function initClassroomState() {
    classroomState = JSON.parse(JSON.stringify(defaultClassroomState));
    classInfo.teams = classroomState.teams.teams;
    updateClassroomStateMessages();
    sendFileState = Object.create(null);
    answerSheets = Object.create(null);
    answerSheetMetadata = Object.create(null);
    userContents = Object.create(null);
    mediaService.onCrash = onMediaServiceCrash;
    mediaService.onRecordError = onRecordScreenError;
}
const classroomControlHandler = {
    Reset: (msg) => {
        initClassroomState();
        if (msg.classInfo || msg.users) {
            if (msg.classInfo) {
                classInfo.className = msg.classInfo.className;
                classInfo.students = msg.classInfo.students;
                if (msg.classInfo.teams) {
                    classInfo.teams = classroomState.teams.teams = msg.classInfo.teams;
                }
            }
            else if (msg.users)
                classInfo.students = msg.users;
            console.log('Reset:users: ');
            Object.keys(clients).forEach(id => {
                if (!isSpecialUid(id)) {
                    var client = clients[id];
                    client.responses.forEach(res => res.end());
                    stopPing(client);
                    delete clients[id];
                }
            });
            msg.classInfo.students.forEach(info => {
                const client = clients[info.uid] = { userInfo: info, responses: new Set() };
                if (classroomState.ping.on)
                    startPing(client);
            });
        }
        sendClassroomStateSSE();
    },
    ScreenCapture: (msg) => {
        const oldSc = classroomState.screenCapture;
        classroomState.screenCapture = msg;
        if (!msg.uids)
            defaultScreenCaptureOption = msg;
        for (let id in clients) {
            if (msg.ignoreScreenBroadcast && classroomState.screenBroadcast.on
                && msg.uids && msg.uids.indexOf(id) >= 0) {
                unicastSSE(id, { type: 'ScreenBroadcast', on: false });
            }
            sendScreenCaptureSSE(id);
        }
        if ((!msg.on || !msg.uids) &&
            oldSc.on && oldSc.uids && oldSc.ignoreScreenBroadcast && classroomState.screenBroadcast.on) {
            oldSc.uids.forEach(uid => sendScreenBroadcastSSE(uid));
        }
    },
    LockScreen: (msg) => {
        classroomState.lockScreen = msg;
        updateClassroomStateMessages();
        broadcastSSE('', msg);
    },
    StudentDeviceControl: (msg) => {
        classroomState.studentDeviceControl = msg;
        updateClassroomStateMessages();
        broadcastSSE('', msg);
    },
    ScreenBroadcast: (msg) => {
        const defaultSbc = defaultClassroomState.screenBroadcast;
        if (msg.disableMrdp != null)
            defaultSbc.disableMrdp = msg.disableMrdp;
        if (msg.width != null)
            defaultSbc.width = msg.width;
        if (msg.height != null)
            defaultSbc.height = msg.height;
        if (msg.bitrate != null)
            defaultSbc.bitrate = msg.bitrate;
        if (msg.ffmpeg != null)
            mediaService.ffmpegCmd = defaultSbc.ffmpeg = msg.ffmpeg;
        if (msg.on) {
            return startScreenBroadcast(Object.assign({}, defaultSbc, msg));
        }
        else {
            return killScreenBroadcast(true);
        }
    },
    RemoteDesktop: (msg) => {
        const defaultSbc = defaultClassroomState.screenBroadcast;
        if (msg.disableMrdp != null)
            defaultSbc.disableMrdp = msg.disableMrdp;
        if (msg.width != null)
            defaultSbc.width = msg.width;
        if (msg.height != null)
            defaultSbc.height = msg.height;
        if (msg.bitrate != null)
            defaultSbc.bitrate = msg.bitrate;
        if (msg.ffmpeg != null)
            mediaService.ffmpegCmd = defaultSbc.ffmpeg = msg.ffmpeg;
        if (msg.on) {
            return startRemoteDesktop(Object.assign({}, defaultSbc, msg));
        }
        else {
            return killRemoteDesktop(true);
        }
    },
    SendFile: (msg) => startSendFiles(msg.path),
    SendFiles: (msg) => startSendFiles(msg.paths),
    SendInstallFile: (msg) => startSendInstallFile(msg.path),
    SendUrls: (msg) => startSendUrls(msg.items),
    DropPacket: (msg) => {
        if (mediaService)
            mediaService.packetDropRatio = msg.ratio;
    },
    ClearCache: (msg) => {
        broadcastSSE('', msg);
    },
    Signalling: (msg) => {
        if (msg.on)
            startSignalling();
        else
            stopSignalling();
    },
    ScreenRecovder: (msg) => {
        classroomState.screenRecovder = msg;
        updateClassroomStateMessages();
        broadcastSSE('', msg);
    },
    Race: (msg) => {
        if (msg.on)
            console.log('start race: ' + msg.id);
        else if (!msg.id)
            console.log('end race');
        classroomState.race = msg;
        updateClassroomStateMessages();
        broadcastSSE('', msg);
    },
    CountDown: (msg) => {
        classroomState.countDown = msg;
        updateClassroomStateMessages();
        broadcastSSE('', msg);
    },
    SelectStudent: (msg) => {
        classroomState.selectStudent = msg;
        updateClassroomStateMessages();
        broadcastSSE('', msg);
    },
    Quiz: (quiz) => {
        if (classroomState.quiz.on) {
            if (quiz.on) {
                if (quiz.expired) {
                    if (!classroomState.quiz.expired) {
                        classroomState.quiz.expired = true;
                        console.log('quiz expired manually: ' + classroomState.quiz.id);
                    }
                    else
                        throw 'bad message';
                }
                else {
                    console.log('end quiz: ' + classroomState.quiz.id);
                    broadcastSSE('', { type: 'Quiz', on: false });
                    classroomState.quiz = quiz;
                }
            }
            else if (!quiz.on) {
                console.log('end quiz:' + classroomState.quiz.id);
                classroomState.quiz = { type: 'Quiz', on: false };
            }
            else {
                throw 'bad message';
            }
        }
        else if (quiz.on && !quiz.expired) {
            classroomState.quiz = quiz;
        }
        else
            throw 'bad message';
        quiz = quiz;
        if (quiz.on && !quiz.expired || !quiz.on) {
            answerSheets = Object.create(null);
            answerSheetMetadata = Object.create(null);
            const imgs = new Set();
            const adminUC = userContents['admin'];
            if (quiz.on && adminUC) {
                try {
                    quiz.questions.forEach(ques => {
                        if (ques.body) {
                            ques.body.images.forEach(url => {
                                const spec = parseUserContentUrl(url);
                                if (spec.uid === 'admin' && adminUC[spec.subPath])
                                    imgs.add(spec.subPath);
                            });
                        }
                    });
                }
                catch (e) {
                    console.error(e);
                }
                ;
            }
            if (imgs.size) {
                for (let subPath in adminUC) {
                    if (imgs.has(subPath)) {
                        console.log('userContent admin/' + subPath + ' retained');
                    }
                    else {
                        delete adminUC[subPath];
                        console.log('userContent admin/' + subPath + ' removed');
                    }
                }
            }
            else {
                userContents = Object.create(null);
            }
        }
        if (classroomState.quiz.on && !classroomState.quiz.expired) {
            const id = classroomState.quiz.id;
            classroomState.quiz.startTime = Date.now();
            if (classroomState.quiz.lifeSpan > 0) {
                setTimeout(() => {
                    if (id === classroomState.quiz.id && !classroomState.quiz.expired) {
                        classroomState.quiz.expired = true;
                        console.log('quiz expired: ' + id);
                        broadcastSSE('', classroomState.quiz);
                    }
                }, classroomState.quiz.lifeSpan);
            }
            console.log('start quiz: ' + id);
        }
        updateClassroomStateMessages();
        broadcastSSE('', classroomState.quiz);
    },
    LessonRecord: (lessonRecord) => {
        classroomState.lessonRecord = lessonRecord;
        if (classroomState.lessonRecord.lifeSpan > 0) {
            setTimeout(() => {
                classroomState.lessonRecord.expired = true;
                if (classroomState.lessonRecord.expired) {
                    broadcastSSE('', classroomState.lessonRecord);
                }
            }, classroomState.lessonRecord.lifeSpan);
        }
        console.log('tserver: handler LessonRecord\'s liftSpan: ', classroomState.lessonRecord.lifeSpan);
        updateClassroomStateMessages();
        broadcastSSE('', classroomState.lessonRecord);
    },
    CreateTeams: (ct) => {
        if (ct.on && !(ct.teamCount > 1))
            throw 'Bad teamCount:' + ct.teamCount;
        classroomState.createTeams = ct;
        broadcastSSE('', ct);
        const teams = classroomState.teams.teams;
        teams.length = 0;
        if (ct.on) {
            for (let i = 1; i <= ct.teamCount; i++) {
                teams[i - 1] = { tid: i, score: 0, members: {} };
            }
            broadcastSSE2Admins('', classroomState.teams);
        }
        updateClassroomStateMessages();
    },
    Teams: (tc) => {
        if (!tc.teams)
            throw new Error('missing property Teams.teams');
        classroomState.teams = tc;
        classInfo.teams = tc.teams;
        broadcastSSE2Admins('', tc);
        updateClassroomStateMessages();
    },
    SignIns: (si) => {
        classroomState.signIns = si;
        updateClassroomStateMessages();
        if (!classroomState.signIns.on) {
            delete classroomState.signIns.startTime;
        }
        broadcastSSE('', classroomState.signIns);
    },
    StartUpdate: (sua) => {
        const startUpdateMsg = { type: "StartUpdate", on: sua.on };
        classroomState.startUpdate = startUpdateMsg;
        updateClassroomStateMessages();
        if (sua.on) {
            startUpdateApk(sua.urls);
        }
        broadcastSSE('', startUpdateMsg);
    },
    RecordScreen: handleRecordScreen,
    Ping: (ping) => {
        classroomState.ping = ping;
        for (let uid in clients) {
            const c = clients[uid];
            if (uid !== 'admin' && ping.on)
                startPing(c);
            else
                stopPing(c);
        }
        updateClassroomStateMessages();
        unicastSSE('admin', classroomState.ping);
    }
};
function handleGetClassInfo(req, res) {
    if (utils_1.isLocalHost(utils_1.extractIpAddress(req.socket.localAddress))) {
        res.writeHead(200, { 'Content-Type': 'application/json' });
        res.write(JSON.stringify(classInfo));
        res.end();
    }
    else {
        res.writeHead(403);
        res.end();
    }
}
function handleLocalAddressChange(curAddress) {
    const curIpV4 = utils_1.extractIpAddress(curAddress);
    if (curIpV4 && curIpV4 !== localIP && curIpV4 !== '127.0.0.1') {
        if (classroomState && classroomState.screenBroadcast.url) {
            classroomState.screenBroadcast.url.replace(localIP, curIpV4);
            updateClassroomStateMessages();
            broadcastSSE('admin', classroomState.screenBroadcast);
        }
        localIP = curIpV4;
        console.log('handleLocalAddressChange: changed to ' + curIpV4);
    }
}
function handleSSE(req, res) {
    console.log('handleSSE: ' + req.url);
    handleLocalAddressChange(req.socket.localAddress);
    const pathParts = utils_1.getPathParts(req.url);
    const id = pathParts[1];
    let client = clients[id];
    if (!client) {
        if (isSpecialUid(id)) {
            client = clients[id] =
                { userInfo: { uid: id, uname: id }, responses: new Set() };
            if (id.indexOf('device-') === 0)
                classInfo.devices.push(client.userInfo);
        }
        else {
            res.writeHead(200, "no such user", sseHeaders);
            res.end();
            return;
        }
    }
    res.setTimeout(0);
    res.on("close", function () {
        console.log('handleSSE: on get req close:', id);
        client.responses.delete(res);
        if (!client.responses.size) {
            client.userInfo.connected = false;
            console.log('on get req close: no connection:', id);
            unicastSSE('admin', { type: 'Leave', uid: id });
            maybeDropPresenter(id);
        }
    });
    res.writeHead(200, sseHeaders);
    res.write('\n');
    client.responses.add(res);
    const ipAddress = client.userInfo.ipAddress = utils_1.extractIpAddress(req.socket.remoteAddress);
    if (client.responses.size === 1) {
        client.userInfo.connected = true;
        if (id !== 'admin') {
            unicastSSE('admin', { type: 'Join', uid: id, ipAddress });
        }
    }
    if (id !== 'admin' && classroomState.ping.on)
        startPing(client, true);
    sendClassroomStateSSE(id, res);
    if (id === 'admin') {
        sendReceiveFilesProgresses(res);
        sendQuizState(res);
    }
}
function maybeDropPresenter(id) {
    if (id === classroomState.presenter.id) {
        classroomState.presenter = Object.assign({}, defaultClassroomState.presenter);
        updateClassroomStateMessages();
        broadcastSSE('', classroomState.presenter);
    }
}
function unicastSSE(receiverId, data) {
    const client = clients[receiverId];
    if (!client) {
        console.error('unicastSSE:no such client:' + receiverId);
        return;
    }
    data = toSseData(data);
    client.responses.forEach(res => {
        sendSSE(res, data);
    });
}
function broadcastSSE(senderId, data) {
    console.log('broadcastSSE from: ' + senderId + ', ', data);
    const msg = toSseData(data);
    for (var id in clients) {
        if (senderId !== id) {
            unicastSSE(id, msg);
        }
    }
}
function broadcastSSE2Admins(senderId, data) {
    console.log('broadcastSSE from: ' + senderId + ', ', data);
    const msg = toSseData(data);
    for (let id in clients) {
        if (senderId !== id && (id === 'admin' || id.startsWith('device-remote'))) {
            unicastSSE(id, msg);
        }
    }
}
function updateClassroomStateMessages() {
    classroomStateMessages = Object.keys(classroomState).filter(key => key !== 'screenCapture' && key !== 'screenBroadcast').map(key => toSseData(classroomState[key]));
}
function sendClassroomStateSSE(uid, res) {
    if (!classroomStateMessages)
        updateClassroomStateMessages();
    classroomStateMessages.forEach(msg => {
        if (uid) {
            if (res)
                sendSSE(res, msg);
            else
                unicastSSE(uid, msg);
        }
        else
            broadcastSSE('', msg);
    });
    if (uid) {
        sendScreenCaptureSSE(uid, res);
        sendScreenBroadcastSSE(uid, res);
    }
    else {
        for (var id in clients) {
            sendScreenCaptureSSE(id);
            sendScreenBroadcastSSE(id);
        }
    }
}
function sendScreenBroadcastSSE(uid, res) {
    let msg;
    if (classroomState.screenCapture.on && classroomState.screenCapture.ignoreScreenBroadcast &&
        (!classroomState.screenCapture.uids || classroomState.screenCapture.uids.indexOf(uid) >= 0)) {
        msg = { type: 'ScreenBroadcast', on: false };
    }
    else {
        msg = classroomState.screenBroadcast;
    }
    if (res)
        sendSSE(res, msg);
    else
        unicastSSE(uid, msg);
}
function sendScreenCaptureSSE(uid, res) {
    let msg;
    if (uid !== 'admin' && classroomState.screenCapture.uids) {
        if (classroomState.screenCapture.on && classroomState.screenCapture.uids.indexOf(uid) >= 0) {
            msg = Object.assign({}, classroomState.screenCapture);
            delete msg.uids;
        }
        else {
            msg = defaultScreenCaptureOption;
        }
    }
    else {
        msg = classroomState.screenCapture;
    }
    if (res)
        sendSSE(res, msg);
    else
        unicastSSE(uid, msg);
}
function sendReceiveFilesProgresses(res) {
    const rfp = { type: 'ReceiveFilesProgresses', progresses: {} };
    for (let tid in sendFileState) {
        rfp.progresses[tid] = sendFileState[tid].progresses;
    }
    sendSSE(res, rfp);
}
function sendQuizState(res) {
    if (classroomState.quiz.on) {
        const state = { type: 'QuizState', id: classroomState.quiz.id, answerSheets, answerSheetMetadata };
        sendSSE(res, state);
    }
}
function startPing(client, noForce) {
    if (noForce && client.pingAgent && client.userInfo.ipAddress === client.pingAgent.targetIp) {
        return;
    }
    stopPing(client);
    const ipAddress = client.userInfo.ipAddress, uid = client.userInfo.uid;
    if (!ipAddress) {
        return;
    }
    try {
        let pa = client.pingAgent = new PingAgent_1.PingAgent(ipAddress);
        pa.start();
        pa.onResponse = (error, time) => {
            unicastSSE('admin', {
                type: 'Pong', time, error, uid, ipAddress,
                sumTime: pa.sumTime, maxTime: pa.maxTime, okCount: pa.okCount, errorCount: pa.errorCount, pingCount: pa.pingCount,
            });
        };
    }
    catch (err) {
        console.error('startPing: error:', err);
    }
}
function stopPing(client) {
    if (client.pingAgent) {
        client.pingAgent.stop();
        client.pingAgent = null;
    }
}
function handleUnicastMessage(req, res) {
    const pathParts = utils_1.getPathParts(req.url);
    const id = pathParts[1];
    var client = clients[id];
    utils_1.readRequestBody(req).then(msg => {
        console.log('handleUnicastMessage: ' + req.url + ' , ' + msg);
        unicastSSE(id, msg);
        res.writeHead(200);
        res.end();
    }, err => {
        console.error(err);
        res.end();
    });
}
function handleBroadcastMessage(req, res) {
    const pathParts = utils_1.getPathParts(req.url);
    const id = pathParts[1];
    utils_1.readRequestBody(req).then(msg => {
        console.log('handleBroadcastMessage: ' + req.url + ' , ' + msg);
        broadcastSSE(id, msg);
        res.writeHead(200);
        res.end();
    }, err => {
        console.error(err);
        res.end();
    });
}
function handlePostScreenCapture(req, res) {
    console.log('handlePostScreenCapture: ' + req.url);
    const pathParts = utils_1.getPathParts(req.url);
    const id = pathParts[1], status = pathParts[2];
    if (!classroomState || !classroomState.screenCapture.on && !status) {
        console.log('ScreenCapture is off, rejected: ' + id);
        res.writeHead(404);
        res.end();
        return;
    }
    var content = [];
    utils_1.readRequestBody(req, true).then(data => {
        console.log('ScreenCapture from: ' + id);
        res.writeHead(200);
        res.end();
        var client = clients[id];
        if (client) {
            if (data && data.length)
                client.screenCaptureImage = data;
            if (!status || status !== client.userInfo.status) {
                unicastSSE('admin', { type: 'UpdateScreenCapture', uid: id, status });
            }
            client.userInfo.status = status;
        }
    });
}
function handleGetScreenCapture(req, res) {
    const pathParts = utils_1.getPathParts(req.url);
    const id = pathParts[1];
    var client = clients[id];
    if (!client || !client.screenCaptureImage) {
        res.writeHead(404);
        res.end();
        console.log('handleGetScreenCapture:', id, ', not found');
    }
    else {
        res.writeHead(200, {
            "Content-Type": 'image/png',
            "Cache-Control": "no-cache",
            'Content-Length': utils_1.buffersLength(client.screenCaptureImage),
        });
        utils_1.sendBinaryBody(res, client.screenCaptureImage);
    }
}
function handleClassroomControl(req, res) {
    const pathParts = utils_1.getPathParts(req.url);
    const id = pathParts[1];
    return utils_1.readRequestBody(req).then((data) => {
        const msg = JSON.parse(data);
        console.log('handleClassroomControl: ' + data);
        const keyed = msg;
        if (typeof keyed.remoteKey === 'string' &&
            (remoteKey == '' || keyed.remoteKey !== remoteKey) &&
            !utils_1.isLocalAddress(req.socket.remoteAddress)) {
            res.writeHead(403);
            res.end('wrong remoteKey');
            return;
        }
        delete keyed.remoteKey;
        const handler = classroomControlHandler[msg.type];
        return handler(msg, id);
    })
        .then(() => {
        res.writeHead(200);
        res.end();
    });
}
function handlePostFinishRace(req, res) {
    const race = classroomState.race;
    if (race.on && race.id) {
        const pathParts = utils_1.getPathParts(req.url);
        const raceId = pathParts[1], uid = pathParts[2];
        if (raceId === race.id && clients[uid]) {
            if (race.winner) {
                res.writeHead(403);
                res.end();
            }
            else {
                const info = clients[uid].userInfo;
                race.winner = { uid: info.uid, uname: info.uname };
                updateClassroomStateMessages();
                res.writeHead(200);
                res.end();
                broadcastSSE('', race);
            }
        }
        else {
            res.writeHead(404);
            res.end();
        }
    }
    else {
        res.writeHead(404);
        res.end();
    }
}
function handleLessonRecord(req, res) {
    const lessonRecord = classroomState.lessonRecord;
    if (lessonRecord.on && lessonRecord.expired) {
        const pathParts = utils_1.getPathParts(req.url);
        const uid = pathParts[2];
        if (clients[uid]) {
            utils_1.readRequestBody(req)
                .then((data) => {
                const answerSheet = JSON.parse(data);
                res.writeHead(200);
                res.end();
                const state = { type: 'LessonRecord', on: false };
                unicastSSE('admin', state);
            }).catch(err => {
                res.writeHead(500, err.toString());
                res.end();
            });
        }
        else {
            res.writeHead(404);
            res.end();
        }
    }
    else {
        res.writeHead(404);
        res.end();
    }
}
function handlePostAnswerSheet(req, res) {
    const quiz = classroomState.quiz;
    if (quiz.on && quiz.id && !quiz.expired) {
        const pathParts = utils_1.getPathParts(req.url);
        const quizId = pathParts[1], uid = pathParts[2];
        if (quizId === quiz.id && clients[uid]) {
            if (answerSheets[uid]) {
                res.writeHead(403);
                res.end();
            }
            else {
                utils_1.readRequestBody(req)
                    .then((data) => {
                    const answerData = JSON.parse(data);
                    const answerSheet = answerData[0];
                    const answerTimeUsed = answerData[1];
                    answerSheets[uid] = answerSheet;
                    res.writeHead(200);
                    res.end();
                    const state = {
                        type: 'QuizState', id: quiz.id,
                        answerSheets: { [uid]: answerSheet },
                        answerSheetMetadata: { [uid]: { timeUsed: answerTimeUsed } }
                    };
                    unicastSSE('admin', state);
                }).catch(err => {
                    res.writeHead(500, err.toString());
                    res.end();
                });
            }
        }
        else {
            res.writeHead(404);
            res.end();
        }
    }
    else {
        res.writeHead(404);
        res.end();
    }
}
function handlePostSelectTeam(req, res) {
    utils_1.readRequestBody(req)
        .then((data) => {
        if (!classroomState.createTeams.on) {
            res.writeHead(403);
            res.end();
            return;
        }
        const pathParts = utils_1.getPathParts(req.url);
        const uid = pathParts[1];
        const st = JSON.parse(data), tid = st.tid;
        if (tid >= 0 && tid <= classroomState.createTeams.teamCount) {
            st.uid = uid;
            const teams = classroomState.teams.teams;
            teams.forEach(team => delete team.members[uid]);
            if (tid > 0) {
                teams[tid - 1].members[uid] = '';
            }
            unicastSSE('admin', st);
            broadcastSSE2Admins('', classroomState.teams);
            updateClassroomStateMessages();
            res.writeHead(200);
        }
        else {
            res.writeHead(400);
        }
        res.end();
    }).catch(err => {
        res.writeHead(500, err.toString());
        res.end();
    });
}
function handlePostCommitSignIn(req, res) {
    utils_1.readRequestBody(req)
        .then((data) => {
        if (!classroomState.signIns.on) {
            res.writeHead(403);
            res.end();
            return;
        }
        const pathParts = utils_1.getPathParts(req.url);
        const uid = pathParts[1];
        const st = JSON.parse(data);
        if (uid) {
            st.uid = uid;
            unicastSSE('admin', st);
            res.writeHead(200);
        }
        else {
            res.writeHead(400);
        }
        res.end();
    }).catch(err => {
        res.writeHead(500, err.toString());
        res.end();
    });
}
function handleStudentInfo(req, res) {
    utils_1.readRequestBody(req)
        .then((data) => {
        const pathParts = utils_1.getPathParts(req.url);
        const uid = pathParts[1];
        const si = JSON.parse(data);
        if (uid) {
            updateinfos[uid] = si;
            unicastSSE('admin', { type: 'UpdateStudent' });
            res.writeHead(200);
        }
        else {
            res.writeHead(400);
        }
        res.end();
    }).catch(err => {
        res.writeHead(500, err.toString());
        res.end();
    });
}
function handlePostMediaServiceConfig(req, res) {
    return utils_1.readStream(req, 'json')
        .then((msg) => {
        if (msg.ffmpegCmd)
            mediaService.ffmpegCmd = msg.ffmpegCmd;
        if (msg.dataDir)
            mediaService.dataDir = msg.dataDir;
    });
}
function handlePostAudioCast(req, res) {
    mediaService.addAudioSrc(req);
    req.on('end', () => {
        res.writeHead(200);
        res.end();
    });
}
function handleGetAudioCast(req, res) {
    res.writeHead(200);
    mediaService.addAudioStreamerClient(res, utils_1.isLocalAddress(req.socket.remoteAddress), req.socket.remoteAddress);
}
function handleRecordScreen(rs) {
    let p;
    let resetBroadcast = false;
    const status = mediaService.recordStatus;
    console.log('handleRecordScreen: status=' + status);
    if (rs.stop) {
        console.log('handleRecordScreen:stop, cmd=' + rs.cmd);
        p = status !== 'stopped' ? mediaService.stopRecord() : Promise.resolve();
        p = p.then(() => {
            classroomState.recordScreen = rs;
            return maybeStopMediaService();
        });
        if (rs.cmd === 'autoSave') {
            p = p
                .then(() => mediaService.saveRecord(rs.targetName))
                .then(path => {
                unicastSSE('admin', { type: 'RecordScreenResult', path });
            });
        }
        else if (rs.cmd === 'delete') {
            p = p
                .then(() => mediaService.removeRecord());
        }
    }
    else if (rs.pause != null && rs.pause !== mediaService.isRecordPaused) {
        console.log('handleRecordScreen:change pause=' + rs.pause);
        if (rs.pause) {
            p = mediaService.pauseRecord().then(() => {
                classroomState.recordScreen = rs;
                return maybeStopMediaService();
            });
        }
        else {
            p = mediaService.resumeRecord();
            resetBroadcast = true;
        }
    }
    else if (rs.audioSrc) {
        const audioDevices = [], audioUrls = [], videoUrls = ['http://localhost:' + kMainPort + '/screenBroadcast'];
        for (let src of rs.audioSrc) {
            if (src === '@post') {
                audioUrls.push('http://localhost:' + kMainPort + '/audioCast');
            }
            else {
                audioDevices.push(src);
            }
        }
        p = mediaService.startRecord({ audioDevices, audioUrls, videoUrls });
        resetBroadcast = true;
    }
    else {
        broadcastSSE('', rs);
    }
    if (resetBroadcast) {
        if (classroomState.screenBroadcast.on) {
            broadcastSSE('', defaultClassroomState.screenBroadcast);
            setTimeout(() => broadcastSSE('', classroomState.screenBroadcast), 100);
        }
        if (classroomState.remoteDesktop.on) {
            broadcastSSE('', defaultClassroomState.remoteDesktop);
            setTimeout(() => broadcastSSE('', classroomState.remoteDesktop), 100);
        }
    }
    if (p) {
        return p.then(() => {
            classroomState.recordScreen = rs;
            updateClassroomStateMessages();
            broadcastSSE('', rs);
            console.log('handleRecordScreen:ok, status=' + mediaService.recordStatus);
        });
    }
    else {
        console.log('handleRecordScreen:no op.');
    }
}
const presenterHandlers = {
    Presenter: handlePresenterState,
    NavigatePresenter: handleNavigatePresenter,
    ImageCast: handleImageCast,
    VideoCast: handleVideoCast,
    SetRemoteKey: handleSetRemoteKey,
    HelloPresenter: handleHelloPresenter,
    ProductionDisplay: handleProductionDisplay,
    StrokeControl: handleStrokeControl,
    ProductionDisplayList: handleProductionDisplayList
};
function handlePostPresenter(req, res) {
    utils_1.readRequestBody(req)
        .then((data) => {
        const m = JSON.parse(data), keyed = m;
        if ((!keyed.remoteKey || keyed.remoteKey !== remoteKey) && !utils_1.isLocalAddress(req.socket.remoteAddress)) {
            res.writeHead(403);
            res.end('no remoteKey or wrong remoteKey');
            return;
        }
        delete keyed.remoteKey;
        const handler = presenterHandlers[m.type];
        handler(m, req, res);
        if (!res.headersSent) {
            res.writeHead(200);
            res.end();
        }
    }, err => {
        res.writeHead(500);
        res.end(err.toString());
    });
}
function handlePresenterState(m) {
    Object.assign(classroomState.presenter, m);
    updateClassroomStateMessages();
    broadcastSSE('', m);
}
function handleHelloPresenter(m) {
    broadcastSSE('', m);
}
function handleNavigatePresenter(m) {
    if (!m.id)
        m.id = classroomState.presenter.id;
    unicastSSE(classroomState.presenter.id, m);
}
function handleImageCast(m) {
    broadcastSSE('', m);
}
function handleProductionDisplay(m) {
    broadcastSSE('', m);
}
function handleProductionDisplayList(m) {
    broadcastSSE('', m);
}
function handleStrokeControl(m) {
    broadcastSSE('', m);
}
function handleVideoCast(m) {
    broadcastSSE('', m);
}
function handleSetRemoteKey(m) {
    remoteKey = m.value || '';
    broadcastSSE('', { type: 'SetRemoteKey', value: '' });
}
function handleGetClassroomState(req, res) {
}
function handlePostStatistics(req, res) {
    console.log('handlePostStatistics: ' + req.url);
    const pathParts = utils_1.getPathParts(req.url);
    const id = pathParts[1];
    var client = clients[id];
    console.log('post req to:', id);
    if (!client) {
        res.writeHead(404);
        res.end();
        console.log('post req to:', id, ', not found');
    }
    else {
        var content = '';
        req.on('data', function (data) {
            content += data;
        });
        req.on('end', function () {
            res.writeHead(200);
            res.end();
            console.log('received Statistics from:', id, ', content: ' + content);
            client.statistics = content;
            var cc = clients['admin'];
            if (cc)
                cc.responses.forEach(function (cRes) { sendSSE(cRes, 'updateStatistics'); });
        });
    }
}
function handleGetStatistics(req, res) {
    console.log('handleGetStatistics: ' + req.url);
    const pathParts = utils_1.getPathParts(req.url);
    const id = pathParts[1];
    if (id) {
        res.writeHead(404);
        res.end();
    }
    else {
        res.writeHead(200, {
            "Content-Type": 'application/json',
            "Cache-Control": "no-cache",
        });
        var content = '[' + Object.keys(clients).
            map(function (key) { return clients[key].statistics ? clients[key].statistics : '{ "clientId": "' + key + '"}'; }).join(',') + ']';
        res.end(content);
    }
}
function handleClearStatistics(req, res) {
    console.log('handleClearStatistics: ' + req.url);
    for (var key in clients)
        clients[key].statistics = null;
    res.writeHead(200);
    res.end();
}
const mimeMap = {
    __proto__: null,
    js: "text/javascript",
    css: "text/css",
    htm: "text/html",
    html: "text/html",
    xml: "text/xml"
};
function sendFile(path, res, onprogress) {
    fs.stat(path, function (error, stats) {
        if (error) {
            res.writeHead(404);
            res.end();
            console.warn('sendFile:not found:' + path);
        }
        else if (!stats.isFile()) {
            res.writeHead(403);
            res.end();
            console.warn('sendFile:not file:' + path);
        }
        else {
            const raw = fs.createReadStream(path);
            const ei = path.lastIndexOf('.'), ext = ei > 0 ? path.slice(ei + 1) : '';
            const mime = mimeMap[ext] || 'application/oct-stream';
            res.writeHead(200, {
                "Content-Type": mime,
                'Cache-Control': 'no-cache',
                'Content-Length': stats.size,
                "Last-Modified": stats.mtime.toUTCString()
            });
            raw.pipe(res);
            raw.on('end', () => onprogress && onprogress(stats.size, stats.size));
            console.log('sendFile:sending:' + path);
        }
    });
}
function handleStatic(req, res) {
    console.log('handleStatic: ' + req.url);
    const pathname = mUrl.parse(req.url).pathname;
    const pathParts = utils_1.getPathParts(req.url);
    const path = staticFileMap[pathname] ?
        staticFileMap[pathname] :
        ((pathParts[0] === 'test' || pathParts[0] === 'client') ?
            mPath.join(__dirname, '..', ...pathParts) :
            mPath.join(process.cwd(), ...pathParts));
    console.log('handleStatic:path: ' + path);
    sendFile(path, res);
}
function handlePutUserContent(req, res) {
    const spec = parseUserContentUrl(req.url);
    if (clients[spec.uid] && spec.subPath) {
        utils_1.readRequestBody(req, true).then((data) => {
            if (!userContents[spec.uid]) {
                userContents[spec.uid] = { [spec.subPath]: data };
            }
            else {
                userContents[spec.uid][spec.subPath] = data;
            }
            maybeDropUserContentLater(spec.uid, spec.subPath, data);
            console.log('handlePutUserContent:uid=' + spec.uid + ', subPath=' + spec.subPath);
            res.writeHead(200);
            res.end();
        }).catch(err => {
            res.writeHead(500);
            res.end();
        });
    }
    else {
        res.writeHead(404);
        res.end();
    }
}
function maybeDropUserContentLater(uid, subPath, data) {
    if (!uid.startsWith('device-remote'))
        return;
    setTimeout(() => {
        if (userContents[uid] && userContents[uid][subPath] === data) {
            delete userContents[uid][subPath];
            console.log('auto-removed /userContent/' + uid + '/' + subPath);
        }
    }, 180000);
}
function parseUserContentUrl(url) {
    const pathParts = utils_1.getPathParts(url);
    const uid = pathParts[1], subPath = pathParts.slice(2).join('/');
    return pathParts[0] === 'userContent' ? { uid, subPath } : {};
}
function handleGetUserContent(req, res) {
    const spec = parseUserContentUrl(req.url);
    const data = userContents[spec.uid] && userContents[spec.uid][spec.subPath];
    if (data) {
        res.writeHead(200, { 'content-length': utils_1.buffersLength(data) });
        utils_1.sendBinaryBody(res, data);
    }
    else {
        res.writeHead(404);
        res.end();
    }
}
function handleGetReceiveFiles(req, res) {
    const pathParts = utils_1.getPathParts(req.url);
    const uid = pathParts[1], tid = pathParts[2], fileName = pathParts[3], rec = sendFileState[tid];
    if (!((pathParts.length === 4) && clients[uid] && rec)) {
        res.writeHead(404);
        res.end();
    }
    else {
        rec.progresses[uid] = 0;
        sendFile(rec.path, res, (loaded, total) => {
            rec.progresses[uid] = loaded;
            unicastSSE('admin', {
                type: 'ReceiveFilesProgresses',
                progresses: { [tid]: { [uid]: loaded } }
            });
        });
    }
}
function handleGetUpdateApk(req, res) {
    const pathParts = utils_1.getPathParts(req.url);
    const uid = pathParts[1], extName = mPath.extname(pathParts[2]), rec = sendFileState[extName];
    if (!(clients[uid] && rec && classroomState.startUpdate.on)) {
        res.writeHead(404);
        res.end();
    }
    else {
        rec.progresses[uid] = 0;
        sendFile(rec.path, res, (loaded, total) => {
            rec.progresses[uid] = loaded;
            unicastSSE('admin', {
                type: 'UpdateApkProgresses',
                progresses: { [extName]: { [uid]: loaded } }
            });
        });
    }
}
const getSendFileTaskId = (function () {
    let sn = 0, base = Date.now();
    return () => base + '-' + (sn++);
})();
function stat(path) {
    return new Promise((resolve, reject) => {
        fs.stat(path, function (error, stats) {
            if (error) {
                reject(error);
            }
            else {
                resolve(stats);
            }
        });
    });
}
function startUpdateApk(paths) {
    Promise.all(paths.map(path => stat(path)))
        .then(statlist => {
        const progresses = Object.create(null);
        paths.forEach((path, i) => {
            const fileName = mPath.basename(path);
            const extName = mPath.extname(path);
            const rec = Object.create(null);
            rec.path = path;
            rec.progresses = Object.create(null);
            for (let uid in clients) {
                if (uid !== 'admin')
                    rec.progresses[uid] = 0;
            }
            sendFileState[extName] = rec;
            progresses[extName] = rec.progresses;
        });
        unicastSSE('admin', { type: 'UpdateApkProgresses', progresses });
    }, error => {
        unicastSSE('admin', { type: 'SendUpdateApk', paths: paths, error: error });
    });
}
function startSendFiles(pathOrPaths) {
    const paths = Array.isArray(pathOrPaths) ? pathOrPaths : [pathOrPaths];
    Promise.all(paths.map(path => stat(path)))
        .then(statList => {
        const progresses = Object.create(null);
        paths.forEach((path, i) => {
            const fileName = mPath.basename(path);
            const tid = getSendFileTaskId();
            classroomState.receiveFiles.tasks.push({
                id: tid,
                fileName: fileName,
                fileSize: statList[i].size,
                startTime: Date.now(),
            });
            const rec = Object.create(null);
            rec.path = path;
            rec.progresses = Object.create(null);
            for (let uid in clients) {
                if (uid !== 'admin')
                    rec.progresses[uid] = 0;
            }
            sendFileState[tid] = rec;
            progresses[tid] = rec.progresses;
        });
        broadcastSSE('', classroomState.receiveFiles);
        updateClassroomStateMessages();
        unicastSSE('admin', { type: 'ReceiveFilesProgresses', progresses });
    }, error => {
        console.error('startSendFile:', paths, error);
        unicastSSE('admin', { type: 'SendFiles', paths: paths, error: error });
    });
}
function startSendInstallFile(path) {
    fs.stat(path, function (error, stats) {
        if (error) {
            console.error('startSendInstallFile:' + path, error);
            unicastSSE('admin', { type: 'SendInstallFile', path: path, error: error });
        }
        else {
            const fileName = mPath.basename(path);
            const url = '/' + encodeURIComponent(fileName);
            const absUrl = 'http://' + localIP + ':' + kMainPort + url;
            staticFileMap[url] = path;
            broadcastSSE('admin', { type: 'Install', url: absUrl });
        }
    });
}
function startSendUrls(items) {
    const rfs = classroomState.receiveFiles;
    items.forEach(sut => rfs.urlTasks.push({
        id: getSendFileTaskId(),
        url: sut.url,
        tags: sut.tags,
        fileName: sut.name,
        fileSize: sut.size || 0,
        startTime: Date.now(),
    }));
    updateClassroomStateMessages();
    broadcastSSE('', classroomState.receiveFiles);
}
const chunks = new Map();
function getChunk(size) {
    console.log('getChunk: ' + size);
    if (chunks.has(size))
        return chunks.get(size);
    else if (!(size >= 0 && size <= 10 * 1000 * 1000)) {
        console.error('getChunk: illegal size');
        return null;
    }
    else {
        console.log('getChunk: creating ' + size);
        if (chunks.size > 4)
            chunks.clear();
        const buf = new Buffer(size);
        for (var i = 0; i < buf.length; i++) {
            buf[i] = Math.random() * 256;
        }
        chunks.set(size, buf);
        return buf;
    }
}
function handleChunk(req, res) {
    const pathParts = utils_1.getPathParts(req.url);
    const id = pathParts[1], size = pathParts[2];
    const item = getChunk(parseInt(size));
    if (item) {
        res.writeHead(200, 'ok', {
            'Access-Control-Allow-Origin': '*',
            'Content-Type': 'application/oct-stream',
            'Cache-Control': 'no-store',
            'Content-Length': '' + item.length
        });
        res.end(item);
    }
    else {
        res.writeHead(403);
        res.end();
    }
}
function onMediaServiceCrash(name, code, signal) {
    const msg = '媒体服务崩溃，请尝试重启服务。子进程 "' + name +
        '" 崩溃，code=' + code + ', signal=' + signal + ' 。';
    console.error('onMediaServiceCrash: ' + msg);
    killScreenBroadcast(false, msg);
    killRemoteDesktop(false, msg);
    pauseRecordScreen(false, msg);
}
function onRecordScreenError(err) {
    console.error('onRecordScreenError: ' + err);
    pauseRecordScreen(false, '' + err);
}
function setScreenBroadcastUrls(options) {
    options.url = 'http://' + localIP + ':' + kMainPort + '/screenBroadcast';
    options.urls = {
        mrdp: options.disableMrdp ? undefined : 'mrdp://' + kScreenBroadcastMulticastAddress + ':' + kScreenBroadcastPort,
        http: options.url,
    };
}
function startScreenBroadcast(options) {
    return (mediaService.isGrabbing ? Promise.resolve() : mediaService.startScreenGrab())
        .then(() => {
        setScreenBroadcastUrls(options);
        classroomState.screenBroadcast = options;
        for (let id in clients) {
            sendScreenBroadcastSSE(id);
        }
    }, err => {
        console.error('startScreenBroadcast: 启动失败:', err);
        throw err;
    });
}
function killScreenBroadcast(alwaysSendMsg, err) {
    if (alwaysSendMsg || classroomState.screenBroadcast.on) {
        classroomState.screenBroadcast = Object.assign({}, defaultClassroomState.screenBroadcast);
        broadcastSSE('', Object.assign({ error: err }, classroomState.screenBroadcast));
    }
    return maybeStopMediaService();
}
function pauseRecordScreen(alwaysSendMsg, err) {
    return mediaService.pauseRecord().then(() => {
        classroomState.recordScreen.pause = true;
        updateClassroomStateMessages();
        broadcastSSE('', Object.assign({ error: err }, classroomState.recordScreen));
        maybeStopMediaService();
    });
}
function startRemoteDesktop(options) {
    return (mediaService.isGrabbing ? Promise.resolve() : mediaService.startScreenGrab())
        .then(() => {
        setScreenBroadcastUrls(options);
        classroomState.remoteDesktop = options;
        updateClassroomStateMessages();
        broadcastSSE('', options);
    }, err => {
        console.error('startRemoteDesktop: 启动失败:', err);
        throw err;
    });
}
function killRemoteDesktop(alwaysSendMsg, err) {
    if (alwaysSendMsg || classroomState.remoteDesktop.on) {
        classroomState.remoteDesktop = Object.assign({}, defaultClassroomState.remoteDesktop);
        updateClassroomStateMessages();
        broadcastSSE('', Object.assign({ error: err }, classroomState.remoteDesktop));
    }
    return maybeStopMediaService();
}
function handleGetScreenBroadcast(req, res) {
    console.log('handleGetScreenBroadcast: ' + req.url);
    const pathParts = utils_1.getPathParts(req.url);
    const id = pathParts[1] || utils_1.extractIpAddress(req.socket.remoteAddress);
    res.writeHead(200, 'ok', {
        'Content-Type': 'video/M2PT',
        'Cache-Control': 'no-store',
    });
    mediaService.addLiveStreamerClient(res, utils_1.isLocalAddress(req.socket.remoteAddress), id);
}
function maybeStopMediaService() {
    if (!classroomState.screenBroadcast.on && !classroomState.remoteDesktop.on
        && (classroomState.recordScreen.stop || classroomState.recordScreen.pause)) {
        return mediaService.pauseAll();
    }
    else
        return Promise.resolve();
}
function startSignalling() {
    stopSignalling();
    const sock = signallingSocket = dgram.createSocket('udp4');
    console.log('startSignalling:' + kSignallingMulticastAddress +
        ':' + kSignallingPort);
    const msg = new Buffer(JSON.stringify({
        type: 'Signalling'
    }));
    let firstSent = false;
    const timer = setInterval(() => {
        sock.send(msg, 0, msg.length, kSignallingPort, kSignallingMulticastAddress);
        if (!firstSent)
            console.log('startSignalling:sending');
        firstSent = true;
    }, 500);
    const end = (err) => {
        if (sock === signallingSocket)
            signallingSocket = null;
        clearInterval(timer);
        if (err)
            console.error('startSignalling:error', err);
        else
            console.log('startSignalling:closed');
    };
    sock.on('listening', () => {
        console.log('startSignalling:addMembership:' + kSignallingMulticastAddress);
        sock.addMembership(kSignallingMulticastAddress);
    });
    sock.on('close', end);
    sock.on('error', end);
}
function stopSignalling() {
    if (signallingSocket) {
        signallingSocket.close();
        signallingSocket = null;
    }
}
function handlePostShutdown(req, res) {
    if (utils_1.isLocalHost(utils_1.extractIpAddress(req.socket.localAddress))) {
        console.error('handlePostShutdown: accepted from address ' + req.socket.localAddress);
        res.writeHead(200);
        res.end('', shutdown);
    }
    else {
        res.writeHead(403);
        const msg = 'handlePostShutdown: not allowed from remote address ' + req.socket.localAddress;
        res.end(msg);
        console.error(msg);
        return;
    }
}
function handleInteruptSSE(req, res) {
    if (utils_1.isLocalHost(utils_1.extractIpAddress(req.socket.localAddress))) {
        console.error('handleInteruptSSE: accepted from address ' + req.socket.localAddress);
        res.writeHead(200);
        res.end('');
        for (let id in clients) {
            clients[id].responses.forEach(res => res.socket.destroy());
        }
    }
    else {
        res.writeHead(403);
        const msg = 'handleInteruptSSE: not allowed from remote address ' + req.socket.localAddress;
        res.end(msg);
        console.error(msg);
        return;
    }
}
function shutdown() {
    console.log('[shutdown] kill screen broadcast');
    killScreenBroadcast();
    killRemoteDesktop();
    console.log('[shutdown] close server');
    server.close();
    inboundSockets.forEach(sock => sock.end());
    console.log('[shutdown] exit');
    process.exit(0);
}
function onConnection(sock) {
    inboundSockets.add(sock);
    console.log('an inbound socket connected, ip=' + sock.remoteAddress + ', remotePort=' + sock.remotePort);
    sock.on('close', () => {
        inboundSockets.delete(sock);
        console.log('an inbound socket closed, ip=' + sock.remoteAddress + ', remotePort=' + sock.remotePort);
    });
    sock.on('timeout', () => {
        console.log('an inbound socket timeout, ip=' + sock.remoteAddress + ', remotePort=' + sock.remotePort);
    });
}
initClassroomState();
const server = http_1.createServer()
    .on('request', utils_1.trapError)
    .on('request', onRequest)
    .on('connection', onConnection)
    .listen(kMainPort);
console.log('listening ' + kMainPort);
setInterval(() => {
    for (let id in clients) {
        clients[id].responses.forEach(res => sendSSE(res, null));
    }
}, 10000);
process.stdin.setEncoding('utf8');
process.stdin.on('readable', () => {
    var chunk;
    while (chunk = process.stdin.read()) {
        const line = chunk.replace(/\r|\n/g, '');
        if (line === 'shutdown') {
            shutdown();
        }
    }
});
//# sourceMappingURL=tserver.js.map