<commit_before>"use strict";

const { SourceStatus } = ArchiveManager;
const { MemoryStorageInterface } = storage;
const { getUniqueID } = tools.encoding;

function createArchiveAndCredentials() {
    this.archive = new Archive();
    this.archiveID = this.archive.getID();
    this.archiveCredentials = createCredentials.fromPassword("testing");
    const tds = new TextDatasource();
    return tds
        .save(this.archive, this.archiveCredentials)
        .then(content => {
            this.sourceCredentials = createCredentials("text");
            this.sourceCredentials.setValue("datasource", JSON.stringify({
                type: "text",
                content
            }));
        });
}

describe("ArchiveManager", function() {

    beforeEach(function() {
        this.archiveManager = new ArchiveManager();
    });

    describe("get:storageInterface", function() {

        it("references a MemoryStorageInterface instance by default", function() {
            expect(this.archiveManager.storageInterface).to.be.an.instanceOf(MemoryStorageInterface);
        });

    });

    describe("addSource", function() {

        beforeEach(function() {
            return createArchiveAndCredentials.call(this);
        });

        it("adds an unlocked source", function() {
            expect(this.archiveManager.sources).to.have.lengthOf(0);
            return this.archiveManager
                .addSource("new item", this.sourceCredentials, this.archiveCredentials)
                .then(id => {
                    expect(this.archiveManager.sources).to.have.lengthOf(1);
                    const item = this.archiveManager.sources[0];
                    expect(item.id).to.equal(id);
                    expect(item.name).to.equal("new item");
                    expect(item.status).to.equal(SourceStatus.UNLOCKED);
                    expect(item.type).to.equal("text");
                });
        });

        it("dehydrates after adding", function() {
            sinon.spy(this.archiveManager.sources, "push");
            sinon.spy(this.archiveManager, "dehydrateSource");
            return this.archiveManager
                .addSource("new item", this.sourceCredentials, this.archiveCredentials)
                .then(id => {
                    expect(this.archiveManager.sources.push.callCount).to.equal(1);
                    expect(this.archiveManager.dehydrateSource.callCount).to.equal(1);
                    expect(this.archiveManager.dehydrateSource.calledWithExactly(id)).to.be.true;
                    expect(this.archiveManager.sources.push.calledBefore(this.archiveManager.dehydrateSource)).to.be.true;
                });
        });

        it("emits an event", function() {
            sinon.spy(this.archiveManager, "emit");
            sinon.stub(this.archiveManager, "dehydrateSource").returns(Promise.resolve());
            return this.archiveManager
                .addSource("new item", this.sourceCredentials, this.archiveCredentials)
                .then((id) => {
                    expect(this.archiveManager.emit.firstCall.args).to.eql([
                        "sourceAdded",
                        {
                            id,
                            name: "new item",
                            status: SourceStatus.UNLOCKED,
                            type: "text"
                        }
                    ]);
                });
        });

    });

    describe("dehydrateSource", function() {

        beforeEach(function() {
            return createArchiveAndCredentials.call(this)
                .then(() => {
                    sinon.spy(this.archiveManager.storageInterface, "setValue");
                    sinon.stub(this.archiveManager, "dehydrateSource").returns(Promise.resolve());
                    return this.archiveManager
                        .addSource("new item", this.sourceCredentials, this.archiveCredentials)
                        .then(id => {
                            this.sourceID = id;
                            this.archiveManager.dehydrateSource.restore();
                        });
                });
        });

        it("stores sources in a locked state", function() {
            expect(this.archiveManager.storageInterface.setValue.callCount).to.equal(0);
            return this.archiveManager.dehydrateSource(this.sourceID)
                .then(() => {
                    expect(this.archiveManager.storageInterface.setValue.callCount).to.equal(1);
                    const [ setKey, setValue ] = this.archiveManager.storageInterface.setValue.firstCall.args;
                    expect(setKey).to.contain(this.sourceID);
                    const packet = JSON.parse(setValue);
                    expect(packet).to.have.property("status", SourceStatus.LOCKED);
                });
        });

        it("stores sources with correct name and type", function() {
            return this.archiveManager.dehydrateSource(this.sourceID)
                .then(() => {
                    const packet = JSON.parse(this.archiveManager.storageInterface.setValue.firstCall.args[1]);
                    expect(packet).to.have.property("name", "new item");
                    expect(packet).to.have.property("type", "text");
                });
        });

        it("stores sources with encrypted source credentials", function() {
            return this.archiveManager.dehydrateSource(this.sourceID)
                .then(() => {
                    const packet = JSON.parse(this.archiveManager.storageInterface.setValue.firstCall.args[1]);
                    expect(packet).to.have.property("sourceCredentials").that.is.a.string;
                });
        });

        it("stores sources with encrypted archive credentials", function() {
            return this.archiveManager.dehydrateSource(this.sourceID)
                .then(() => {
                    const packet = JSON.parse(this.archiveManager.storageInterface.setValue.firstCall.args[1]);
                    expect(packet).to.have.property("archiveCredentials").that.is.a.string;
                });
        });

        it("emits an event", function() {
            sinon.spy(this.archiveManager, "emit");
            return this.archiveManager.dehydrateSource(this.sourceID)
                .then(() => {
                    expect(this.archiveManager.emit.firstCall.args).to.eql([
                        "sourceDehydrated",
                        {
                            id: this.sourceID,
                            name: "new item",
                            status: SourceStatus.LOCKED,
                            type: "text"
                        }
                    ]);
                });
        });

    });

    describe("indexOfSource", function() {

        beforeEach(function() {
            this.targetID = getUniqueID();
            this.archiveManager.sources.push(
                { name: "test1", id: getUniqueID() },
                { name: "test2", id: this.targetID },
                { name: "test3", id: getUniqueID() }
            );
        });

        it("returns correct index for source ID", function() {
            expect(this.archiveManager.indexOfSource(this.targetID)).to.equal(1);
        });

        it("returns -1 for an ID that's not found", function() {
            expect(this.archiveManager.indexOfSource(getUniqueID())).to.equal(-1);
        });

    });

    describe("lock", function() {

        beforeEach(function() {
            return createArchiveAndCredentials.call(this)
                .then(() => {
                    sinon.stub(this.archiveManager, "dehydrateSource").returns(Promise.resolve());
                    return this.archiveManager
                        .addSource("new item", this.sourceCredentials, this.archiveCredentials)
                        .then(id => {
                            this.sourceID = id;
                            this.archiveManager.dehydrateSource.restore();
                        });
                });
        });

        it("locks an unlocked source", function() {
            return this.archiveManager.lock(this.sourceID)
                .then(() => {
                    const source = this.archiveManager.sources[0];
                    expect(source).to.have.property("status", SourceStatus.LOCKED);
                });
        });

        it("encrypts credentials", function() {
            return this.archiveManager.lock(this.sourceID)
                .then(() => {
                    const source = this.archiveManager.sources[0];
                    expect(source).to.have.property("sourceCredentials")
                        .that.is.a.string;
                    expect(source).to.have.property("archiveCredentials")
                        .that.is.a.string;
                });
        });

        it("dehydrates after locking", function() {
            sinon.spy(this.archiveManager, "_replace");
            sinon.stub(this.archiveManager, "dehydrateSource").returns(Promise.resolve());
            return this.archiveManager.lock(this.sourceID)
                .then(() => {
                    expect(this.archiveManager._replace.calledWith(this.sourceID)).to.be.true;
                    expect(this.archiveManager.dehydrateSource.calledWithExactly(this.sourceID)).to.be.true;
                    expect(this.archiveManager._replace.calledBefore(this.archiveManager.dehydrateSource)).to.be.true;
                });
        });

        it("emits an event", function() {
            sinon.spy(this.archiveManager, "emit");
            sinon.stub(this.archiveManager, "dehydrateSource").returns(Promise.resolve());
            return this.archiveManager.lock(this.sourceID)
                .then(() => {
                    expect(this.archiveManager.emit.firstCall.args).to.eql([
                        "sourceLocked",
                        {
                            id: this.sourceID,
                            name: "new item",
                            status: SourceStatus.LOCKED,
                            type: "text"
                        }
                    ]);
                });
        });

    });

    describe("rehydrate", function() {

        beforeEach(function() {
            return createArchiveAndCredentials.call(this)
                .then(() => {
                    return this.archiveManager
                        .addSource("new item", this.sourceCredentials, this.archiveCredentials)
                        .then(id => {
                            this.sourceID = id;
                        });
                });
        });

        it("clears sources", function() {
            this.archiveManager.sources.push({ name: "fake" });
            expect(this.archiveManager.sources).to.have.lengthOf(2);
            return this.archiveManager.rehydrate()
                .then(() => {
                    expect(this.archiveManager.sources).to.have.lengthOf(1);
                });
        });

        it("restores the necessary properties", function() {
            return this.archiveManager.rehydrate()
                .then(() => {
                    const item = this.archiveManager.sources[0];
                    expect(item).to.have.property("name", "new item");
                    expect(item).to.have.property("type", "text");
                    expect(item).to.have.property("status", SourceStatus.LOCKED);
                    expect(item).to.have.property("sourceCredentials")
                        .that.is.a.string;
                    expect(item).to.have.property("archiveCredentials")
                        .that.is.a.string;
                });
        });

        it("emits events for all sources", function() {
            sinon.spy(this.archiveManager, "emit");
            return this.archiveManager.rehydrate()
                .then(() => {
                    expect(this.archiveManager.emit.firstCall.args).to.eql([
                        "sourceRehydrated",
                        {
                            id: this.sourceID,
                            name: "new item",
                            status: SourceStatus.LOCKED,
                            type: "text"
                        }
                    ]);
                });
        });

    });

    describe("remove", function() {

        beforeEach(function() {
            return Promise.all([
                createArchiveAndCredentials.call(this)
                    .then(() => {
                        return this.archiveManager
                            .addSource("Item 1", this.sourceCredentials, this.archiveCredentials)
                            .then(id => {
                                this.sourceID1 = id;
                            });
                    }),
                createArchiveAndCredentials.call(this)
                    .then(() => {
                        return this.archiveManager
                            .addSource("Item 2", this.sourceCredentials, this.archiveCredentials)
                            .then(id => {
                                this.sourceID2 = id;
                            });
                    })
            ]);
        });

        it("removes sources", function() {
            expect(this.archiveManager.sources).to.have.lengthOf(2);
            return this.archiveManager.remove(this.sourceID1)
                .then(() => {
                    expect(this.archiveManager.sources).to.have.lengthOf(1);
                    expect(this.archiveManager.sources[0]).to.have.property("name", "Item 2");
                });
        });

        it("emits removal event", function() {
            sinon.spy(this.archiveManager, "emit");
            return this.archiveManager.remove(this.sourceID2)
                .then(() => {
                    expect(this.archiveManager.emit.firstCall.args).to.eql([
                        "sourceRemoved",
                        {
                            id: this.sourceID2
                        }
                    ]);
                });
        });

    });

    describe("unlock", function() {

        beforeEach(function() {
            return createArchiveAndCredentials.call(this)
                .then(() => {
                    return this.archiveManager
                        .addSource("new item", this.sourceCredentials, this.archiveCredentials)
                        .then(id => {
                            this.sourceID = id;
                            return this.archiveManager.lock(id);
                        });
                });
        });

        it("sets the correct properties", function() {
            return this.archiveManager.unlock(this.sourceID, "testing")
                .then(() => {
                    expect(this.archiveManager.sources).to.have.lengthOf(1);
                    const source = this.archiveManager.sources[0];
                    expect(source).to.have.property("name", "new item");
                    expect(source).to.have.property("type", "text");
                    expect(source).to.have.property("status", SourceStatus.UNLOCKED);
                    expect(source).to.have.property("sourceCredentials")
                        .that.is.an.object;
                    expect(source).to.have.property("archiveCredentials")
                        .that.is.an.object;
                    expect(source).to.have.property("workspace")
                        .that.is.an.instanceOf(Workspace);
                });
        });

        it("restores the credentials", function() {
            return this.archiveManager.unlock(this.sourceID, "testing")
                .then(() => {
                    const source = this.archiveManager.sources[0];
                    expect(source.sourceCredentials.getValue("datasource"))
                        .to.equal(this.sourceCredentials.getValue("datasource"));
                    expect(source.archiveCredentials.password).to.equal("testing");
                });
        });

        it("restores the archive", function() {
            return this.archiveManager.unlock(this.sourceID, "testing")
                .then(() => {
                    const source = this.archiveManager.sources[0];
                    const archive = source.workspace.primary.archive;
                    expect(archive).to.be.an.instanceOf(Archive);
                    expect(archive.getID()).to.equal(this.archiveID);
                });
        });

        it("emits an event", function() {
            sinon.spy(this.archiveManager, "emit");
            return this.archiveManager.unlock(this.sourceID, "testing")
                .then(() => {
                    expect(this.archiveManager.emit.firstCall.args).to.eql([
                        "sourceUnlocked",
                        {
                            id: this.sourceID,
                            name: "new item",
                            status: SourceStatus.UNLOCKED,
                            type: "text"
                        }
                    ]);
                });
        });

    });

});
<commit_msg>Add tests for unlockedSources<commit_after>"use strict";

const { SourceStatus } = ArchiveManager;
const { MemoryStorageInterface } = storage;
const { getUniqueID } = tools.encoding;

function createArchiveAndCredentials() {
    this.archive = new Archive();
    this.archiveID = this.archive.getID();
    this.archiveCredentials = createCredentials.fromPassword("testing");
    const tds = new TextDatasource();
    return tds
        .save(this.archive, this.archiveCredentials)
        .then(content => {
            this.sourceCredentials = createCredentials("text");
            this.sourceCredentials.setValue("datasource", JSON.stringify({
                type: "text",
                content
            }));
        });
}

describe("ArchiveManager", function() {

    beforeEach(function() {
        this.archiveManager = new ArchiveManager();
    });

    describe("get:storageInterface", function() {

        it("references a MemoryStorageInterface instance by default", function() {
            expect(this.archiveManager.storageInterface).to.be.an.instanceOf(MemoryStorageInterface);
        });

    });

    describe("get:unlockedSources", function() {

        beforeEach(function() {
            return createArchiveAndCredentials.call(this);
        });

        it("returns correctly for no sources", function() {
            expect(this.archiveManager.unlockedSources).to.be.an.array;
            expect(this.archiveManager.unlockedSources).to.have.lengthOf(0);
        });

        it("returns correctly for 1 source", function() {
            return this.archiveManager
                .addSource("new item", this.sourceCredentials, this.archiveCredentials)
                .then(id => {
                    expect(this.archiveManager.unlockedSources).to.have.lengthOf(1);
                    expect(this.archiveManager.unlockedSources[0]).to.be.an.object;
                    expect(this.archiveManager.unlockedSources[0]).to.have.property("id", id);
                });
        });

    });

    describe("addSource", function() {

        beforeEach(function() {
            return createArchiveAndCredentials.call(this);
        });

        it("adds an unlocked source", function() {
            expect(this.archiveManager.sources).to.have.lengthOf(0);
            return this.archiveManager
                .addSource("new item", this.sourceCredentials, this.archiveCredentials)
                .then(id => {
                    expect(this.archiveManager.sources).to.have.lengthOf(1);
                    const item = this.archiveManager.sources[0];
                    expect(item.id).to.equal(id);
                    expect(item.name).to.equal("new item");
                    expect(item.status).to.equal(SourceStatus.UNLOCKED);
                    expect(item.type).to.equal("text");
                });
        });

        it("dehydrates after adding", function() {
            sinon.spy(this.archiveManager.sources, "push");
            sinon.spy(this.archiveManager, "dehydrateSource");
            return this.archiveManager
                .addSource("new item", this.sourceCredentials, this.archiveCredentials)
                .then(id => {
                    expect(this.archiveManager.sources.push.callCount).to.equal(1);
                    expect(this.archiveManager.dehydrateSource.callCount).to.equal(1);
                    expect(this.archiveManager.dehydrateSource.calledWithExactly(id)).to.be.true;
                    expect(this.archiveManager.sources.push.calledBefore(this.archiveManager.dehydrateSource)).to.be.true;
                });
        });

        it("emits an event", function() {
            sinon.spy(this.archiveManager, "emit");
            sinon.stub(this.archiveManager, "dehydrateSource").returns(Promise.resolve());
            return this.archiveManager
                .addSource("new item", this.sourceCredentials, this.archiveCredentials)
                .then((id) => {
                    expect(this.archiveManager.emit.firstCall.args).to.eql([
                        "sourceAdded",
                        {
                            id,
                            name: "new item",
                            status: SourceStatus.UNLOCKED,
                            type: "text"
                        }
                    ]);
                });
        });

    });

    describe("dehydrateSource", function() {

        beforeEach(function() {
            return createArchiveAndCredentials.call(this)
                .then(() => {
                    sinon.spy(this.archiveManager.storageInterface, "setValue");
                    sinon.stub(this.archiveManager, "dehydrateSource").returns(Promise.resolve());
                    return this.archiveManager
                        .addSource("new item", this.sourceCredentials, this.archiveCredentials)
                        .then(id => {
                            this.sourceID = id;
                            this.archiveManager.dehydrateSource.restore();
                        });
                });
        });

        it("stores sources in a locked state", function() {
            expect(this.archiveManager.storageInterface.setValue.callCount).to.equal(0);
            return this.archiveManager.dehydrateSource(this.sourceID)
                .then(() => {
                    expect(this.archiveManager.storageInterface.setValue.callCount).to.equal(1);
                    const [ setKey, setValue ] = this.archiveManager.storageInterface.setValue.firstCall.args;
                    expect(setKey).to.contain(this.sourceID);
                    const packet = JSON.parse(setValue);
                    expect(packet).to.have.property("status", SourceStatus.LOCKED);
                });
        });

        it("stores sources with correct name and type", function() {
            return this.archiveManager.dehydrateSource(this.sourceID)
                .then(() => {
                    const packet = JSON.parse(this.archiveManager.storageInterface.setValue.firstCall.args[1]);
                    expect(packet).to.have.property("name", "new item");
                    expect(packet).to.have.property("type", "text");
                });
        });

        it("stores sources with encrypted source credentials", function() {
            return this.archiveManager.dehydrateSource(this.sourceID)
                .then(() => {
                    const packet = JSON.parse(this.archiveManager.storageInterface.setValue.firstCall.args[1]);
                    expect(packet).to.have.property("sourceCredentials").that.is.a.string;
                });
        });

        it("stores sources with encrypted archive credentials", function() {
            return this.archiveManager.dehydrateSource(this.sourceID)
                .then(() => {
                    const packet = JSON.parse(this.archiveManager.storageInterface.setValue.firstCall.args[1]);
                    expect(packet).to.have.property("archiveCredentials").that.is.a.string;
                });
        });

        it("emits an event", function() {
            sinon.spy(this.archiveManager, "emit");
            return this.archiveManager.dehydrateSource(this.sourceID)
                .then(() => {
                    expect(this.archiveManager.emit.firstCall.args).to.eql([
                        "sourceDehydrated",
                        {
                            id: this.sourceID,
                            name: "new item",
                            status: SourceStatus.LOCKED,
                            type: "text"
                        }
                    ]);
                });
        });

    });

    describe("indexOfSource", function() {

        beforeEach(function() {
            this.targetID = getUniqueID();
            this.archiveManager.sources.push(
                { name: "test1", id: getUniqueID() },
                { name: "test2", id: this.targetID },
                { name: "test3", id: getUniqueID() }
            );
        });

        it("returns correct index for source ID", function() {
            expect(this.archiveManager.indexOfSource(this.targetID)).to.equal(1);
        });

        it("returns -1 for an ID that's not found", function() {
            expect(this.archiveManager.indexOfSource(getUniqueID())).to.equal(-1);
        });

    });

    describe("lock", function() {

        beforeEach(function() {
            return createArchiveAndCredentials.call(this)
                .then(() => {
                    sinon.stub(this.archiveManager, "dehydrateSource").returns(Promise.resolve());
                    return this.archiveManager
                        .addSource("new item", this.sourceCredentials, this.archiveCredentials)
                        .then(id => {
                            this.sourceID = id;
                            this.archiveManager.dehydrateSource.restore();
                        });
                });
        });

        it("locks an unlocked source", function() {
            return this.archiveManager.lock(this.sourceID)
                .then(() => {
                    const source = this.archiveManager.sources[0];
                    expect(source).to.have.property("status", SourceStatus.LOCKED);
                });
        });

        it("encrypts credentials", function() {
            return this.archiveManager.lock(this.sourceID)
                .then(() => {
                    const source = this.archiveManager.sources[0];
                    expect(source).to.have.property("sourceCredentials")
                        .that.is.a.string;
                    expect(source).to.have.property("archiveCredentials")
                        .that.is.a.string;
                });
        });

        it("dehydrates after locking", function() {
            sinon.spy(this.archiveManager, "_replace");
            sinon.stub(this.archiveManager, "dehydrateSource").returns(Promise.resolve());
            return this.archiveManager.lock(this.sourceID)
                .then(() => {
                    expect(this.archiveManager._replace.calledWith(this.sourceID)).to.be.true;
                    expect(this.archiveManager.dehydrateSource.calledWithExactly(this.sourceID)).to.be.true;
                    expect(this.archiveManager._replace.calledBefore(this.archiveManager.dehydrateSource)).to.be.true;
                });
        });

        it("emits an event", function() {
            sinon.spy(this.archiveManager, "emit");
            sinon.stub(this.archiveManager, "dehydrateSource").returns(Promise.resolve());
            return this.archiveManager.lock(this.sourceID)
                .then(() => {
                    expect(this.archiveManager.emit.firstCall.args).to.eql([
                        "sourceLocked",
                        {
                            id: this.sourceID,
                            name: "new item",
                            status: SourceStatus.LOCKED,
                            type: "text"
                        }
                    ]);
                });
        });

    });

    describe("rehydrate", function() {

        beforeEach(function() {
            return createArchiveAndCredentials.call(this)
                .then(() => {
                    return this.archiveManager
                        .addSource("new item", this.sourceCredentials, this.archiveCredentials)
                        .then(id => {
                            this.sourceID = id;
                        });
                });
        });

        it("clears sources", function() {
            this.archiveManager.sources.push({ name: "fake" });
            expect(this.archiveManager.sources).to.have.lengthOf(2);
            return this.archiveManager.rehydrate()
                .then(() => {
                    expect(this.archiveManager.sources).to.have.lengthOf(1);
                });
        });

        it("restores the necessary properties", function() {
            return this.archiveManager.rehydrate()
                .then(() => {
                    const item = this.archiveManager.sources[0];
                    expect(item).to.have.property("name", "new item");
                    expect(item).to.have.property("type", "text");
                    expect(item).to.have.property("status", SourceStatus.LOCKED);
                    expect(item).to.have.property("sourceCredentials")
                        .that.is.a.string;
                    expect(item).to.have.property("archiveCredentials")
                        .that.is.a.string;
                });
        });

        it("emits events for all sources", function() {
            sinon.spy(this.archiveManager, "emit");
            return this.archiveManager.rehydrate()
                .then(() => {
                    expect(this.archiveManager.emit.firstCall.args).to.eql([
                        "sourceRehydrated",
                        {
                            id: this.sourceID,
                            name: "new item",
                            status: SourceStatus.LOCKED,
                            type: "text"
                        }
                    ]);
                });
        });

    });

    describe("remove", function() {

        beforeEach(function() {
            return Promise.all([
                createArchiveAndCredentials.call(this)
                    .then(() => {
                        return this.archiveManager
                            .addSource("Item 1", this.sourceCredentials, this.archiveCredentials)
                            .then(id => {
                                this.sourceID1 = id;
                            });
                    }),
                createArchiveAndCredentials.call(this)
                    .then(() => {
                        return this.archiveManager
                            .addSource("Item 2", this.sourceCredentials, this.archiveCredentials)
                            .then(id => {
                                this.sourceID2 = id;
                            });
                    })
            ]);
        });

        it("removes sources", function() {
            expect(this.archiveManager.sources).to.have.lengthOf(2);
            return this.archiveManager.remove(this.sourceID1)
                .then(() => {
                    expect(this.archiveManager.sources).to.have.lengthOf(1);
                    expect(this.archiveManager.sources[0]).to.have.property("name", "Item 2");
                });
        });

        it("emits removal event", function() {
            sinon.spy(this.archiveManager, "emit");
            return this.archiveManager.remove(this.sourceID2)
                .then(() => {
                    expect(this.archiveManager.emit.firstCall.args).to.eql([
                        "sourceRemoved",
                        {
                            id: this.sourceID2
                        }
                    ]);
                });
        });

    });

    describe("unlock", function() {

        beforeEach(function() {
            return createArchiveAndCredentials.call(this)
                .then(() => {
                    return this.archiveManager
                        .addSource("new item", this.sourceCredentials, this.archiveCredentials)
                        .then(id => {
                            this.sourceID = id;
                            return this.archiveManager.lock(id);
                        });
                });
        });

        it("sets the correct properties", function() {
            return this.archiveManager.unlock(this.sourceID, "testing")
                .then(() => {
                    expect(this.archiveManager.sources).to.have.lengthOf(1);
                    const source = this.archiveManager.sources[0];
                    expect(source).to.have.property("name", "new item");
                    expect(source).to.have.property("type", "text");
                    expect(source).to.have.property("status", SourceStatus.UNLOCKED);
                    expect(source).to.have.property("sourceCredentials")
                        .that.is.an.object;
                    expect(source).to.have.property("archiveCredentials")
                        .that.is.an.object;
                    expect(source).to.have.property("workspace")
                        .that.is.an.instanceOf(Workspace);
                });
        });

        it("restores the credentials", function() {
            return this.archiveManager.unlock(this.sourceID, "testing")
                .then(() => {
                    const source = this.archiveManager.sources[0];
                    expect(source.sourceCredentials.getValue("datasource"))
                        .to.equal(this.sourceCredentials.getValue("datasource"));
                    expect(source.archiveCredentials.password).to.equal("testing");
                });
        });

        it("restores the archive", function() {
            return this.archiveManager.unlock(this.sourceID, "testing")
                .then(() => {
                    const source = this.archiveManager.sources[0];
                    const archive = source.workspace.primary.archive;
                    expect(archive).to.be.an.instanceOf(Archive);
                    expect(archive.getID()).to.equal(this.archiveID);
                });
        });

        it("emits an event", function() {
            sinon.spy(this.archiveManager, "emit");
            return this.archiveManager.unlock(this.sourceID, "testing")
                .then(() => {
                    expect(this.archiveManager.emit.firstCall.args).to.eql([
                        "sourceUnlocked",
                        {
                            id: this.sourceID,
                            name: "new item",
                            status: SourceStatus.UNLOCKED,
                            type: "text"
                        }
                    ]);
                });
        });

    });

});
<|endoftext|>


<commit_before>// Generated on 2014-06-20 using generator-webapp 0.4.9
'use strict';

// # Globbing
// for performance reasons we're only matching one level down:
// 'test/spec/{,*/}*.js'
// use this if you want to recursively match all subfolders:
// 'test/spec/**/*.js'

module.exports = function (grunt) {

  // Load grunt tasks automatically
  require('load-grunt-tasks')(grunt);

  // Time how long tasks take. Can help when optimizing build times
  require('time-grunt')(grunt);

  // Configurable paths
  var config = {
    app: 'app'
  };

  // Define the configuration for all the tasks
  grunt.initConfig({

    // Project settings
    config: config,

    // Watches files for changes and runs tasks based on the changed files
    watch: {
      js: {
        files: ['<%= config.app %>/scripts/{,*/}*.js'],
        tasks: ['jshint'],
        options: {
          livereload: true
        }
      },
      gruntfile: {
        files: ['Gruntfile.js'],
        tasks: ['jshint'],
        options: {
          livereload: true
        }
      },
      livereload: {
        options: {
          livereload: '<%= connect.options.livereload %>'
        },
        files: [
          '<%= config.app %>/{,*/}*.html',
          '<%= config.app %>/styles{,*/}*.css',
          '<%= config.app %>/images/{,*/}*',
        ]
      }
    },

    // The actual grunt server settings
    connect: {
      options: {
        port: 9000,
        open: true,
        livereload: 35729,
        // Change this to '0.0.0.0' to access the server from outside
        hostname: 'localhost'
      },
      livereload: {
        options: {
          middleware: function(connect) {
            return [
              connect.static(config.app)
            ];
          }
        }
      }
    },

    // Make sure code styles are up to par and there are no obvious mistakes
    jshint: {
      options: {
        jshintrc: '.jshintrc',
        reporter: require('jshint-stylish')
      },
      all: [
        'Gruntfile.js',
        '<%= config.app %>/scripts/{,*/}*.js',
      ]
    }
  });

  grunt.registerTask('serve', [
    'connect:livereload',
    'watch'
  ]);

  grunt.registerTask('server', function () {
    grunt.log.warn('The `server` task has been deprecated. Use `grunt serve` to start a server.');
    grunt.task.run(['serve']);
  });

  grunt.registerTask('default', [
    'serve'
  ]);
};
<commit_msg>If running Grunt, lint the gulpfile as well<commit_after>// Generated on 2014-06-20 using generator-webapp 0.4.9
'use strict';

// # Globbing
// for performance reasons we're only matching one level down:
// 'test/spec/{,*/}*.js'
// use this if you want to recursively match all subfolders:
// 'test/spec/**/*.js'

module.exports = function (grunt) {

  // Load grunt tasks automatically
  require('load-grunt-tasks')(grunt);

  // Time how long tasks take. Can help when optimizing build times
  require('time-grunt')(grunt);

  // Configurable paths
  var config = {
    app: 'app'
  };

  // Define the configuration for all the tasks
  grunt.initConfig({

    // Project settings
    config: config,

    // Watches files for changes and runs tasks based on the changed files
    watch: {
      js: {
        files: ['<%= config.app %>/scripts/{,*/}*.js'],
        tasks: ['jshint'],
        options: {
          livereload: true
        }
      },
      gruntfile: {
        files: ['Gruntfile.js'],
        tasks: ['jshint'],
        options: {
          livereload: true
        }
      },
      livereload: {
        options: {
          livereload: '<%= connect.options.livereload %>'
        },
        files: [
          '<%= config.app %>/{,*/}*.html',
          '<%= config.app %>/styles{,*/}*.css',
          '<%= config.app %>/images/{,*/}*',
        ]
      }
    },

    // The actual grunt server settings
    connect: {
      options: {
        port: 9000,
        open: true,
        livereload: 35729,
        // Change this to '0.0.0.0' to access the server from outside
        hostname: 'localhost'
      },
      livereload: {
        options: {
          middleware: function(connect) {
            return [
              connect.static(config.app)
            ];
          }
        }
      }
    },

    // Make sure code styles are up to par and there are no obvious mistakes
    jshint: {
      options: {
        jshintrc: '.jshintrc',
        reporter: require('jshint-stylish')
      },
      all: [
        'Gruntfile.js',
        'gulpfile.js',
        '<%= config.app %>/scripts/{,*/}*.js',
      ]
    }
  });

  grunt.registerTask('serve', [
    'connect:livereload',
    'watch'
  ]);

  grunt.registerTask('server', function () {
    grunt.log.warn('The `server` task has been deprecated. Use `grunt serve` to start a server.');
    grunt.task.run(['serve']);
  });

  grunt.registerTask('default', [
    'serve'
  ]);
};
<|endoftext|>


<commit_before>// Run JSHint and Plato over the specified files

var fs = require('fs');
var async = require('async');
var chalk = require('chalk');
var plato = require('plato');
var Getopt = require('node-getopt');
var buildJSHint = require('build-jshint');

var filesets = require('./lib/filesets.js');
var reporter = require('./lib/reporter.js');

// By default reports are output to `./reports` and given the title of `Plato
// Report`.

var outputDir = 'reports';

var options = {};
var jsHintOptions = {};
var platoOptions = {
    title: 'Plato Report',
    recurse: true,
    quiet: true
};

// Check the path provided by the user, prefixing it with `./` if it's not an
// explicitly provided as an absolute or relative path. Valid paths are passed
// to the provided callback.

function checkPath(string, callback, done) {
    var path = string;
    if (!(/^\.|\//).test(string)) {
        path = './' + string;
    }

    fs.stat(path, function(err) {
        if (err) {
            console.log(chalk.red('Error, cannot open: %s'), string);
            process.exit(1);
        } else {
            callback(path, done);
        }
    });
}

// Plato uses the `jshintrc` as is, whereas JSHint-Build needs the options
// inserted into its own options.

function setJSHint(path, done) {
    var opts = require(path);
    platoOptions.jshint = path;
    jsHintOptions.options = opts;
    done(null);
}

// The default `jshintrc` from the linter package can be overridden with the
// `jshint` option. The provided file is checked to ensure it exists before it's
// used.

function checkJSHint(done) {
    if (options.jshint) {
        checkPath(options.jshint, setJSHint, done);
    } else {
        done();
    }
}

// Set the filesets from those defined in the provided configuration file. If
// a given fileset isn't defined then a warning will be output.

function setFilesets(path, done) {
    var config = require(path);

    /* jshint sub: true */
    var includeFiles = config['includeFiles'];
    var excludeFiles = config['excludeFiles'];
    /* jshint sub: false */

    if (includeFiles) {
        filesets.include(includeFiles);
    } else {
        console.log(chalk.yellow('`includeFiles` not found in %s'), path);
    }

    if (excludeFiles) {
        filesets.exclude(excludeFiles);
    } else {
        console.log(chalk.yellow('`excludeFiles` not found in %s'), path);
    }

    done(null);
}

// The default filesets that are used can be overridden from the command line by
// providing a configuration file with the new filesets defined. The existence
// of the configuration file is checked before the new filesets are defined.

function checkFilesets(done) {
    /* jshint sub: true */
    if (options['filesets']) {
        checkPath(options['filesets'], setFilesets, done);
    } else {
        done();
    }
    /* jshint sub: false */
}

// Run the actual reports. The plato reports are run first, then the JSHint
// report. The output is annotated with headers and details on where the reports
// are stored.

function runReports(err) {
    if (err) { console.log(chalk.red('Error running reports: %s'), err); }

    var excludes = filesets.platoExcludes();

    if (excludes) {
        platoOptions.exclude = excludes;
    }

    jsHintOptions.ignore = filesets.jshintExcludes();
    jsHintOptions.reporter = reporter.reporter;

    console.log(chalk.blue('Building plato reports...'));
    console.log(chalk.gray('(Ignore any output about being unable to parse ' +
        'JSON files)'));

    plato.inspect(filesets.platoIncludes(), outputDir, platoOptions,
        function() {
            console.log(chalk.blue('\nPlato reports have been built to: ') +
                chalk.magenta(outputDir));
            console.log(chalk.blue('\nJSHint Report\n=============\n'));
            buildJSHint(filesets.jshintIncludes(), jsHintOptions,
                reporter.callback);
        }
    );
}

// Provide options to override all the defaults from the command line

getopt = new Getopt([
    ['t', 'title=TITLE', 'The title of the report.'],
    ['o', 'output=PATH', 'The directory to output the reports to'],
    ['l', 'jshint=PATH', 'Path to a jshintrc file for JSHint linting.'],
    ['f', 'filesets=PATH',
     'Path to a file containing the filesets to report on.'],
    ['h', 'help', 'display this help text.']
]);

getopt.setHelp("Usage: node report.js [options]\n[[OPTIONS]]");
getopt.bindHelp();

// Override defaults with any options from the command line, then run the
// reports.

options = getopt.parseSystem().options;

if (options.title) {
    platoOptions.title = options.title;
}

if (options.output) {
    outputDir = options.output;
}

async.parallel([checkJSHint, checkFilesets], runReports);<commit_msg>Alter report.js to handle being called from anywhere<commit_after>// Run JSHint and Plato over the specified files

var fs = require('fs');
var async = require('async');
var chalk = require('chalk');
var plato = require('plato');
var Getopt = require('node-getopt');
var buildJSHint = require('build-jshint');

var filesets = require('./lib/filesets.js');
var reporter = require('./lib/reporter.js');

// By default reports are output to `./reports` and given the title of `Plato
// Report`.

var outputDir = 'reports';

var options = {};
var jsHintOptions = {};
var platoOptions = {
    title: 'Plato Report',
    recurse: true,
    quiet: true
};

// Check the path provided by the user, prefixing it with `./` if it's not an
// explicitly provided as an absolute or relative path. Valid paths are passed
// to the provided callback.

function checkPath(string, callback, done) {
    var path = string;
    if (!(/^\.|\//).test(string)) {
        path = './' + string;
    }

    fs.stat(path, function(err) {
        if (err) {
            console.log(chalk.red('Error, cannot open: %s'), string);
            process.exit(1);
        } else {
            callback(path, done);
        }
    });
}

// Plato uses the `jshintrc` as is, whereas JSHint-Build needs the options
// inserted into its own options.

function setJSHint(path, done) {
    var opts = require(path);
    platoOptions.jshint = path;
    jsHintOptions.options = opts;
    done(null);
}

// The default `jshintrc` from the linter package can be overridden with the
// `jshint` option. The provided file is checked to ensure it exists before it's
// used.

function checkJSHint(done) {
    if (options.jshint) {
        checkPath(options.jshint, setJSHint, done);
    } else {
        done();
    }
}

// Set the filesets from those defined in the provided configuration file. If
// a given fileset isn't defined then a warning will be output.

function setFilesets(path, done) {
    var config = JSON.parse(fs.readFileSync(path, 'utf8'));

    /* jshint sub: true */
    var includeFiles = config['includeFiles'];
    var excludeFiles = config['excludeFiles'];
    /* jshint sub: false */

    if (includeFiles) {
        filesets.include(includeFiles);
    } else {
        console.log(chalk.yellow('`includeFiles` not found in %s'), path);
    }

    if (excludeFiles) {
        filesets.exclude(excludeFiles);
    } else {
        console.log(chalk.yellow('`excludeFiles` not found in %s'), path);
    }

    done(null);
}

// The default filesets that are used can be overridden from the command line by
// providing a configuration file with the new filesets defined. The existence
// of the configuration file is checked before the new filesets are defined.

function checkFilesets(done) {
    /* jshint sub: true */
    if (options['filesets']) {
        checkPath(options['filesets'], setFilesets, done);
    } else {
        done();
    }
    /* jshint sub: false */
}

// Run the actual reports. The plato reports are run first, then the JSHint
// report. The output is annotated with headers and details on where the reports
// are stored.

function runReports(err) {
    if (err) { console.log(chalk.red('Error running reports: %s'), err); }

    var excludes = filesets.platoExcludes();

    if (excludes) {
        platoOptions.exclude = excludes;
    }

    jsHintOptions.ignore = filesets.jshintExcludes();
    jsHintOptions.reporter = reporter.reporter;

    console.log(chalk.blue('Building plato reports...'));
    console.log(chalk.gray('(Ignore any output about being unable to parse ' +
        'JSON files)'));

    plato.inspect(filesets.platoIncludes(), outputDir, platoOptions,
        function() {
            console.log(chalk.blue('\nPlato reports have been built to: ') +
                chalk.magenta(outputDir));
            console.log(chalk.blue('\nJSHint Report\n=============\n'));
            buildJSHint(filesets.jshintIncludes(), jsHintOptions,
                reporter.callback);
        }
    );
}

// Provide options to override all the defaults from the command line

getopt = new Getopt([
    ['t', 'title=TITLE', 'The title of the report.'],
    ['o', 'output=PATH', 'The directory to output the reports to'],
    ['l', 'jshint=PATH', 'Path to a jshintrc file for JSHint linting.'],
    ['f', 'filesets=PATH',
     'Path to a file containing the filesets to report on.'],
    ['h', 'help', 'display this help text.']
]);

getopt.setHelp("Usage: node report.js [options]\n[[OPTIONS]]");
getopt.bindHelp();

// Override defaults with any options from the command line, then run the
// reports.

options = getopt.parseSystem().options;

if (options.title) {
    platoOptions.title = options.title;
}

if (options.output) {
    outputDir = options.output;
}

async.parallel([checkJSHint, checkFilesets], runReports);<|endoftext|>


<commit_before>require('ember-handlebars/ext');

/**
@module ember
@submodule ember-handlebars
*/

/**
  `partial` renders a template directly using the current context.
  If needed the context can be set using the `{{#with foo}}` helper.

  ```html
  <script type="text/x-handlebars" data-template-name="header_bar">
    {{#with currentUser}}
      {{partial user_info}}
    {{/with}}
  </script>

  The `data-template-name` attribute of a partial template
  is prefixed with an underscore.

  ```html

  ```

  @method partial
  @for Ember.Handlebars.helpers
  @param {String} partialName the name of the template to render minus the leading underscore
*/

Ember.Handlebars.registerHelper('partial', function(name, options) {
  var nameParts = name.split("/"),
      lastPart = nameParts[nameParts.length - 1];

  nameParts[nameParts.length - 1] = "_" + lastPart;

  var view = options.data.view,
      underscoredName = nameParts.join("/"),
      template = view.templateForName(underscoredName),
      deprecatedTemplate = view.templateForName(name);

  Ember.deprecate("You tried to render the partial " + name + ", which should be at '" + underscoredName + "', but Ember found '" + name + "'. Please use a leading underscore in your partials", template);
  Ember.assert("Unable to find partial with name '"+name+"'.", template || deprecatedTemplate);

  template = template || deprecatedTemplate;

  template(this, { data: options.data });
});
<commit_msg>Fix typo in {{partial}} docs.<commit_after>require('ember-handlebars/ext');

/**
@module ember
@submodule ember-handlebars
*/

/**
  `partial` renders a template directly using the current context.
  If needed the context can be set using the `{{#with foo}}` helper.

  ```html

  ```

  The `data-template-name` attribute of a partial template
  is prefixed with an underscore.

  ```html

  ```

  @method partial
  @for Ember.Handlebars.helpers
  @param {String} partialName the name of the template to render minus the leading underscore
*/

Ember.Handlebars.registerHelper('partial', function(name, options) {
  var nameParts = name.split("/"),
      lastPart = nameParts[nameParts.length - 1];

  nameParts[nameParts.length - 1] = "_" + lastPart;

  var view = options.data.view,
      underscoredName = nameParts.join("/"),
      template = view.templateForName(underscoredName),
      deprecatedTemplate = view.templateForName(name);

  Ember.deprecate("You tried to render the partial " + name + ", which should be at '" + underscoredName + "', but Ember found '" + name + "'. Please use a leading underscore in your partials", template);
  Ember.assert("Unable to find partial with name '"+name+"'.", template || deprecatedTemplate);

  template = template || deprecatedTemplate;

  template(this, { data: options.data });
});
<|endoftext|>


<commit_before>'use strict';

const fs = require('fs');
const path = require('path');

const ROOMFAQ_FILE = path.resolve(__dirname, '../config/chat-plugins/faqs.json');

let roomFaqs = {};
try {
	roomFaqs = require(ROOMFAQ_FILE);
} catch (e) {
	if (e.code !== 'MODULE_NOT_FOUND') throw e;
}
if (!roomFaqs || typeof roomFaqs !== 'object') roomFaqs = {};

function saveRoomFaqs() {
	fs.writeFile(ROOMFAQ_FILE, JSON.stringify(roomFaqs), () => {});
}

// Aliases are implemented as a "regular" FAQ entry starting with a >. EX: {a: "text", b: ">a"}
// This is done to allow easy checking whether a key is associated with a value or alias as well as preserve backwards compatibility.
function getAlias(roomid, key) {
	if (!roomFaqs[roomid]) return false;
	let value = roomFaqs[roomid][key];
	if (value && value[0] === '>') return value.substr(1);
	return false;
}

exports.commands = {
	addfaq: function (target, room, user) {
		if (!this.canTalk()) return this.errorReply("You cannot do this while unable to talk.");
		if (!this.can('declare', null, room)) return false;
		if (!room.chatRoomData) return this.errorReply("This command is unavailable in temporary rooms.");
		let [topic, ...text] = target.split(',');

		topic = toId(topic);
		if (!(topic && text.length)) return this.parse('/help roomfaq');
		text = text.join(',').trim();
		if (topic.length > 25) return this.errorReply("FAQ topics should not exceed 25 characters.");
		if (text.length > 500) return this.errorReply("FAQ entries should not exceed 500 characters.");

		text = text.replace(/^>/, '>');

		if (!roomFaqs[room.id]) roomFaqs[room.id] = {};
		roomFaqs[room.id][topic] = text;
		saveRoomFaqs();
		this.sendReplyBox(Chat.parseText(text));
		this.privateModCommand(`(${user.name} added a FAQ for '${topic}')`);
	},
	removefaq: function (target, room, user) {
		if (!this.canTalk()) return this.errorReply("You cannot do this while unable to talk.");
		if (!this.can('declare', null, room)) return false;
		if (!room.chatRoomData) return this.errorReply("This command is unavailable in temporary rooms.");
		let topic = toId(target);
		if (!topic) return this.parse('/help roomfaq');

		if (!(roomFaqs[room.id] && roomFaqs[room.id][topic])) return this.errorReply("Invalid topic.");
		delete roomFaqs[room.id][topic];
		Object.keys(roomFaqs[room.id]).filter(val => getAlias(room.id, val) === topic).map(val => delete roomFaqs[room.id][val]);
		if (!Object.keys(roomFaqs[room.id]).length) delete roomFaqs[room.id];
		saveRoomFaqs();
		this.privateModCommand(`(${user.name} removed the FAQ for '${topic}')`);
	},
	addalias: function (target, room, user) {
		if (!this.canTalk()) return this.errorReply("You cannot do this while unable to talk.");
		if (!this.can('declare', null, room)) return false;
		if (!room.chatRoomData) return this.errorReply("This command is unavailable in temporary rooms.");
		let [alias, topic] = target.split(',').map(val => toId(val));

		if (!(alias && topic)) return this.parse('/help roomfaq');
		if (alias.length > 25) return this.errorReply("FAQ topics should not exceed 25 characters.");

		if (!(roomFaqs[room.id] && topic in roomFaqs[room.id])) return this.errorReply(`The topic ${topic} was not found in this room's faq list.`);
		if (getAlias(room.id, topic)) return this.errorReply(`You cannot make an alias of an alias. Use /addalias ${alias}, ${getAlias(room.id, topic)} instead.`);
		roomFaqs[room.id][alias] = `>${topic}`;
		saveRoomFaqs();
		this.privateModCommand(`(${user.name} added an alias for '${topic}': ${alias})`);
	},
	rfaq: 'roomfaq',
	roomfaq: function (target, room, user) {
		if (!roomFaqs[room.id]) return this.errorReply("This room has no FAQ topics.");
		let topic = toId(target);
		if (topic === 'constructor') return false;
		if (!topic) return this.sendReplyBox(`List of topics in this room: ${Object.keys(roomFaqs[room.id]).filter(val => !getAlias(room.id, val)).sort((a, b) => a.localeCompare(b)).join(', ')}`);
		if (!roomFaqs[room.id][topic]) return this.errorReply("Invalid topic.");
		topic = getAlias(room.id, topic) || topic;

		if (!this.runBroadcast()) return;
		this.sendReplyBox(Chat.parseText(roomFaqs[room.id][topic]));
		if (!this.broadcasting && user.can('declare', null, room)) this.sendReplyBox(`<code>/addfaq ${topic}, ${Chat.escapeHTML(roomFaqs[room.id][topic])}</code>`);
	},
	roomfaqhelp: [
		"/roomfaq - Shows the list of all available FAQ topics",
		"/roomfaq <topic> - Shows the FAQ for <topic>.",
		"/addfaq <topic>, <text> - Adds an entry for <topic> in this room or updates it. Requires: # & ~",
		"/addalias <alias>, <topic> - Adds <alias> as an alias for <topic>, displaying it when users use /roomfaq <alias>. Requires: # & ~",
		"/removefaq <topic> - Removes the entry for <topic> in this room. If used on an alias, removes the alias. Requires: # & ~",
	],
};
<commit_msg>Roomfaq: display aliases for roomfaqs when ROs use /roomfaq<commit_after>'use strict';

const fs = require('fs');
const path = require('path');

const ROOMFAQ_FILE = path.resolve(__dirname, '../config/chat-plugins/faqs.json');

let roomFaqs = {};
try {
	roomFaqs = require(ROOMFAQ_FILE);
} catch (e) {
	if (e.code !== 'MODULE_NOT_FOUND') throw e;
}
if (!roomFaqs || typeof roomFaqs !== 'object') roomFaqs = {};

function saveRoomFaqs() {
	fs.writeFile(ROOMFAQ_FILE, JSON.stringify(roomFaqs), () => {});
}

// Aliases are implemented as a "regular" FAQ entry starting with a >. EX: {a: "text", b: ">a"}
// This is done to allow easy checking whether a key is associated with a value or alias as well as preserve backwards compatibility.
function getAlias(roomid, key) {
	if (!roomFaqs[roomid]) return false;
	let value = roomFaqs[roomid][key];
	if (value && value[0] === '>') return value.substr(1);
	return false;
}

exports.commands = {
	addfaq: function (target, room, user) {
		if (!this.canTalk()) return this.errorReply("You cannot do this while unable to talk.");
		if (!this.can('declare', null, room)) return false;
		if (!room.chatRoomData) return this.errorReply("This command is unavailable in temporary rooms.");
		let [topic, ...text] = target.split(',');

		topic = toId(topic);
		if (!(topic && text.length)) return this.parse('/help roomfaq');
		text = text.join(',').trim();
		if (topic.length > 25) return this.errorReply("FAQ topics should not exceed 25 characters.");
		if (text.length > 500) return this.errorReply("FAQ entries should not exceed 500 characters.");

		text = text.replace(/^>/, '>');

		if (!roomFaqs[room.id]) roomFaqs[room.id] = {};
		roomFaqs[room.id][topic] = text;
		saveRoomFaqs();
		this.sendReplyBox(Chat.parseText(text));
		this.privateModCommand(`(${user.name} added a FAQ for '${topic}')`);
	},
	removefaq: function (target, room, user) {
		if (!this.canTalk()) return this.errorReply("You cannot do this while unable to talk.");
		if (!this.can('declare', null, room)) return false;
		if (!room.chatRoomData) return this.errorReply("This command is unavailable in temporary rooms.");
		let topic = toId(target);
		if (!topic) return this.parse('/help roomfaq');

		if (!(roomFaqs[room.id] && roomFaqs[room.id][topic])) return this.errorReply("Invalid topic.");
		delete roomFaqs[room.id][topic];
		Object.keys(roomFaqs[room.id]).filter(val => getAlias(room.id, val) === topic).map(val => delete roomFaqs[room.id][val]);
		if (!Object.keys(roomFaqs[room.id]).length) delete roomFaqs[room.id];
		saveRoomFaqs();
		this.privateModCommand(`(${user.name} removed the FAQ for '${topic}')`);
	},
	addalias: function (target, room, user) {
		if (!this.canTalk()) return this.errorReply("You cannot do this while unable to talk.");
		if (!this.can('declare', null, room)) return false;
		if (!room.chatRoomData) return this.errorReply("This command is unavailable in temporary rooms.");
		let [alias, topic] = target.split(',').map(val => toId(val));

		if (!(alias && topic)) return this.parse('/help roomfaq');
		if (alias.length > 25) return this.errorReply("FAQ topics should not exceed 25 characters.");

		if (!(roomFaqs[room.id] && topic in roomFaqs[room.id])) return this.errorReply(`The topic ${topic} was not found in this room's faq list.`);
		if (getAlias(room.id, topic)) return this.errorReply(`You cannot make an alias of an alias. Use /addalias ${alias}, ${getAlias(room.id, topic)} instead.`);
		roomFaqs[room.id][alias] = `>${topic}`;
		saveRoomFaqs();
		this.privateModCommand(`(${user.name} added an alias for '${topic}': ${alias})`);
	},
	rfaq: 'roomfaq',
	roomfaq: function (target, room, user) {
		if (!roomFaqs[room.id]) return this.errorReply("This room has no FAQ topics.");
		let topic = toId(target);
		if (topic === 'constructor') return false;
		if (!topic) return this.sendReplyBox(`List of topics in this room: ${Object.keys(roomFaqs[room.id]).filter(val => !getAlias(room.id, val)).sort((a, b) => a.localeCompare(b)).join(', ')}`);
		if (!roomFaqs[room.id][topic]) return this.errorReply("Invalid topic.");
		topic = getAlias(room.id, topic) || topic;

		if (!this.runBroadcast()) return;
		this.sendReplyBox(Chat.parseText(roomFaqs[room.id][topic]));
		if (!this.broadcasting && user.can('declare', null, room)) {
			let extra = `<code>/addfaq ${topic}, ${Chat.escapeHTML(roomFaqs[room.id][topic])}</code>`;
			let aliases = Object.keys(roomFaqs[room.id]).filter(val => getAlias(room.id, val) === topic);
			if (aliases.length) {
				extra += `<br/><br/>Aliases: ${Object.keys(roomFaqs[room.id]).filter(val => getAlias(room.id, val) === topic).join(', ')}`;
			}
			this.sendReplyBox(extra);
		}
	},
	roomfaqhelp: [
		"/roomfaq - Shows the list of all available FAQ topics",
		"/roomfaq <topic> - Shows the FAQ for <topic>.",
		"/addfaq <topic>, <text> - Adds an entry for <topic> in this room or updates it. Requires: # & ~",
		"/addalias <alias>, <topic> - Adds <alias> as an alias for <topic>, displaying it when users use /roomfaq <alias>. Requires: # & ~",
		"/removefaq <topic> - Removes the entry for <topic> in this room. If used on an alias, removes the alias. Requires: # & ~",
	],
};
<|endoftext|>


<commit_before>"use strict";

// declare public exports
module.exports = {
  evalReportFolder: evalReportFolder,
  evalReport: evalReport
}

var sfutil = require('./sfutil.js');
var jsforce = require('jsforce');
var fs = require('fs');
var _ = require('underscore');
var s3 = require('./s3.js');

var conn = {};
var access = {};

function evalReportFolder (folderName, sfacess, sfconn, callback) {
    conn = sfconn;
    access = sfacess;
    folderName = _.isUndefined(folderName) ? 'Current Actions' : folderName;

    conn.query("SELECT Id, Name FROM Folder WHERE Name = '"+folderName+"'",
    function(err, result) {
        if (err) { return console.error(err); }
        var folderID = result.records[0].Id;
        conn.query("SELECT Id, Name, Description FROM Report WHERE Ownerid = '"+folderID+"' and Ownerid != null", function(err, result) {
            if (err) { return console.error(err); }
            var records =[];
            for (var i = 0; i < result.records.length; i++) {
                //console.log('evalReport: '+result.records[i].Id);
                evalReport(result.records[i].Id, access, conn, callback);
                //records.push();
            }
            //return records;
        });
    });
}

function evalReport (reportId, sfacesss, sfconn, callback) {
    conn = sfconn;
    access = sfacesss;

    // execute report synchronously with details option,
    // to get detail rows in execution result.
    var report = conn.analytics.report(reportId);
    report.execute({ details: true }, function(err, report) {
        if (err) {
            return console.error(err);
        }
        saveOutput("full.json", JSON.stringify(report));
        var headers = createColumnHeaders(report);
        report.headers = headers;
        var groupingsDown = report.groupingsDown.groupings;
        var path = [];
        var path_node = {
            label : report.attributes.reportName,
            value : report.attributes.reportId.replace(" ", "")
        };
        path.push(path_node);
        var insights = evalGrouping(groupingsDown, path, report, 0, []);
        //console.log(insights);
        //saveOutput("insights.json", JSON.stringify(insights));

        createInsights(insights);
    });
}

function createInsights (insights) {

    conn.bulk.load("Insight__c", "insert", insights, function(err, rets) {
      if (err) { return console.error(err); }
      for (var i=0; i < rets.length; i++) {
        if (rets[i].success) {
          console.log("#" + (i+1) + " loaded successfully, id = " + rets[i].id);
        } else {
          console.log("#" + (i+1) + " error occurred, message = " + rets[i].errors.join(', '));
        }
      }
      // ...
    });

}

function evalGrouping (parentGroup, path, report, level, insights) {

    for (var i = 0; i < parentGroup.length; i++) {

        // eval this group
        var group = parentGroup[i];
        var clone_path = _.clone(path);
        console.log('going to nonNullValue: '+group.value );
        var path_node = {
            label : group.label,
            value : nonNullValue(group.value).replace(" ", "")
        };
        clone_path.push(path_node);

        var insight = evalData(group, clone_path, report, level);
        if (insight != null) {
            insights.push(insight);
        }

        // eval child groupings
        var childGroup = group.groupings;
        evalGrouping(childGroup, clone_path, report, level+1, insights);
    }
    return insights;
}

function evalData (group, path, report, level) {
    console.log('path: '+arrayFromKey(path, "value").join(".")+' key: '+group.key+' label: '+group.label+' value: '+group.value+' level: '+level);

    var keyT = group.key+'!T';
    var data = report.factMap[keyT];
    var count = data.rows.length;
    if (count === 0) {
        return;
    }

    var store = {};

    store.data = data;
    store.path = path;
    store.label = group.label;
    store.value = group.value;
    store.groupingColumnInfo = groupingColumnInfo;
    if (data.rows.length <= 0) {
        return;
    }
    store.detailColumnInfo = report.reportExtendedMetadata.detailColumnInfo;
    console.log(table);
    saveOutput('store.json', JSON.stringify(store), arrayFromKey(path, "value").join("."));

    // var answer = {};
    // answer['title'] = path.join(".");
    // answer['details'] = 'Found '+data.rows.length + ' objects.';
    // answer['path'] = path.join(".");
    // answer['guid'] = '0';
    //
    // return answer;
    var insight = {};

    insight.Data_Source__c = report.attributes.reportName;
    insight.ReportID__c = report.attributes.reportId;

    var groupInfo =  groupingColumnInfoForLevel(level, report);
    var typeLabel = report.reportMetadata.reportType.label;
    var labelPath = arrayFromKey(path, "label").join(" > ");

    insight.Name = '('+ count +') '+typeLabel+' from '+report.attributes.reportName;
    insight.Long_Name__c = '('+ count +') '+typeLabel+' matching: '+labelPath
    var table = buildTable(report.headers, data.rows);
    insight.Table_Data__c = table;

    insight.Details__c = count+' '+report.reportMetadata.reportType.label+' found.';
    insight.Report_Type_Label__c = report.reportMetadata.reportType.label;
    insight.Path__c = arrayFromKey(path, "value").join(".");

    // create the parents list
    insight.Parents__c = '';
    if (path.length > 1) {
        // TODO: consider moving the ul tag to the component
        var parents = '
';

        for (var i = 1; i < path.length; i++) {
            var groupingColumnInfo =  groupingColumnInfoForLevel(i -1, report);
            console.log(JSON.stringify(groupingColumnInfo));
            var node = path[i];
            var dataType = groupingColumnInfo.dataType;
            var node_li = ''+groupingColumnInfo.label+': ';
            if (node.value != null && dataType === "string") {
                var myRe = /(\d\d\d\d\d)/;
                var myArray = myRe.exec(node.value);
                console.log(node.value);

                if (myArray!= null && myArray.length > 0) {
                    node_li = node_li + ''+node.label+'';
                    insight = setAssocForLevel(node.value, node.label, i, insight);
                }
            } else {
                node_li = node_li +node.label
            }
        parents = parents + '
'+node_li+'
';
        }
        parents = parents + '
';
        insight.Parents__c = parents;
    }

    return insight;
}

function setAssocForLevel (assoc_id, assoc_label, level, insight) {
    if (level === 1) {
        insight.AssocID__c = assoc_id;
        insight.AssocLabel__c = assoc_label;
    } else if (level === 2) {
        insight.Assoc2ID__c = assoc_id;
        insight.AssocLabel2__c = assoc_label;
    } else if (level === 3) {
        insight.Assoc3ID__c = assoc_id;
        insight.AssocLabel3__c = assoc_label;
    }

    return insight;
}

function buildTable (headers, rows) {

    if (rows.length != 0 && headers.length != rows[0].dataCells.length) {
        console.log("ERROR mismatch in lengths: "+headers+ " --- "+ rows[0].dataCells);
        return;
    }

    var html = '

';

    for (var i = 0; i < headers.length; i++) {
        var header = headers[i];
        html = html + '';
        html = html + header;
        html = html + '	';
    }
    html = html + '

';

    for (var i = 0; i < rows.length; i++) {
        var cells = rows[i].dataCells;
        html = html + '
';
        for (var cell_i = 0; cell_i < cells.length; cell_i++) {
            var cell = cells[cell_i];
            var head = headers[cell_i];

            html = html + '';
            html = html + ''+cell.label+'	';

            //console.log('col: '+head+' label: '+cell.label+' value:'+cell.value);
        }
    }

    html = html + '

';

    return html;
}

function arrayFromKey (array, key) {
    var new_array = [];
    for (var i = 0; i < array.length; i++) {
        new_array.push(array[i][key]);
    }
    return new_array;
}

function groupingColumnInfoForLevel (level, report) {
    var groupingColumnInfo = report.reportExtendedMetadata.groupingColumnInfo;
    for(var key in groupingColumnInfo) {
        if (groupingColumnInfo[key].groupingLevel === level) {
            return groupingColumnInfo[key];
        }
    }
}

function createColumnHeaders (report) {
    // create the array of column headers
    var detailColumns = report.reportMetadata.detailColumns;
    var detailColumnInfo = report.reportExtendedMetadata.detailColumnInfo;

    var columns = [];
    for (var i = 0; i < detailColumns.length; i++) {
        var detailColumn = detailColumns[i];
        var info = detailColumnInfo[detailColumn];

        columns.push(info.label);
    }
    return columns;
}

function nonNullValue (value) {
    if ( _.isNull(value)) {
        console.log('need to repalce a null value of: '+value)
        var thing = "Other";
        return thing;
    }

    if (!_.isString(value)) {
        return JSON.stringify(value);
    }
    //console.log(value);
    return value;
}

function evalMetaData (reportId) {
    conn.analytics.report(reportId).describe(function(err, meta) {
        if (err) { return console.error(err); }
        console.log(JSON.stringify(meta.reportMetadata, null, 4));
        console.log(JSON.stringify(meta.reportTypeMetadata, null, 4));
        console.log(JSON.stringify(meta.reportExtendedMetadata, null, 4));
    });
}

function getReports() {
    conn.analytics.reports(function(err, reports) {
        if (err) { return console.error(err); }

        console.log("reports length: "+reports.length);

        var lines = [];
        for (var i=0; i < 18; i++) {
            // console.log(reports[i].id);
            // console.log(reports[i].name);
            // var line = [reports[i].id, reports[i].name];
            //lines.push(reports[i].id);
            evalReport(reports[i].id);
            // if ( i % 5) {
            //     suspend();
            // }
        }
        //saveOutput("out.json", JSON.stringify(lines));

        // var answer = {};
        // answer['title'] = 'Report List';
        // answer['details'] = 'Found '+lines.length + ' reports.';
        // answer['path'] = 'reportList';
        // answer['guid'] = '0';
        // saveOutput("answer.json", JSON.stringify(answer));
    });

}

// function suspend() {
//     sleep.sleep(30000);
// }

function saveOutput (filename, output, dir, callback) {

    if (access) {
        // we have an access object so we'll use S3

        if (dir) {
            filename = dir+'/'+filename;
        }

        s3.uploadObject(access.orgid, filename, output, function(result) {
            console.log(result);
        });

        return;
    }

    if (dir && !fs.existsSync(dir)){
        fs.mkdirSync(dir);
        filename = dir+'/'+filename;
    }

    fs.writeFile(filename, output, function(err) {
        if(err) {
            return console.log(err);
        }

        console.log("The file was saved!");
    });
}
<commit_msg>path needs to be / not .<commit_after>"use strict";

// declare public exports
module.exports = {
  evalReportFolder: evalReportFolder,
  evalReport: evalReport
}

var sfutil = require('./sfutil.js');
var jsforce = require('jsforce');
var fs = require('fs');
var _ = require('underscore');
var s3 = require('./s3.js');

var conn = {};
var access = {};

function evalReportFolder (folderName, sfacess, sfconn, callback) {
    conn = sfconn;
    access = sfacess;
    folderName = _.isUndefined(folderName) ? 'Current Actions' : folderName;

    conn.query("SELECT Id, Name FROM Folder WHERE Name = '"+folderName+"'",
    function(err, result) {
        if (err) { return console.error(err); }
        var folderID = result.records[0].Id;
        conn.query("SELECT Id, Name, Description FROM Report WHERE Ownerid = '"+folderID+"' and Ownerid != null", function(err, result) {
            if (err) { return console.error(err); }
            var records =[];
            for (var i = 0; i < result.records.length; i++) {
                //console.log('evalReport: '+result.records[i].Id);
                evalReport(result.records[i].Id, access, conn, callback);
                //records.push();
            }
            //return records;
        });
    });
}

function evalReport (reportId, sfacesss, sfconn, callback) {
    conn = sfconn;
    access = sfacesss;

    // execute report synchronously with details option,
    // to get detail rows in execution result.
    var report = conn.analytics.report(reportId);
    report.execute({ details: true }, function(err, report) {
        if (err) {
            return console.error(err);
        }
        saveOutput("full.json", JSON.stringify(report));
        var headers = createColumnHeaders(report);
        report.headers = headers;
        var groupingsDown = report.groupingsDown.groupings;
        var path = [];
        var path_node = {
            label : report.attributes.reportName,
            value : report.attributes.reportId.replace(" ", "")
        };
        path.push(path_node);
        var insights = evalGrouping(groupingsDown, path, report, 0, []);
        //console.log(insights);
        //saveOutput("insights.json", JSON.stringify(insights));

        createInsights(insights);
    });
}

function createInsights (insights) {

    conn.bulk.load("Insight__c", "insert", insights, function(err, rets) {
      if (err) { return console.error(err); }
      for (var i=0; i < rets.length; i++) {
        if (rets[i].success) {
          console.log("#" + (i+1) + " loaded successfully, id = " + rets[i].id);
        } else {
          console.log("#" + (i+1) + " error occurred, message = " + rets[i].errors.join(', '));
        }
      }
      // ...
    });

}

function evalGrouping (parentGroup, path, report, level, insights) {

    for (var i = 0; i < parentGroup.length; i++) {

        // eval this group
        var group = parentGroup[i];
        var clone_path = _.clone(path);
        console.log('going to nonNullValue: '+group.value );
        var path_node = {
            label : group.label,
            value : nonNullValue(group.value).replace(" ", "")
        };
        clone_path.push(path_node);

        var insight = evalData(group, clone_path, report, level);
        if (insight != null) {
            insights.push(insight);
        }

        // eval child groupings
        var childGroup = group.groupings;
        evalGrouping(childGroup, clone_path, report, level+1, insights);
    }
    return insights;
}

function evalData (group, path, report, level) {
    console.log('path: '+arrayFromKey(path, "value").join(".")+' key: '+group.key+' label: '+group.label+' value: '+group.value+' level: '+level);

    var keyT = group.key+'!T';
    var data = report.factMap[keyT];
    var count = data.rows.length;
    if (count === 0) {
        return;
    }

    var store = {};

    store.data = data;
    store.path = path;
    store.label = group.label;
    store.value = group.value;
    store.groupingColumnInfo = groupingColumnInfo;
    if (data.rows.length <= 0) {
        return;
    }
    store.detailColumnInfo = report.reportExtendedMetadata.detailColumnInfo;
    console.log(table);
    saveOutput('store.json', JSON.stringify(store), arrayFromKey(path, "value").join("/"));

    // var answer = {};
    // answer['title'] = path.join(".");
    // answer['details'] = 'Found '+data.rows.length + ' objects.';
    // answer['path'] = path.join(".");
    // answer['guid'] = '0';
    //
    // return answer;
    var insight = {};

    insight.Data_Source__c = report.attributes.reportName;
    insight.ReportID__c = report.attributes.reportId;

    var groupInfo =  groupingColumnInfoForLevel(level, report);
    var typeLabel = report.reportMetadata.reportType.label;
    var labelPath = arrayFromKey(path, "label").join(" > ");

    insight.Name = '('+ count +') '+typeLabel+' from '+report.attributes.reportName;
    insight.Long_Name__c = '('+ count +') '+typeLabel+' matching: '+labelPath
    var table = buildTable(report.headers, data.rows);
    insight.Table_Data__c = table;

    insight.Details__c = count+' '+report.reportMetadata.reportType.label+' found.';
    insight.Report_Type_Label__c = report.reportMetadata.reportType.label;
    insight.Path__c = arrayFromKey(path, "value").join(".");

    // create the parents list
    insight.Parents__c = '';
    if (path.length > 1) {
        // TODO: consider moving the ul tag to the component
        var parents = '
';

        for (var i = 1; i < path.length; i++) {
            var groupingColumnInfo =  groupingColumnInfoForLevel(i -1, report);
            console.log(JSON.stringify(groupingColumnInfo));
            var node = path[i];
            var dataType = groupingColumnInfo.dataType;
            var node_li = ''+groupingColumnInfo.label+': ';
            if (node.value != null && dataType === "string") {
                var myRe = /(\d\d\d\d\d)/;
                var myArray = myRe.exec(node.value);
                console.log(node.value);

                if (myArray!= null && myArray.length > 0) {
                    node_li = node_li + ''+node.label+'';
                    insight = setAssocForLevel(node.value, node.label, i, insight);
                }
            } else {
                node_li = node_li +node.label
            }
        parents = parents + '
'+node_li+'
';
        }
        parents = parents + '
';
        insight.Parents__c = parents;
    }

    return insight;
}

function setAssocForLevel (assoc_id, assoc_label, level, insight) {
    if (level === 1) {
        insight.AssocID__c = assoc_id;
        insight.AssocLabel__c = assoc_label;
    } else if (level === 2) {
        insight.Assoc2ID__c = assoc_id;
        insight.AssocLabel2__c = assoc_label;
    } else if (level === 3) {
        insight.Assoc3ID__c = assoc_id;
        insight.AssocLabel3__c = assoc_label;
    }

    return insight;
}

function buildTable (headers, rows) {

    if (rows.length != 0 && headers.length != rows[0].dataCells.length) {
        console.log("ERROR mismatch in lengths: "+headers+ " --- "+ rows[0].dataCells);
        return;
    }

    var html = '

';

    for (var i = 0; i < headers.length; i++) {
        var header = headers[i];
        html = html + '';
        html = html + header;
        html = html + '	';
    }
    html = html + '

';

    for (var i = 0; i < rows.length; i++) {
        var cells = rows[i].dataCells;
        html = html + '
';
        for (var cell_i = 0; cell_i < cells.length; cell_i++) {
            var cell = cells[cell_i];
            var head = headers[cell_i];

            html = html + '';
            html = html + ''+cell.label+'	';

            //console.log('col: '+head+' label: '+cell.label+' value:'+cell.value);
        }
    }

    html = html + '

';

    return html;
}

function arrayFromKey (array, key) {
    var new_array = [];
    for (var i = 0; i < array.length; i++) {
        new_array.push(array[i][key]);
    }
    return new_array;
}

function groupingColumnInfoForLevel (level, report) {
    var groupingColumnInfo = report.reportExtendedMetadata.groupingColumnInfo;
    for(var key in groupingColumnInfo) {
        if (groupingColumnInfo[key].groupingLevel === level) {
            return groupingColumnInfo[key];
        }
    }
}

function createColumnHeaders (report) {
    // create the array of column headers
    var detailColumns = report.reportMetadata.detailColumns;
    var detailColumnInfo = report.reportExtendedMetadata.detailColumnInfo;

    var columns = [];
    for (var i = 0; i < detailColumns.length; i++) {
        var detailColumn = detailColumns[i];
        var info = detailColumnInfo[detailColumn];

        columns.push(info.label);
    }
    return columns;
}

function nonNullValue (value) {
    if ( _.isNull(value)) {
        console.log('need to repalce a null value of: '+value)
        var thing = "Other";
        return thing;
    }

    if (!_.isString(value)) {
        return JSON.stringify(value);
    }
    //console.log(value);
    return value;
}

function evalMetaData (reportId) {
    conn.analytics.report(reportId).describe(function(err, meta) {
        if (err) { return console.error(err); }
        console.log(JSON.stringify(meta.reportMetadata, null, 4));
        console.log(JSON.stringify(meta.reportTypeMetadata, null, 4));
        console.log(JSON.stringify(meta.reportExtendedMetadata, null, 4));
    });
}

function getReports() {
    conn.analytics.reports(function(err, reports) {
        if (err) { return console.error(err); }

        console.log("reports length: "+reports.length);

        var lines = [];
        for (var i=0; i < 18; i++) {
            // console.log(reports[i].id);
            // console.log(reports[i].name);
            // var line = [reports[i].id, reports[i].name];
            //lines.push(reports[i].id);
            evalReport(reports[i].id);
            // if ( i % 5) {
            //     suspend();
            // }
        }
        //saveOutput("out.json", JSON.stringify(lines));

        // var answer = {};
        // answer['title'] = 'Report List';
        // answer['details'] = 'Found '+lines.length + ' reports.';
        // answer['path'] = 'reportList';
        // answer['guid'] = '0';
        // saveOutput("answer.json", JSON.stringify(answer));
    });

}

// function suspend() {
//     sleep.sleep(30000);
// }

function saveOutput (filename, output, dir, callback) {

    if (access) {
        // we have an access object so we'll use S3

        if (dir) {
            filename = dir+'/'+filename;
        }

        s3.uploadObject(access.orgid, filename, output, function(result) {
            console.log(result);
        });

        return;
    }

    if (dir && !fs.existsSync(dir)){
        fs.mkdirSync(dir);
        filename = dir+'/'+filename;
    }

    fs.writeFile(filename, output, function(err) {
        if(err) {
            return console.log(err);
        }

        console.log("The file was saved!");
    });
}
<|endoftext|>


<commit_before>(function (exports) {
'use strict';

//import webeid from './vendor/web-eid/web-eid.js';
//TODO configure rollup so, that import works

function parseMessage(message){
  try {
    var r = JSON.parse(message);
    console.log(r);
    return r;
  } catch (e) {
    return {error:e}
  }
}

const autoReconnectInterval = 1000 * 3;
const autoReconnectMaxCount = 3;
let autoReconnectCounter = 0;

var authenticatedWebSocket = function (url) {
  return new Promise(function (resolve, reject) {
    var socket = new WebSocket(url)
    socket.onclose = function (c){
      if (c.code !== 1000) { // server sent unclean close, we try to reconnect
        if (autoReconnectCounter < autoReconnectMaxCount ) {
        autoReconnectCounter += 1;
        console.log('reconnect '+ url + ' in ' + autoReconnectInterval + ' count ' + autoReconnectCounter)
        setTimeout(function(){
            console.log("reconnecting...");
            authenticatedWebSocket(url)
              .then(function (ws) {resolve(ws);})
              .catch(function(e) {reject(e)});
        },autoReconnectInterval);
      } else {
        console.log("reached autoReconnectMaxCount "+autoReconnectMaxCount);
        reject(new Error('server has disappeared'));
      }
    } else { // server sent clean close
        console.dir(c);
        reject(new Error('server closed connection'));
      }
    }
    socket.onmessage = function (m) {
      try {
        var msg = JSON.parse(m.data);
      } catch(e) {
        console.log('Server did not send JSON');
        reject(new Error('please contact your sysadmin!'));
      }
      if (!msg.nonce){
        console.log('Server did not send nonce');
        reject(new Error('please contact your sysadmin!'));
      } else {
        webeid.authenticate(msg.nonce).then(function (token) {
          socket.send(JSON.stringify({token: token}));
          console.log('sent token to server');
          socket.onmessage = function (m){ //if server answers, then we are ok
            console.dir(m);
            socket.onclose = undefined;
            socket.onmessage = undefined;
            resolve(socket);
          }
        }, function (reason) { // TODO handle reasons
          socket.send(JSON.stringify({user : reason.message}));
          socket.close();
          reject(new Error(reason.message));
          /*
          if (reason.message == 'CKR_FUNCTION_CANCELED'){
            reject(new Error('user cancel'));
          } else {
            // !? try again ...
          }
          */
        });
      } // !msg.nonce
    } //onmessage

  });
}

// -----

function hello(logo, html) {
  const wsPING_INTERVAL = 30000;
  function msg(m){
    var div = document.createElement("div");
    var content = document.createTextNode(m);
    div.appendChild(content);
    html.appendChild(div);
    //return div;
  }
  function btn(m,f){
    var theButton = document.createElement("button");
    theButton.innerHTML = m;
    theButton.onclick = f;
    theButton.className = "btn";
    return theButton;
  }
  function input(m,f){
    var div = document.createElement("div");
    var bt = btn("
⚲
",f)
    div.appendChild(bt);
    bt.insertAdjacentHTML('beforebegin','<input "type=text" name="q" className="message-text">');
    return div;
  }

    msg('looking for web-eid, please wait ...')
    webeid.isAvailable({timeout:6}).then(function(d) {

      let signin = btn('auth',function click () {
        msg('asking nonce from server, please wait ...')
        const url = window.location.href.replace('https','wss').slice(0, -1)+':443/chat';
        authenticatedWebSocket(url).then(function (ws) {
          console.dir(ws);
          msg('auth ok');
          let qchat = input('q',function click () {
            ws.send(JSON.stringify({raw:this.parentElement.childNodes[0].value}));
            this.parentElement.childNodes[0].value = "";
          });
          html.appendChild(qchat);
          window.setInterval(function ping() {
            console.log('ping '+Date.now());
            ws.send(JSON.stringify({ping:Date.now()}));
          }, wsPING_INTERVAL);
          ws.onclose = function onclose(c){
            console.dir(c);
            msg('server closed');
          }
          ws.onmessage = function onmessage(m) {
            console.dir(m);
            msg(m.data);
          }
        }).catch(function(e) {
          console.dir(e);
          msg('no server, redirecting...');
          setTimeout(function(){
              console.log("rediderct to interpol");
              window.location.href = "https://www.interpol.int/Crime-areas/Cybercrime/Cybercrime"
          },5000);
        }); //authenticatedWebSocket
        html.removeChild(signin);

      });
      html.appendChild(signin);
    }).catch(function(e) { // hasExtension
        console.dir(e);
        msg('no web-eid, redirecting...');
        setTimeout(function(){
            console.log("rediderct to web-eid.com");
            window.location.href = "https://web-eid.com/"
        },5000);
    }); // isAvailable
}

exports.hello = hello;

}((this.main = this.main || {})));
<commit_msg>add cookies<commit_after>(function (exports) {
'use strict';

//import webeid from './vendor/web-eid/web-eid.js';
//TODO configure rollup so, that import works

function guid() {
  function s4() {
    return Math.floor((1 + Math.random()) * 0x10000)
      .toString(16)
      .substring(1);
  }
  return s4() + s4() + '-' + s4() + '-' + s4() + '-' +
    s4() + '-' + s4() + s4() + s4();
}
function getGuid() {
  var cookiestring=RegExp("guid[^;]+").exec(document.cookie);
  return unescape(!!cookiestring ? cookiestring.toString().replace(/^[^=]+./,"") : "");
}

function parseMessage(message){
  try {
    var r = JSON.parse(message);
    console.log(r);
    return r;
  } catch (e) {
    return {error:e}
  }
}

const autoReconnectInterval = 1000 * 3;
const autoReconnectMaxCount = 3;
let autoReconnectCounter = 0;

var authenticatedWebSocket = function (url) {
  return new Promise(function (resolve, reject) {
    var socket = new WebSocket(url)
    socket.onclose = function (c){
      // 1000	CLOSE_NORMAL
      // 1008	Policy Violation
      if (c.code !== 1000 && c.code !== 1008) { // server sent unclean close, we try to reconnect
        if (autoReconnectCounter < autoReconnectMaxCount ) {
        autoReconnectCounter += 1;
        console.log('reconnect '+ url + ' in ' + autoReconnectInterval + ' count ' + autoReconnectCounter)
        setTimeout(function(){
            console.log("reconnecting...");
            authenticatedWebSocket(url)
              .then(function (ws) {resolve(ws);})
              .catch(function(e) {reject(e)});
        },autoReconnectInterval);
      } else {
        console.log("reached autoReconnectMaxCount "+autoReconnectMaxCount);
        reject(new Error('server has disappeared'));
      }
    } else { // server sent clean close
        console.dir(c);
        reject(new Error('server closed connection'));
      }
    }
    socket.onmessage = function (m) {
      try {
        var msg = JSON.parse(m.data);
      } catch(e) {
        console.log('Server did not send JSON');
        reject(new Error('please contact your sysadmin!'));
      }
      if (!msg.nonce){
        console.log('Server did not send nonce');
        reject(new Error('please contact your sysadmin!'));
      } else {
        webeid.authenticate(msg.nonce).then(function (token) {
          socket.send(JSON.stringify({token: token}));
          console.log('sent token to server');
          socket.onmessage = function (m){ //if server answers, then we are ok
            console.dir(m);
            socket.onclose = undefined;
            socket.onmessage = undefined;
            resolve(socket);
          }
        }, function (reason) { // TODO handle reasons
          socket.send(JSON.stringify({user : reason.message}));
          socket.close();
          reject(new Error(reason.message));
          /*
          if (reason.message == 'CKR_FUNCTION_CANCELED'){
            reject(new Error('user cancel'));
          } else {
            // !? try again ...
          }
          */
        });
      } // !msg.nonce
    } //onmessage

  });
}

// -----

// https://developer.mozilla.org/en-US/docs/Web/API/Document/cookie#Example_5_Do_something_only_once_–_a_general_library
function executeOnce () {
  var argc = arguments.length, bImplGlob = typeof arguments[argc - 1] === "string";
  if (bImplGlob) { argc++; }
  if (argc < 3) { throw new TypeError("executeOnce - not enough arguments"); }
  var fExec = arguments[0], sKey = arguments[argc - 2];
  if (typeof fExec !== "function") { throw new TypeError("executeOnce - first argument must be a function"); }
  if (!sKey || /^(?:expires|max\-age|path|domain|secure)$/i.test(sKey)) { throw new TypeError("executeOnce - invalid identifier"); }
  if (decodeURIComponent(document.cookie.replace(new RegExp("(?:(?:^|.*;)\\s*" + encodeURIComponent(sKey).replace(/[\-\.\+\*]/g, "\\$&") + "\\s*\\=\\s*([^;]*).*$)|^.*$"), "$1")) === "1") { return false; }
  fExec.apply(argc > 3 ? arguments[1] : null, argc > 4 ? Array.prototype.slice.call(arguments, 2, argc - 2) : []);
  document.cookie = encodeURIComponent(sKey) + "=1; expires=Fri, 31 Dec 9999 23:59:59 GMT" + (bImplGlob || !arguments[argc - 1] ? "; path=/" : "");
  return true;
}

function hello(logo, html) {

  function alertCookie (sMsg) {
    var expiration_date = new Date();
    var cookie_string = '';
    expiration_date.setFullYear(expiration_date.getFullYear() + 1);
    // Build the set-cookie string:
    var uuid = guid();
    cookie_string = "guid=" + uuid + "; path=/; expires=" + expiration_date.toUTCString();
    // Create or update the cookie:
    document.cookie = cookie_string;
    alert(sMsg);
  }

  executeOnce(alertCookie, null, "this site uses cookies", "executeOnce");
  const CUID = getGuid();

  const wsPING_INTERVAL = 30000;
  function msg(m){
    var div = document.createElement("div");
    var content = document.createTextNode(m);
    div.appendChild(content);
    html.appendChild(div);
    //return div;
  }
  function btn(m,f){
    var theButton = document.createElement("button");
    theButton.innerHTML = m;
    theButton.onclick = f;
    theButton.className = "btn";
    return theButton;
  }
  function input(m,f){
    var div = document.createElement("div");
    var bt = btn("
⚲
",f)
    div.appendChild(bt);
    bt.insertAdjacentHTML('beforebegin','<input "type=text" name="q" className="message-text">');
    return div;
  }

    msg('looking for web-eid, please wait ...')
    webeid.isAvailable({timeout:6}).then(function(d) {

      let signin = btn('auth',function click () {
        msg('asking nonce from server, please wait ...')
        const url = 'wss://'+window.location.host.replace(/:\d+/, '')+':443/chat/'+CUID;

        authenticatedWebSocket(url).then(function (ws) {
          console.dir(ws);
          msg('auth ok');
          ws.send(JSON.stringify({cuid:CUID}));
          let qchat = input('q',function click () {
            ws.send(JSON.stringify({raw:this.parentElement.childNodes[0].value}));
            this.parentElement.childNodes[0].value = "";
          });
          html.appendChild(qchat);
          const ping = window.setInterval(function ping() {
            console.log('ping '+Date.now());
            ws.send(JSON.stringify({ping:Date.now()}));
          }, wsPING_INTERVAL);
          ws.onclose = function onclose(c){
            console.dir(c);
            window.clearInterval(ping);
            html.removeChild(qchat);
            msg('server closed');
            html.appendChild(signin);

          }
          ws.onmessage = function onmessage(m) {
            console.dir(m);
            msg(m.data);
          }
        }).catch(function(e) {
          console.dir(e);
          msg('no server, redirecting...');
          setTimeout(function(){
              console.log("rediderct to interpol");
              window.location.href = "https://www.interpol.int/Crime-areas/Cybercrime/Cybercrime"
          },5000);
        }); //authenticatedWebSocket
        html.removeChild(signin);

      });
      html.appendChild(signin);
    }).catch(function(e) { // hasExtension
        console.dir(e);
        msg('no web-eid, redirecting...');
        setTimeout(function(){
            console.log("rediderct to web-eid.com");
            window.location.href = "https://web-eid.com/"
        },5000);
    }); // isAvailable
}

exports.hello = hello;

}((this.main = this.main || {})));
<|endoftext|>