123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484 |
- /* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
- define(["require", "util", "channels", "jquery", "storage"], function (require, util, channels, $, storage) {
- var DEBUG = true;
- // This is the amount of time in which a hello-back must be received after a hello
- // for us to respect a URL change:
- var HELLO_BACK_CUTOFF = 1500;
- var session = util.mixinEvents(util.Module("session"));
- var assert = util.assert;
- // We will load this module later (there's a circular import):
- var peers;
- // This is the hub we connect to:
- session.shareId = null;
- // This is the ID that identifies this client:
- session.clientId = null;
- session.router = channels.Router();
- // Indicates if TogetherJS has just started (not continuing from a saved session):
- session.firstRun = false;
- // This is the key we use for localStorage:
- var localStoragePrefix = "togetherjs.";
- // This is the channel to the hub:
- var channel = null;
- // Setting, essentially global:
- session.AVATAR_SIZE = 90;
- var MAX_SESSION_AGE = 30*24*60*60*1000; // 30 days
- /****************************************
- * URLs
- */
- var includeHashInUrl = TogetherJS.config.get("includeHashInUrl");
- TogetherJS.config.close("includeHashInUrl");
- var currentUrl = (location.href + "").replace(/\#.*$/, "");
- if (includeHashInUrl) {
- currentUrl = location.href;
- }
- session.hubUrl = function (id) {
- id = id || session.shareId;
- assert(id, "URL cannot be resolved before TogetherJS.shareId has been initialized");
- TogetherJS.config.close("hubBase");
- var hubBase = TogetherJS.config.get("hubBase");
- return hubBase.replace(/\/*$/, "") + "/hub/" + id;
- };
- session.shareUrl = function () {
- assert(session.shareId, "Attempted to access shareUrl() before shareId is set");
- var hash = location.hash;
- var m = /\?[^#]*/.exec(location.href);
- var query = "";
- if (m) {
- query = m[0];
- }
- hash = hash.replace(/&?togetherjs-[a-zA-Z0-9]+/, "");
- hash = hash || "#";
- return location.protocol + "//" + location.host + location.pathname + query +
- hash + "&togetherjs=" + session.shareId;
- };
- session.recordUrl = function () {
- assert(session.shareId);
- var url = TogetherJS.baseUrl.replace(/\/*$/, "") + "/togetherjs/recorder.html";
- url += "#&togetherjs=" + session.shareId + "&hubBase=" + TogetherJS.config.get("hubBase");
- return url;
- };
- /* location.href without the hash */
- session.currentUrl = function () {
- if (includeHashInUrl) {
- return location.href;
- } else {
- return location.href.replace(/#.*/, "");
- }
- };
- /****************************************
- * Message handling/dispatching
- */
- session.hub = util.mixinEvents({});
- var IGNORE_MESSAGES = TogetherJS.config.get("ignoreMessages");
- if (IGNORE_MESSAGES === true) {
- DEBUG = false;
- IGNORE_MESSAGES = [];
- }
- // These are messages sent by clients who aren't "part" of the TogetherJS session:
- var MESSAGES_WITHOUT_CLIENTID = ["who", "invite", "init-connection"];
- // We ignore incoming messages from the channel until this is true:
- var readyForMessages = false;
- function openChannel() {
- assert(! channel, "Attempt to re-open channel");
- console.info("Connecting to", session.hubUrl(), location.href);
- var c = channels.WebSocketChannel(session.hubUrl());
- c.onmessage = function (msg) {
- if (! readyForMessages) {
- if (DEBUG) {
- console.info("In (but ignored for being early):", msg);
- }
- return;
- }
- if (DEBUG && IGNORE_MESSAGES.indexOf(msg.type) == -1) {
- console.info("In:", msg);
- }
- if (! peers) {
- // We're getting messages before everything is fully initialized
- console.warn("Message received before all modules loaded (ignoring):", msg);
- return;
- }
- if ((! msg.clientId) && MESSAGES_WITHOUT_CLIENTID.indexOf(msg.type) == -1) {
- console.warn("Got message without clientId, where clientId is required", msg);
- return;
- }
- if (msg.clientId) {
- msg.peer = peers.getPeer(msg.clientId, msg);
- }
- if (msg.type == "hello" || msg.type == "hello-back" || msg.type == "peer-update") {
- // We do this here to make sure this is run before any other
- // hello handlers:
- msg.peer.updateFromHello(msg);
- }
- if (msg.peer) {
- msg.sameUrl = msg.peer.url == currentUrl;
- if (!msg.peer.isSelf) {
- msg.peer.updateMessageDate(msg);
- }
- }
- session.hub.emit(msg.type, msg);
- TogetherJS._onmessage(msg);
- };
- channel = c;
- session.router.bindChannel(channel);
- }
- session.send = function (msg) {
- if (DEBUG && IGNORE_MESSAGES.indexOf(msg.type) == -1) {
- console.info("Send:", msg);
- }
- msg.clientId = session.clientId;
- channel.send(msg);
- };
- session.appSend = function (msg) {
- var type = msg.type;
- if (type.search(/^togetherjs\./) === 0) {
- type = type.substr("togetherjs.".length);
- } else if (type.search(/^app\./) === -1) {
- type = "app." + type;
- }
- msg.type = type;
- session.send(msg);
- };
- /****************************************
- * Standard message responses
- */
- /* Always say hello back, and keep track of peers: */
- session.hub.on("hello hello-back", function (msg) {
- if (msg.type == "hello") {
- sendHello(true);
- }
- if (session.isClient && (! msg.isClient) &&
- session.firstRun && session.timeHelloSent &&
- Date.now() - session.timeHelloSent < HELLO_BACK_CUTOFF) {
- processFirstHello(msg);
- }
- });
- session.hub.on("who", function (msg) {
- sendHello(true);
- });
- function processFirstHello(msg) {
- if (! msg.sameUrl) {
- var url = msg.url;
- if (msg.urlHash) {
- url += msg.urlHash;
- }
- require("ui").showUrlChangeMessage(msg.peer, url);
- location.href = url;
- }
- }
- session.timeHelloSent = null;
- function sendHello(helloBack) {
- var msg = session.makeHelloMessage(helloBack);
- if (! helloBack) {
- session.timeHelloSent = Date.now();
- peers.Self.url = msg.url;
- }
- session.send(msg);
- }
- session.makeHelloMessage = function (helloBack) {
- var msg = {
- name: peers.Self.name || peers.Self.defaultName,
- avatar: peers.Self.avatar,
- color: peers.Self.color,
- url: session.currentUrl(),
- urlHash: location.hash,
- // FIXME: titles update, we should track those changes:
- title: document.title,
- rtcSupported: session.RTCSupported,
- isClient: session.isClient
- };
- if (helloBack) {
- msg.type = "hello-back";
- } else {
- msg.type = "hello";
- msg.clientVersion = TogetherJS.version;
- }
- if (! TogetherJS.startup.continued) {
- msg.starting = true;
- }
- // This is a chance for other modules to effect the hello message:
- session.emit("prepare-hello", msg);
- return msg;
- };
- /****************************************
- * Lifecycle (start and end)
- */
- // These are Javascript files that implement features, and so must
- // be injected at runtime because they aren't pulled in naturally
- // via define().
- // ui must be the first item:
- var features = ["peers", "ui", "chat", "webrtc", "cursor", "startup", "videos", "forms", "visibilityApi", "youtubeVideos"];
- function getRoomName(prefix, maxSize) {
- var findRoom = TogetherJS.config.get("hubBase").replace(/\/*$/, "") + "/findroom";
- return $.ajax({
- url: findRoom,
- dataType: "json",
- data: {prefix: prefix, max: maxSize}
- }).then(function (resp) {
- return resp.name;
- });
- }
- function initIdentityId() {
- return util.Deferred(function (def) {
- if (session.identityId) {
- def.resolve();
- return;
- }
- storage.get("identityId").then(function (identityId) {
- if (! identityId) {
- identityId = util.generateId();
- storage.set("identityId", identityId);
- }
- session.identityId = identityId;
- // We don't actually have to wait for the set to succede, so
- // long as session.identityId is set
- def.resolve();
- });
- });
- }
- initIdentityId.done = initIdentityId();
- function initShareId() {
- return util.Deferred(function (def) {
- var hash = location.hash;
- var shareId = session.shareId;
- var isClient = true;
- var set = true;
- var sessionId;
- session.firstRun = ! TogetherJS.startup.continued;
- if (! shareId) {
- if (TogetherJS.startup._joinShareId) {
- // Like, below, this *also* means we got the shareId from the hash
- // (in togetherjs.js):
- shareId = TogetherJS.startup._joinShareId;
- }
- }
- if (! shareId) {
- // FIXME: I'm not sure if this will ever happen, because togetherjs.js should
- // handle it
- var m = /&?togetherjs=([^&]*)/.exec(hash);
- if (m) {
- isClient = ! m[1];
- shareId = m[2];
- var newHash = hash.substr(0, m.index) + hash.substr(m.index + m[0].length);
- location.hash = newHash;
- }
- }
- return storage.tab.get("status").then(function (saved) {
- var findRoom = TogetherJS.config.get("findRoom");
- TogetherJS.config.close("findRoom");
- if (findRoom && saved && findRoom != saved.shareId) {
- console.info("Ignoring findRoom in lieu of continued session");
- } else if (findRoom && TogetherJS.startup._joinShareId) {
- console.info("Ignoring findRoom in lieu of explicit invite to session");
- }
- if (findRoom && typeof findRoom == "string" && (! saved) && (! TogetherJS.startup._joinShareId)) {
- isClient = true;
- shareId = findRoom;
- sessionId = util.generateId();
- } else if (findRoom && (! saved) && (! TogetherJS.startup._joinShareId)) {
- assert(findRoom.prefix && typeof findRoom.prefix == "string", "Bad findRoom.prefix", findRoom);
- assert(findRoom.max && typeof findRoom.max == "number" && findRoom.max > 0,
- "Bad findRoom.max", findRoom);
- sessionId = util.generateId();
- if (findRoom.prefix.search(/[^a-zA-Z0-9]/) != -1) {
- console.warn("Bad value for findRoom.prefix:", JSON.stringify(findRoom.prefix));
- }
- getRoomName(findRoom.prefix, findRoom.max).then(function (shareId) {
- // FIXME: duplicates code below:
- session.clientId = session.identityId + "." + sessionId;
- storage.tab.set("status", {reason: "joined", shareId: shareId, running: true, date: Date.now(), sessionId: sessionId});
- session.isClient = true;
- session.shareId = shareId;
- session.emit("shareId");
- def.resolve(session.shareId);
- });
- return;
- } else if (TogetherJS.startup._launch) {
- if (saved) {
- isClient = saved.reason == "joined";
- if (! shareId) {
- shareId = saved.shareId;
- }
- sessionId = saved.sessionId;
- } else {
- isClient = TogetherJS.startup.reason == "joined";
- assert(! sessionId);
- sessionId = util.generateId();
- }
- if (! shareId) {
- shareId = util.generateId();
- }
- } else if (saved) {
- isClient = saved.reason == "joined";
- TogetherJS.startup.reason = saved.reason;
- TogetherJS.startup.continued = true;
- shareId = saved.shareId;
- sessionId = saved.sessionId;
- // The only case when we don't need to set the storage status again is when
- // we're already set to be running
- set = ! saved.running;
- } else {
- throw new util.AssertionError("No saved status, and no startup._launch request; why did TogetherJS start?");
- }
- assert(session.identityId);
- session.clientId = session.identityId + "." + sessionId;
- if (set) {
- storage.tab.set("status", {reason: TogetherJS.startup.reason, shareId: shareId, running: true, date: Date.now(), sessionId: sessionId});
- }
- session.isClient = isClient;
- session.shareId = shareId;
- session.emit("shareId");
- def.resolve(session.shareId);
- });
- });
- }
- function initStartTarget() {
- var id;
- if (TogetherJS.startup.button) {
- id = TogetherJS.startup.button.id;
- if (id) {
- storage.set("startTarget", id);
- }
- return;
- }
- storage.get("startTarget").then(function (id) {
- var el = document.getElementById(id);
- if (el) {
- TogetherJS.startup.button = el;
- }
- });
- }
- session.start = function () {
- initStartTarget();
- initIdentityId().then(function () {
- initShareId().then(function () {
- readyForMessages = false;
- openChannel();
- require(["ui"], function (ui) {
- TogetherJS.running = true;
- ui.prepareUI();
- require(features, function () {
- $(function () {
- peers = require("peers");
- var startup = require("startup");
- session.emit("start");
- session.once("ui-ready", function () {
- readyForMessages = true;
- startup.start();
- });
- ui.activateUI();
- TogetherJS.config.close("enableAnalytics");
- if (TogetherJS.config.get("enableAnalytics")) {
- require(["analytics"], function (analytics) {
- analytics.activate();
- });
- }
- peers._SelfLoaded.then(function () {
- sendHello(false);
- });
- TogetherJS.emit("ready");
- });
- });
- });
- });
- });
- };
- session.close = function (reason) {
- TogetherJS.running = false;
- var msg = {type: "bye"};
- if (reason) {
- msg.reason = reason;
- }
- session.send(msg);
- session.emit("close");
- var name = window.name;
- storage.tab.get("status").then(function (saved) {
- if (! saved) {
- console.warn("No session information saved in", "status." + name);
- } else {
- saved.running = false;
- saved.date = Date.now();
- storage.tab.set("status", saved);
- }
- channel.close();
- channel = null;
- session.shareId = null;
- session.emit("shareId");
- TogetherJS.emit("close");
- TogetherJS._teardown();
- });
- };
- session.on("start", function () {
- $(window).on("resize", resizeEvent);
- if (includeHashInUrl) {
- $(window).on("hashchange", hashchangeEvent);
- }
- });
- session.on("close", function () {
- $(window).off("resize", resizeEvent);
- if (includeHashInUrl) {
- $(window).off("hashchange", hashchangeEvent);
- }
- });
- function hashchangeEvent() {
- // needed because when message arives from peer this variable will be checked to
- // decide weather to show actions or not
- sendHello(false);
- }
- function resizeEvent() {
- session.emit("resize");
- }
- if (TogetherJS.startup._launch) {
- setTimeout(session.start);
- }
- util.testExpose({
- getChannel: function () {
- return channel;
- }
- });
- return session;
- });
|