<commit_before>
const dispatchToActor = function(actor, activity) {
    if (!actor || !activity) return;

    HTTP.post(actor.inbox, {data: activity, npmRequestOptions: {
        httpSignature: {
            authorizationHeaderName: "Signature",
            keyId: activity.actor + "#main-key",
            key: Keys.findOne({id: activity.actor + "#main-key"}, {fields: {privateKeyPem: 1}}).privateKeyPem
        }
    }}, function(err, result) {
        //if (err) console.log("Error: ", err);
        //if (result) console.log("Result: ", result);
    });
};

dispatchActivity = function(activity) {

    activity = cleanActivityPub(activity);

    const targetArrays = ['to', 'cc', 'bto', 'bcc', 'audience'];

    for (let i = 0; i < targetArrays.length; i++) {
        const arrayName = targetArrays[i];
        const targetArray = activity[arrayName];
        for (let j = 0; j < targetArray.length; j++) {
            const targetID = targetArray[j];
            if (targetID === "https://www.w3.org/ns/activitystreams#Public")
                continue;
            let actor = Actors.findOne({id: targetID});
            if (actor)
                dispatchToActor(actor, activity);
            else {
                const list = FollowerLists.findOne({id: targetID});
                if (list)
                    for (let actorID of list.orderedItems) {
                        dispatchToActor(Actors.findOne({id: actorID}), activity);
                    }
            }
        }
    }
};

const checkFederatedActivityPermitted = function(activity) {

    //TODO: Check to see if foreign instances are blocked or banned.

    const actor = Actors.findOne({id: activity.actor});

    //I think theres some kind of race condition going on here, where it won't
    //have finished importing the actor by the time it gets to this code and so it throws an error?

    //I tried to ensure it would have to have finished by the time it gets here, but it seems like I didn't quite manage.
    if (!actor)
        throw new Meteor.Error('Actor Not Found', 'No actor with the given ID could be found in the database: ' + activity.actor);

    switch(activity.type) {

        //Users can follow without being verified. Thus, return here, instead of further down after the verification check.
        case 'Follow':
        case 'Undo':
            return;

        case 'Update':
        case 'Delete':

            checkUpdateOrDeleteActivityPermitted(activity);

        //No break here, as update and delete activities should be subject to the same restrictions as create and announce.
        case 'Create':

        if (Posts.findOne({id: activity.object.id}))
            throw new Meteor.Error('Post Already Exists', 'Cannot insert post, id already exists.');

        case 'Announce':
    }

    //TODO: Check to see if foreign actors are blocked or banned.
};

const processFederatedFollowActivity = function(activity) {
    const follower = Actors.findOne({id: activity.actor});

    const followee = getObjectFromActivity(activity, Actors);

    if (!followee)
        throw new Meteor.Error('Actor not found!', 'No actor with the given ID could be found: ' + activity.object);

    let accept = new ActivityPubActivity("Accept", followee.id, activity);

    accept.to.push(activity.actor);

    Meteor.setTimeout(function(){
        dispatchActivity(accept);
    }, 0);

    FollowerLists.update({id: followee.followers}, {$inc: {totalItems: 1}, $push: {orderedItems: activity.actor}});

    if (follower.local)
        FollowingLists.update({id: follower.following}, {$inc: {totalItems: 1}, $push: {orderedItems: activity.object}});

    return activity;
};

const processFederatedCreateActivity = function(activity) {
    const post = getObjectFromActivity(activity);

    if (Posts.findOne({id: post.id}))
        throw new Meteor.Error('Post Already Exists', 'Cannot insert post, id already exists: ' + JSON.stringify({prev: Posts.findOne({id: post.id}), post: post}));

    post.local = false;
    Posts.insert(post);

    return activity;
};

const processFederatedDeleteActivity = function(activity) {
    const postID = activity.object;

    deletePost(postID);

    return activity;
};

const processFederatedAcceptActivity = function(activity) {

    let follower;

    switch (typeof activity.object) {
        case 'string':
            follower = Actors.findOne({id: activity.object});
            break;
        case 'object':
            follower = Actors.findOne({id: activity.object.actor});
            break;
    }

    if (!follower)
        throw new Meteor.Error('Actor not found!', 'No actor with the given ID could be found: ' + JSON.stringify(activity.object));

    const followee = Actors.findOne({id: activity.actor});

    let pendingFollow = PendingFollows.findOne({follower: follower.id, followee: followee.id});

    if (!pendingFollow)
        throw new Meteor.Error('No Follow Pending!', 'No pending follow between those actors was found: ' + activity.object + ", " + activity.actor);

    FollowingLists.update({id: follower.following}, {$inc: {totalItems: 1}, $push: {orderedItems: activity.actor}});

    if (followee.local)
        FollowerLists.update({id: followee.followers}, {$inc: {totalItems: 1}, $push: {orderedItems: activity.object}});

    PendingFollows.remove(pendingFollow);

    return activity;
};

const processFederatedUndoActivity = function(activity) {

    let targetActivity = Activities.findOne({id: activity.object});

    if (targetActivity.type === "Follow"){

        const follower = Actors.findOne({id: targetActivity.actor});

        if (!follower)
            throw new Meteor.Error('Actor not found!', 'No actor with the given ID could be found: ' + targetActivity.object);

        const followee = Actors.findOne({id: targetActivity.object});

        FollowingLists.update({id: follower.following}, {$inc: {totalItems: -1}, $pull: {orderedItems: targetActivity.object}});

        if (followee.local)
            FollowerLists.update({id: followee.followers}, {$inc: {totalItems: -1}, $pull: {orderedItems: targetActivity.actor}});

        PendingFollows.remove({follower: follower.id, followee: followee.id});

        return activity;
    }
};

const processFederatedActivity = function(activity) {

    if (activity.id && Activities.findOne({id: activity.id}))
        return;

    let processFederatedActivityCallback = function(err, result) {
        try {
            checkFederatedActivityPermitted(activity);
        } catch (error) {
            if (['Post Not Present', 'Post Already Exists'].includes(error.error)) {
                const _id = Activities.insert(activity);

                return Activities.findOne({_id: _id});
            } else throw error;
        }

        switch(activity.type) {
            case 'Follow':
                try {
                    activity = processFederatedFollowActivity(activity);
                } catch (error) {
                    throw error;
                }
                break;
            case 'Create':
                try {
                    activity = processFederatedCreateActivity(activity);
                } catch (error) {
                    throw error;
                }
                break;
            case 'Delete':
            try {
                activity = processFederatedDeleteActivity(activity);
            } catch (error) {
                if (error.error === 'Post Not Present') {

                } else throw error;
            }
                break;
            case "Accept":
                activity = processFederatedAcceptActivity(activity);
                break;
        }

        Activities.insert(activity);
    }

    if (activity.actor && !Actors.findOne({id: activity.actor})) { //Is this posts actor already present? If not,
        importActivityJSONFromUrl(activity.actor, processFederatedActivityCallback); //add it, and finish the function as a callback.
    } else {
        processFederatedActivityCallback(); //If it is present, then continue as normal.
    }
};

const importActorFromActivityPubJSON = function(json) {
    if (!Actors.findOne({id: json.id})) { //Is actor already present? If not,
        json.local = false; //mark it as foreign,
        Actors.insert(json); //then add it.

        console.log("Added Actor: " + json.id);
    }
};

const importPostFromActivityPubJSON = function(json) {
    if (!Posts.findOne({id: json.id})) { //Is post already present? If not,
        json.local = false; //mark it as foreign,
        Posts.insert(json); //then add it.
    }
};

importActivityJSONFromUrl = function(url, callback) {
    console.log("Importing from: " + url);
    let json = JSON.parse(HTTP.get(url, {
        headers: {
            Accept: 'application/activity+json; profile="https://www.w3.org/ns/activitystreams#"'
        }
    }).content);

    importFromActivityPubJSON(json);

    if (callback) callback(null, json);

    return json;
};

importFromActivityPubJSON = function(json) {
    if (!json) throw new Meteor.Error('Empty JSON');

    if (!json.type) throw new Meteor.Error('Untyped ActivityPub JSON');

    if (activityPubActorTypes.includes(json.type))
        importActorFromActivityPubJSON(json);

    else if (activityPubContentTypes.includes(json.type))
        importPostFromActivityPubJSON(json);
};

Meteor.methods({
    importActivityJSONFromUrl: importActivityJSONFromUrl,
    importFromActivityPubJSON: importFromActivityPubJSON,
    postActivity: function(object) {
        const user = Meteor.users.findOne({_id: this.userId});

        let result = processClientActivity(user, object);

        return result;
    }
});

const successfulJSON = function(data) {
    response = {
        statusCode: 200
    };

    if (data) {
        response.body = cleanActivityPub(data);
    }

    return response;
};

const failedJSON = function(message) {
    return {
        statusCode: 400,
        body: {status: "fail", message: message}
    };
};

Api = new Restivus({
    apiPath: '/',
    prettyJson: true
});

Api.addRoute('.well-known/host-meta', {}, {
    get: {
        action: function () {
            let response = successfulJSON();
            response.headers = {
                'Access-Control-Allow-Origin': '*',
                'Content-Type': 'application/xml; charset=UTF-8'
            };

            response.body = '<XRD>' +
                            '  <Link rel="lrdd"' +
                            '    type="application/xrd+xml"' +
                            '    template="' +
                            process.env.ROOT_URL + '/webfinger/xrd/{uri}" />' +
                            '</XRD>'

            return response;
        }
    }
});

Api.addRoute('.well-known/host-meta.json', {}, {
    get: {
        action: function () {
            let response = successfulJSON();
            response.headers = {
                'Access-Control-Allow-Origin': '*',
                'Content-Type': 'application/json; charset=UTF-8'
            };

            response.body = {
                links: [
                    {
                        "rel": "lrdd",
                        "type": "application/json",
                        "template": process.env.ROOT_URL + "/.well-known/webfinger?resource={uri}"
                    }
                ]
            }

            return response;
        }
    }
});

Api.addRoute('.well-known/webfinger', {}, {
    get: {
        action: function () {

            let resource = this.queryParams.resource;
            var re = /(acct:@?)(.*)(@.*)/;
            let handle = resource.replace(re, "$2");
            let actor = Actors.findOne({preferredUsername: handle, local: true});

            actor = cleanActivityPub(actor);

            let response = successfulJSON();

            response.headers = {
                'Content-Type': 'application/json; charset=UTF-8'
            };

            response.body = {
                subject: resource,
                aliases: [actor.url, actor.id],
                links: [
                    {
                        "rel":"http://webfinger.net/rel/profile-page",
                        "type":"text/html",
                        "href": actor.url
                    },
                    {
                        "rel":"self",
                        "type":"application/activity+json",
                        "href": actor.id
                    }
                ]
            }

            return response;
        }
    }
});

Api.addRoute('post/:_id', {}, {
    get: {
        action: function () {
            let post = Posts.findOne({_id: this.urlParams._id});
            if (post) {
                let response = successfulJSON(post);
                response.headers = {
                    'Content-Type': 'activity/json; charset=UTF-8'
                };
                return response;
            } else return failedJSON("Unable to get post!");
        }
    }
});

Api.addRoute('activity/:_id', {}, {
    get: {
        action: function () {
            let activity = Activities.findOne({_id: this.urlParams._id});
            if (activity) {
                let response = successfulJSON(activity);
                response.headers = {
                    'Content-Type': 'activity/json; charset=UTF-8'
                };
                return response;
            } else return failedJSON("Unable to get post!");
        }
    }
});

Api.addRoute('actors/:handle', {}, {
    get: {
        action: function () {
            let actor = Actors.findOne({preferredUsername: this.urlParams.handle, local: true});
            if (actor) {
                let response = successfulJSON(actor);
                response.headers = {
                    'Content-Type': 'activity/json; charset=UTF-8'
                };
                return response;
            } else return failedJSON("Unable to get actor!");
        }
    }
});

Api.addRoute('actors/:handle/inbox', {}, {
    post: {
        action: function () {
            let object = this.request.body;
            processFederatedActivity(object);
            return successfulJSON();
        }
    }
});

Api.addRoute('actors/:handle/outbox', {}, {
    get: {
        action: function () {
            var outbox = Outboxes.findOne({id: process.env.ROOT_URL + "actors/" + this.urlParams.handle + "/outbox"});
            if (outbox) {
                let orderedItems = [];
                for (let i = 0; i < outbox.totalItems && i < 10; i++) {
                    orderedItems.push(Activities.findOne({id: outbox.orderedItems[i]}));
                }
                outbox.orderedItems = orderedItems;
                let response = successfulJSON(outbox);
                response.headers = {
                    'Content-Type': 'activity/json; charset=UTF-8'
                };
                return response;
            } else return failedJSON("Unable to get outbox!");
        }
    },
    post: {
        action: function () {
            let object = this.request.body;

            try {
                return successfulJSON(processClientActivity(object));
            } catch (exception) {
                return failedJSON(exception.reason);
            }
        }
    }
});

Api.addRoute('actors/:handle/following', {}, {
    get: {
        action: function () {
            let following = FollowingLists.findOne({id: process.env.ROOT_URL + "actors/" + this.urlParams.handle + "/following"});
            if (following) {
                let response = successfulJSON(following);
                response.headers = {
                    'Content-Type': 'activity/json; charset=UTF-8'
                };
                return response;
            } else return failedJSON("Unable to get following list!");
        }
    }
});

Api.addRoute('actors/:handle/followers', {}, {
    get: {
        action: function () {
            let actor = Actors.findOne({preferredUsername: this.urlParams.handle});
            if (actor) {
                let followers = FollowerLists.findOne({id: actor.followers});
                if (followers) {
                    return successfulJSON(followers);
                }
            }

            return failedJSON("Unable to get followers list!");
        }
    }
});
<commit_msg>add processing for undo activities<commit_after>
const dispatchToActor = function(actor, activity) {
    if (!actor || !activity) return;

    HTTP.post(actor.inbox, {data: activity, npmRequestOptions: {
        httpSignature: {
            authorizationHeaderName: "Signature",
            keyId: activity.actor + "#main-key",
            key: Keys.findOne({id: activity.actor + "#main-key"}, {fields: {privateKeyPem: 1}}).privateKeyPem
        }
    }}, function(err, result) {
        //if (err) console.log("Error: ", err);
        //if (result) console.log("Result: ", result);
    });
};

dispatchActivity = function(activity) {

    activity = cleanActivityPub(activity);

    const targetArrays = ['to', 'cc', 'bto', 'bcc', 'audience'];

    for (let i = 0; i < targetArrays.length; i++) {
        const arrayName = targetArrays[i];
        const targetArray = activity[arrayName];
        for (let j = 0; j < targetArray.length; j++) {
            const targetID = targetArray[j];
            if (targetID === "https://www.w3.org/ns/activitystreams#Public")
                continue;
            let actor = Actors.findOne({id: targetID});
            if (actor)
                dispatchToActor(actor, activity);
            else {
                const list = FollowerLists.findOne({id: targetID});
                if (list)
                    for (let actorID of list.orderedItems) {
                        dispatchToActor(Actors.findOne({id: actorID}), activity);
                    }
            }
        }
    }
};

const checkFederatedActivityPermitted = function(activity) {

    //TODO: Check to see if foreign instances are blocked or banned.

    const actor = Actors.findOne({id: activity.actor});

    //I think theres some kind of race condition going on here, where it won't
    //have finished importing the actor by the time it gets to this code and so it throws an error?

    //I tried to ensure it would have to have finished by the time it gets here, but it seems like I didn't quite manage.
    if (!actor)
        throw new Meteor.Error('Actor Not Found', 'No actor with the given ID could be found in the database: ' + activity.actor);

    switch(activity.type) {

        //Users can follow without being verified. Thus, return here, instead of further down after the verification check.
        case 'Follow':
        case 'Undo':
            return;

        case 'Update':
        case 'Delete':

            checkUpdateOrDeleteActivityPermitted(activity);

        //No break here, as update and delete activities should be subject to the same restrictions as create and announce.
        case 'Create':

        if (Posts.findOne({id: activity.object.id}))
            throw new Meteor.Error('Post Already Exists', 'Cannot insert post, id already exists.');

        case 'Announce':
    }

    //TODO: Check to see if foreign actors are blocked or banned.
};

const processFederatedFollowActivity = function(activity) {
    const follower = Actors.findOne({id: activity.actor});

    const followee = getObjectFromActivity(activity, Actors);

    if (!followee)
        throw new Meteor.Error('Actor not found!', 'No actor with the given ID could be found: ' + activity.object);

    let accept = new ActivityPubActivity("Accept", followee.id, activity);

    accept.to.push(activity.actor);

    Meteor.setTimeout(function(){
        dispatchActivity(accept);
    }, 0);

    FollowerLists.update({id: followee.followers}, {$inc: {totalItems: 1}, $push: {orderedItems: activity.actor}});

    if (follower.local)
        FollowingLists.update({id: follower.following}, {$inc: {totalItems: 1}, $push: {orderedItems: activity.object}});

    return activity;
};

const processFederatedCreateActivity = function(activity) {
    const post = getObjectFromActivity(activity);

    if (Posts.findOne({id: post.id}))
        throw new Meteor.Error('Post Already Exists', 'Cannot insert post, id already exists: ' + JSON.stringify({prev: Posts.findOne({id: post.id}), post: post}));

    post.local = false;
    Posts.insert(post);

    return activity;
};

const processFederatedDeleteActivity = function(activity) {
    const postID = activity.object;

    deletePost(postID);

    return activity;
};

const processFederatedAcceptActivity = function(activity) {

    let follower;

    switch (typeof activity.object) {
        case 'string':
            follower = Actors.findOne({id: activity.object});
            break;
        case 'object':
            follower = Actors.findOne({id: activity.object.actor});
            break;
    }

    if (!follower)
        throw new Meteor.Error('Actor not found!', 'No actor with the given ID could be found: ' + JSON.stringify(activity.object));

    const followee = Actors.findOne({id: activity.actor});

    let pendingFollow = PendingFollows.findOne({follower: follower.id, followee: followee.id});

    if (!pendingFollow)
        throw new Meteor.Error('No Follow Pending!', 'No pending follow between those actors was found: ' + activity.object + ", " + activity.actor);

    FollowingLists.update({id: follower.following}, {$inc: {totalItems: 1}, $push: {orderedItems: activity.actor}});

    if (followee.local)
        FollowerLists.update({id: followee.followers}, {$inc: {totalItems: 1}, $push: {orderedItems: activity.object}});

    PendingFollows.remove(pendingFollow);

    return activity;
};

const processFederatedUndoActivity = function(activity) {

    let targetActivity = Activities.findOne({id: activity.object});

    if (targetActivity.type === "Follow"){

        const follower = Actors.findOne({id: targetActivity.actor});

        if (!follower)
            throw new Meteor.Error('Actor not found!', 'No actor with the given ID could be found: ' + targetActivity.object);

        const followee = Actors.findOne({id: targetActivity.object});

        FollowingLists.update({id: follower.following}, {$inc: {totalItems: -1}, $pull: {orderedItems: targetActivity.object}});

        if (followee.local)
            FollowerLists.update({id: followee.followers}, {$inc: {totalItems: -1}, $pull: {orderedItems: targetActivity.actor}});

        PendingFollows.remove({follower: follower.id, followee: followee.id});

        return activity;
    }
};

const processFederatedActivity = function(activity) {

    if (activity.id && Activities.findOne({id: activity.id}))
        return;

    let processFederatedActivityCallback = function(err, result) {
        try {
            checkFederatedActivityPermitted(activity);
        } catch (error) {
            if (['Post Not Present', 'Post Already Exists'].includes(error.error)) {
                const _id = Activities.insert(activity);

                return Activities.findOne({_id: _id});
            } else throw error;
        }

        switch(activity.type) {
            case 'Follow':
                try {
                    activity = processFederatedFollowActivity(activity);
                } catch (error) {
                    throw error;
                }
                break;
            case 'Undo':
                try {
                    activity = processFederatedUndoActivity(activity);
                } catch (error) {
                    throw error;
                }
                break;
            case 'Create':
                try {
                    activity = processFederatedCreateActivity(activity);
                } catch (error) {
                    throw error;
                }
                break;
            case 'Delete':
            try {
                activity = processFederatedDeleteActivity(activity);
            } catch (error) {
                if (error.error === 'Post Not Present') {

                } else throw error;
            }
                break;
            case "Accept":
                activity = processFederatedAcceptActivity(activity);
                break;
        }

        Activities.insert(activity);
    }

    if (activity.actor && !Actors.findOne({id: activity.actor})) { //Is this posts actor already present? If not,
        importActivityJSONFromUrl(activity.actor, processFederatedActivityCallback); //add it, and finish the function as a callback.
    } else {
        processFederatedActivityCallback(); //If it is present, then continue as normal.
    }
};

const importActorFromActivityPubJSON = function(json) {
    if (!Actors.findOne({id: json.id})) { //Is actor already present? If not,
        json.local = false; //mark it as foreign,
        Actors.insert(json); //then add it.

        console.log("Added Actor: " + json.id);
    }
};

const importPostFromActivityPubJSON = function(json) {
    if (!Posts.findOne({id: json.id})) { //Is post already present? If not,
        json.local = false; //mark it as foreign,
        Posts.insert(json); //then add it.
    }
};

importActivityJSONFromUrl = function(url, callback) {
    console.log("Importing from: " + url);
    let json = JSON.parse(HTTP.get(url, {
        headers: {
            Accept: 'application/activity+json; profile="https://www.w3.org/ns/activitystreams#"'
        }
    }).content);

    importFromActivityPubJSON(json);

    if (callback) callback(null, json);

    return json;
};

importFromActivityPubJSON = function(json) {
    if (!json) throw new Meteor.Error('Empty JSON');

    if (!json.type) throw new Meteor.Error('Untyped ActivityPub JSON');

    if (activityPubActorTypes.includes(json.type))
        importActorFromActivityPubJSON(json);

    else if (activityPubContentTypes.includes(json.type))
        importPostFromActivityPubJSON(json);
};

Meteor.methods({
    importActivityJSONFromUrl: importActivityJSONFromUrl,
    importFromActivityPubJSON: importFromActivityPubJSON,
    postActivity: function(object) {
        const user = Meteor.users.findOne({_id: this.userId});

        let result = processClientActivity(user, object);

        return result;
    }
});

const successfulJSON = function(data) {
    response = {
        statusCode: 200
    };

    if (data) {
        response.body = cleanActivityPub(data);
    }

    return response;
};

const failedJSON = function(message) {
    return {
        statusCode: 400,
        body: {status: "fail", message: message}
    };
};

Api = new Restivus({
    apiPath: '/',
    prettyJson: true
});

Api.addRoute('.well-known/host-meta', {}, {
    get: {
        action: function () {
            let response = successfulJSON();
            response.headers = {
                'Access-Control-Allow-Origin': '*',
                'Content-Type': 'application/xml; charset=UTF-8'
            };

            response.body = '<XRD>' +
                            '  <Link rel="lrdd"' +
                            '    type="application/xrd+xml"' +
                            '    template="' +
                            process.env.ROOT_URL + '/webfinger/xrd/{uri}" />' +
                            '</XRD>'

            return response;
        }
    }
});

Api.addRoute('.well-known/host-meta.json', {}, {
    get: {
        action: function () {
            let response = successfulJSON();
            response.headers = {
                'Access-Control-Allow-Origin': '*',
                'Content-Type': 'application/json; charset=UTF-8'
            };

            response.body = {
                links: [
                    {
                        "rel": "lrdd",
                        "type": "application/json",
                        "template": process.env.ROOT_URL + "/.well-known/webfinger?resource={uri}"
                    }
                ]
            }

            return response;
        }
    }
});

Api.addRoute('.well-known/webfinger', {}, {
    get: {
        action: function () {

            let resource = this.queryParams.resource;
            var re = /(acct:@?)(.*)(@.*)/;
            let handle = resource.replace(re, "$2");
            let actor = Actors.findOne({preferredUsername: handle, local: true});

            actor = cleanActivityPub(actor);

            let response = successfulJSON();

            response.headers = {
                'Content-Type': 'application/json; charset=UTF-8'
            };

            response.body = {
                subject: resource,
                aliases: [actor.url, actor.id],
                links: [
                    {
                        "rel":"http://webfinger.net/rel/profile-page",
                        "type":"text/html",
                        "href": actor.url
                    },
                    {
                        "rel":"self",
                        "type":"application/activity+json",
                        "href": actor.id
                    }
                ]
            }

            return response;
        }
    }
});

Api.addRoute('post/:_id', {}, {
    get: {
        action: function () {
            let post = Posts.findOne({_id: this.urlParams._id});
            if (post) {
                let response = successfulJSON(post);
                response.headers = {
                    'Content-Type': 'activity/json; charset=UTF-8'
                };
                return response;
            } else return failedJSON("Unable to get post!");
        }
    }
});

Api.addRoute('activity/:_id', {}, {
    get: {
        action: function () {
            let activity = Activities.findOne({_id: this.urlParams._id});
            if (activity) {
                let response = successfulJSON(activity);
                response.headers = {
                    'Content-Type': 'activity/json; charset=UTF-8'
                };
                return response;
            } else return failedJSON("Unable to get post!");
        }
    }
});

Api.addRoute('actors/:handle', {}, {
    get: {
        action: function () {
            let actor = Actors.findOne({preferredUsername: this.urlParams.handle, local: true});
            if (actor) {
                let response = successfulJSON(actor);
                response.headers = {
                    'Content-Type': 'activity/json; charset=UTF-8'
                };
                return response;
            } else return failedJSON("Unable to get actor!");
        }
    }
});

Api.addRoute('actors/:handle/inbox', {}, {
    post: {
        action: function () {
            let object = this.request.body;
            processFederatedActivity(object);
            return successfulJSON();
        }
    }
});

Api.addRoute('actors/:handle/outbox', {}, {
    get: {
        action: function () {
            var outbox = Outboxes.findOne({id: process.env.ROOT_URL + "actors/" + this.urlParams.handle + "/outbox"});
            if (outbox) {
                let orderedItems = [];
                for (let i = 0; i < outbox.totalItems && i < 10; i++) {
                    orderedItems.push(Activities.findOne({id: outbox.orderedItems[i]}));
                }
                outbox.orderedItems = orderedItems;
                let response = successfulJSON(outbox);
                response.headers = {
                    'Content-Type': 'activity/json; charset=UTF-8'
                };
                return response;
            } else return failedJSON("Unable to get outbox!");
        }
    },
    post: {
        action: function () {
            let object = this.request.body;

            try {
                return successfulJSON(processClientActivity(object));
            } catch (exception) {
                return failedJSON(exception.reason);
            }
        }
    }
});

Api.addRoute('actors/:handle/following', {}, {
    get: {
        action: function () {
            let following = FollowingLists.findOne({id: process.env.ROOT_URL + "actors/" + this.urlParams.handle + "/following"});
            if (following) {
                let response = successfulJSON(following);
                response.headers = {
                    'Content-Type': 'activity/json; charset=UTF-8'
                };
                return response;
            } else return failedJSON("Unable to get following list!");
        }
    }
});

Api.addRoute('actors/:handle/followers', {}, {
    get: {
        action: function () {
            let actor = Actors.findOne({preferredUsername: this.urlParams.handle});
            if (actor) {
                let followers = FollowerLists.findOne({id: actor.followers});
                if (followers) {
                    return successfulJSON(followers);
                }
            }

            return failedJSON("Unable to get followers list!");
        }
    }
});
<|endoftext|>


<commit_before>'use strict';

import uuid from 'uuid';
import redis from 'redis';
import axios from 'axios';
import { List } from 'immutable';

const info = console.info.bind(console, 'INFO: ');
const error = console.error.bind(console, 'ERROR: ');
const channel = 'reddit';

let fetch = (reply) => {
  axios.get('https://www.reddit.com/.json').then((res) => {
    if (res.data && res.data.data) {
      debugger;
      let results =
        List(res.data.data.children)
          .filter((i) => !!i.data)
          .map((i) => {
            let r = i.data;
            return {
              subreddit:    r.subreddit,
              id:           r.id,
              thumbnail:    r.thumbnail,
              score:        r.score,
              permalink:    r.permalink,
              num_comments: r.num_comments,
              ups:          r.ups,
              downs:        r.downs,
              author:       r.author,
            };
          }
        );

      reply(null, results);
    }
  }).catch((error) => {
    reply(error, {});
  });
};

let id = null;
let monitors = List();

let update = (err, data) => {
  monitors.forEach((obj) => {
    obj.fn(err, data);
  });
};

let run = () => {
  if (id) return;

  info('Starting to read data from reddit');

  setInterval(function() {
    fetch((err, list) => {

      if (err) {
        error("we didn't publish", err);
      } else {
        client.publish(channel, JSON.stringify(list.toJS()));
      }

      update(err, list);
    })
  }, 3000);
};

let cancel = () => {
  clearInterval(id);
  id = null;
};

var client = redis.createClient(
    process.env.REDIS_PORT,
    process.env.REDIS_URL);

client.auth(process.env.REDIS_SECRET, (err) => {
  if (err) {
    error(err);
    process.exit(-1);
  } else {
    info('Redis authentication successful');
  }

  run();
});

export default {
  monitor(fn) {
    var id = uuid();
    monitors = monitors.concat({id, fn});
    return id;
  },
  unmonitor(id) {
    monitors = monitors.filter((m) => m.id !== id);
  },
  cancel, run
};
<commit_msg>remove debugger<commit_after>'use strict';

import uuid from 'uuid';
import redis from 'redis';
import axios from 'axios';
import { List } from 'immutable';

const info = console.info.bind(console, 'INFO: ');
const error = console.error.bind(console, 'ERROR: ');
const channel = 'reddit';

let fetch = (reply) => {
  axios.get('https://www.reddit.com/.json').then((res) => {
    if (res.data && res.data.data) {
      let results =
        List(res.data.data.children)
          .filter((i) => !!i.data)
          .map((i) => {
            let r = i.data;
            return {
              subreddit:    r.subreddit,
              id:           r.id,
              thumbnail:    r.thumbnail,
              score:        r.score,
              permalink:    r.permalink,
              num_comments: r.num_comments,
              ups:          r.ups,
              downs:        r.downs,
              author:       r.author,
            };
          }
        );

      reply(null, results);
    }
  }).catch((error) => {
    reply(error, {});
  });
};

let id = null;
let monitors = List();

let update = (err, data) => {
  monitors.forEach((obj) => {
    obj.fn(err, data);
  });
};

let run = () => {
  if (id) return;

  info('Starting to read data from reddit');

  setInterval(function() {
    fetch((err, list) => {

      if (err) {
        error("we didn't publish", err);
      } else {
        client.publish(channel, JSON.stringify(list.toJS()));
      }

      update(err, list);
    })
  }, 3000);
};

let cancel = () => {
  clearInterval(id);
  id = null;
};

var client = redis.createClient(
    process.env.REDIS_PORT,
    process.env.REDIS_URL);

client.auth(process.env.REDIS_SECRET, (err) => {
  if (err) {
    error(err);
    process.exit(-1);
  } else {
    info('Redis authentication successful');
  }

  run();
});

export default {
  monitor(fn) {
    var id = uuid();
    monitors = monitors.concat({id, fn});
    return id;
  },
  unmonitor(id) {
    monitors = monitors.filter((m) => m.id !== id);
  },
  cancel, run
};
<|endoftext|>


<commit_before>"use strict";

var util = require('util');
var SerialPort = require("serialport").SerialPort;

var options = {
  baudRate: 115200,
  dataBits: 8,
  parity: 'none',
  stopBits: 1,
  flowControl: false,
};

function Arduinode(path, callback){
  var self = this;
  self.sp = new SerialPort(path, options);
  self.buf = [];
  self.callback = [];
  self.error = null;

  self.once("ready", callback);

  // arduinoは\r\nを返してくる.
  self.sp.on("data", function(data){
    for(var i = 0; i < data.length; i++){
      if( data[i] != 10){
        // \rは無視する.
        if(data[i] != 13){
          self.buf.push(data[i]);
        }
      }else{
        var received = new Buffer(self.buf).toString();
        if(received === "READY"){
          self.emit("ready");
        }else{
          var cb = self.callback.shift();
          if(typeof(cb) == "function"){
            var result = JSON.parse(received);
            if(result.msg == "NG"){
              var error = new Error();
              error.name = "Command error.";
              error.message = result.error;
              cb(error, result);
            }else{
              cb(self.error, JSON.parse(received));
            }
            self.errro = null;
          }
        }
        self.buf = [];
      }
    }
  });
}

util.inherits(Arduinode, SerialPort);

/*
 *
 * send command.
 *
 * Low level API
 */
Arduinode.prototype.send = function(cmd, callback) {
  var self = this;
  if(cmd.length < 100){
    self.callback.push(callback);
    self._write(cmd,null);
  }else{
    // Arduinoのバッファがあふれるようなサイズのコマンドは
    // 送信せずにエラーを発生させる。
    // Arduinoの受信バッファサイズは128byteであるが、100に制限する.
    var error = new Error();
    error.name = "Command error.";
    error.message = "Command is too long.";
    callback(error, null);
  }
}

/*
 * High level API
 */

/***

# Analogポートに関する操作

*/

/***

## AD値読み込み

指定したポートのAD値を読み込む。

### リクエスト(node.js -> Arduino)

```txt
ai/read/[port]
```

[port]
:ポート番号

### レスポンス(node.js <- Arduino)

```js
{"msg":"OK","port":[port],"val":[val]}
```

[port]
:リクエストで指定した[port]番号

[val]
:読み取られたAD値

### Sample code

```js
// High level API
arduinode.analogRead(0, function(err, reuslt){
if(err) throw err;
console.log(result);
});

// Low level API
arduinode.send("ai/read/0", function(err, result){
if(err) throw err;
console.log(result);
});
```

### 対応するArduinoの操作

```c
analogRead([port]);
```

*/
Arduinode.prototype.analogRead = function(port, callback) {
  var self = this;
  self.send("ai/read/" + port, callback);
}

/***

## アナログ値(PWM)出力

指定したポートからアナログ値を出力します。

### リクエスト(node.js -> Arduino)

```txt
ao/write/[port]?val=[val]

```
[port]
:ポート番号

[val]
:出力値(0 - 255)

### レスポンス(node.js <- Arduino)

```js
{"msg":"OK","port":[port],"val":[val]}
```

### Sample code

```js
// Low level API
arduinode.send("ao/write/0?val=25", function(err, result){
    if(err) throw err;
    console.log(result);
});

// High level API
arduinode.analogWrite(1, 100, function(err, result){
    if(err) throw err;
    console.log(result);
});
```

### 対応するArduinoの操作

```c
analogWrite([port], [val]);
```

*/
Arduinode.prototype.analogWrite = function(port, val, callback) {
  var self = this;
  self.send("ao/write/" + port + "?val=" + val, callback);
}

/***

## Analog入力基準電圧変更

AD値読み込みに使用される基準電圧源を変更します。

### リクエスト(node.js -> Arduino)

```txt
ai/ref?type=[type]
```

[type]
:DEFAULT、INTERNAL、EXTERNALのいずれか

DEFAULT
:電源電圧5Vが基準電圧になります

INTERNAL
:内部基準電圧が基準電圧になります。通常1.1Vです

EXTERNAL
:AREFピンに供給される電圧が基準電圧になります。

### レスポンス(node.js <- Arduino)

```js
{"msg":"OK","type":[type]}
```

### Sample code

```js
// Low level API
arduinode.send("ai/ref?type=INTERNAL", function(err, result){
    if(err) throw err;
    console.log(result);
);

// High level API
arduinode.analogReference("INTERNAL", function(err, result){
    if(err) throw err;
    console.log(result);
});
```

### 対応するArduinoの操作

```
analogReference([type]);
```

*/
Arduinode.prototype.analogReference = function(type, callback) {
  var self = this;
  self.send("ai/ref?type" + type, callback);
}

/***

# Digitalポートに関する操作

*/

/***

## ポート値読み込み

指定したポートの値(0 or 1)を読み込みます。

### リクエスト(node.js -> Arduino)

```txt
di/read/{port}
```

[port]
:ポート番号

### レスポンス(node.js <- Arduino)

```js
{"msg":"OK","port":[port],"val":[val]}
```

[port]
:リクエストで指定したポート番号

[val]
:読み込まれたポートの値(0 or 1)

### Sample code

```js
// Low level API
arduinode.send("di/read/0", function(err, result){
    if(err) throw err;
    console.log(result);
);

// High level API
arduinode.digitalRead(0, function(err, result){
    if(err) throw err;
    console.log(result);
});
```

### 対応するArduinoの操作

```c
digitalRead([port]);
```

*/
Arduinode.prototype.digitalRead = function(port, callback) {
  var self = this;
  self.send("di/read/" + port, callback);
}

/***

## ポート出力

指定したポートに値を書き込みます。

### リクエスト(node.js -> Arduino)

```txt
do/write/[port]?val=[val]
```

[port]
:ポート番号

[val]
:出力する値。0, 1, LOW, HIGHのいずれか

### レスポンス(node.js <- Arduino)

```js
{"msg":"OK","port":[port],"val":[val]}
```

[port]
:リクエストで指定したポート番号

[val]
:リクエストで指定した値(0 or 1)

### Sample code

```js
// Low level API
arduinode.send("do/write/0?val=1", function(err, result){
    if(err) throw err;
    console.log(result);
);

// High level API
arduinode.digitalWrite(0, 0,function(err, result){
    if(err) throw err;
    console.log(result);
});
```

### 対応するArduinoの操作

```c
digitalWrite([port], [val]);
```

*/
Arduinode.prototype.digitalWrite = function(port, val, callback) {
  var self = this;
  self.send("do/write/" + port + "?val=" + val, callback);
}

/***

## ピンモード変更

指定したポートのピンモードを変更します。

### リクエスト(node.js -> Arduino)

```txt
d/mode/[port]?type=[type]
```

[port]
:ポート番号

[type]
:INPUT、OUTPUT、INPUT_PULLUPのいずれか

INPUT
:入力ポートに設定する。内蔵プルアップ無効

INPUT_PULLUP
:入力ポートに設定する。内蔵プルアップ有効

OUTPUT
:出力ポートに設定する

### レスポンス(node.js <- Arduino)

```js
{"msg":"OK","type":[type]}
```

### Sample code

```js
// Low level API
arduinode.send("d/mode/0?type=INPUT", function(err, result){
    if(err) throw err;
    console.log(result);
);

// High level API
arduinode.pinMode(0, "INPUT", function(err, result){
    if(err) throw err;
    console.log(result);
});
```

### 対応するArduinoの操作

```c
pinMode([port], [type);
```

*/
Arduinode.prototype.pinMode = function(port, type, callback) {
  var self = this;
  self.send("d/mode/" + port + "?type=" + type, callback);
}

/***

# Arduinoそのものに関する操作

*/

/***

## シリアル切断

Arduinoを強制的にリセットすることでシリアルポート接続を切断する。

このAPIを使って切断してから出ないとnode.jsを終了できない。

### リクエスト(node.js -> Arduino)

```txt
system/reset
```

### レスポンス(node.js <- Arduino)

無し

### Sample code

```js
arduinode.close(function(){
    console.log("closed");
});
```

### 対応するArduinoの操作

無し.

Arduino内でスタックオーバーフローを発生させることで強制的にリセットを行なっている。

ArduinoはDTR信号を制御することでリセット可能だが、node-serialportでDTR信号を制御する方法が不明なので、現段階ではこの方法を取る。

*/
Arduinode.prototype.close = function(callback) {
  var self = this;
  var sendCmd = "system/reset\n";
  self._write("system/reset", function(){
    self.sp.close();
    callback();
  });
}

/*
 * 非公開
*/
Arduinode.prototype._write = function(cmd, callback){
  var self = this;
  var sendCmd = cmd.replace(/\n$/, "") + "\n";
  self.sp.write(sendCmd, function(err, writeBytes){
    if(err) throw err;

    if(writeBytes != sendCmd.length){
      var error = new Error();
      error.name = "Send error.";
      error.message = "Write bytes mismatch."
    throw error;
    }
    if(callback){
      callback();
    }
  });
}

module.exports.Arduinode = Arduinode;

/*
 * NOTE:
 *
 *
 * Arduinoのシリアルバッファが大きくないので、
 * 以下の様なコードを書くと直ぐにバッファが溢れて
 * 正常にコマンドが送信できなくなる.
 *
 *  arduinode.on("open", function(){
 *    for(var i = 0; i < 10; i++){
 *      arduinode.send("ai/read/" + i, function(err, resp){
 *        if(err) throw err;
 *        console.log(resp);
 *      });
 *    }
 *  });
 *
 */
<commit_msg>node側も実装.<commit_after>"use strict";

var util = require('util');
var SerialPort = require("serialport").SerialPort;

var options = {
  baudRate: 115200,
  dataBits: 8,
  parity: 'none',
  stopBits: 1,
  flowControl: false,
};

function Arduinode(path, callback){
  var self = this;
  self.sp = new SerialPort(path, options);
  self.buf = [];
  self.callback = [];
  self.error = null;

  self.once("ready", callback);

  // arduinoは\r\nを返してくる.
  self.sp.on("data", function(data){
    for(var i = 0; i < data.length; i++){
      if( data[i] != 10){
        // \rは無視する.
        if(data[i] != 13){
          self.buf.push(data[i]);
        }
      }else{
        var received = new Buffer(self.buf).toString();
        if(received === "READY"){
          self.emit("ready");
        }else{
          var result = JSON.parse(received);
          // eventってデータが含まれてたらemit
          if(result.event){
            self.emit("event", result);
          }else{
            var cb = self.callback.shift();
            if(typeof(cb) == "function"){
              if(result.msg == "NG"){
                var error = new Error();
                error.name = "Command error.";
                error.message = result.error;
                cb(error, result);
              }else{
                cb(self.error, JSON.parse(received));
              }
              self.errro = null;
            }
          }
        }
        self.buf = [];
      }
    }
  });
}

util.inherits(Arduinode, SerialPort);

/*
 *
 * send command.
 *
 * Low level API
 */
Arduinode.prototype.send = function(cmd, callback) {
  var self = this;
  if(cmd.length < 100){
    self.callback.push(callback);
    self._write(cmd,null);
  }else{
    // Arduinoのバッファがあふれるようなサイズのコマンドは
    // 送信せずにエラーを発生させる。
    // Arduinoの受信バッファサイズは128byteであるが、100に制限する.
    var error = new Error();
    error.name = "Command error.";
    error.message = "Command is too long.";
    callback(error, null);
  }
}

/*
 * High level API
 */

/***

# Analogポートに関する操作

*/

/***

## AD値読み込み

指定したポートのAD値を読み込む。

### リクエスト(node.js -> Arduino)

```txt
ai/read/[port]
```

[port]
:ポート番号

### レスポンス(node.js <- Arduino)

```js
{"msg":"OK","port":[port],"val":[val]}
```

[port]
:リクエストで指定した[port]番号

[val]
:読み取られたAD値

### Sample code

```js
// High level API
arduinode.analogRead(0, function(err, reuslt){
if(err) throw err;
console.log(result);
});

// Low level API
arduinode.send("ai/read/0", function(err, result){
if(err) throw err;
console.log(result);
});
```

### 対応するArduinoの操作

```c
analogRead([port]);
```

*/
Arduinode.prototype.analogRead = function(port, callback) {
  var self = this;
  self.send("ai/read/" + port, callback);
}

/***

## アナログ値(PWM)出力

指定したポートからアナログ値を出力します。

### リクエスト(node.js -> Arduino)

```txt
ao/write/[port]?val=[val]

```
[port]
:ポート番号

[val]
:出力値(0 - 255)

### レスポンス(node.js <- Arduino)

```js
{"msg":"OK","port":[port],"val":[val]}
```

### Sample code

```js
// Low level API
arduinode.send("ao/write/0?val=25", function(err, result){
    if(err) throw err;
    console.log(result);
});

// High level API
arduinode.analogWrite(1, 100, function(err, result){
    if(err) throw err;
    console.log(result);
});
```

### 対応するArduinoの操作

```c
analogWrite([port], [val]);
```

*/
Arduinode.prototype.analogWrite = function(port, val, callback) {
  var self = this;
  self.send("ao/write/" + port + "?val=" + val, callback);
}

/***

## Analog入力基準電圧変更

AD値読み込みに使用される基準電圧源を変更します。

### リクエスト(node.js -> Arduino)

```txt
ai/ref?type=[type]
```

[type]
:DEFAULT、INTERNAL、EXTERNALのいずれか

DEFAULT
:電源電圧5Vが基準電圧になります

INTERNAL
:内部基準電圧が基準電圧になります。通常1.1Vです

EXTERNAL
:AREFピンに供給される電圧が基準電圧になります。

### レスポンス(node.js <- Arduino)

```js
{"msg":"OK","type":[type]}
```

### Sample code

```js
// Low level API
arduinode.send("ai/ref?type=INTERNAL", function(err, result){
    if(err) throw err;
    console.log(result);
);

// High level API
arduinode.analogReference("INTERNAL", function(err, result){
    if(err) throw err;
    console.log(result);
});
```

### 対応するArduinoの操作

```
analogReference([type]);
```

*/
Arduinode.prototype.analogReference = function(type, callback) {
  var self = this;
  self.send("ai/ref?type" + type, callback);
}

/***

# Digitalポートに関する操作

*/

/***

## ポート値読み込み

指定したポートの値(0 or 1)を読み込みます。

### リクエスト(node.js -> Arduino)

```txt
di/read/{port}
```

[port]
:ポート番号

### レスポンス(node.js <- Arduino)

```js
{"msg":"OK","port":[port],"val":[val]}
```

[port]
:リクエストで指定したポート番号

[val]
:読み込まれたポートの値(0 or 1)

### Sample code

```js
// Low level API
arduinode.send("di/read/0", function(err, result){
    if(err) throw err;
    console.log(result);
);

// High level API
arduinode.digitalRead(0, function(err, result){
    if(err) throw err;
    console.log(result);
});
```

### 対応するArduinoの操作

```c
digitalRead([port]);
```

*/
Arduinode.prototype.digitalRead = function(port, callback) {
  var self = this;
  self.send("di/read/" + port, callback);
}

/***

## ポート出力

指定したポートに値を書き込みます。

### リクエスト(node.js -> Arduino)

```txt
do/write/[port]?val=[val]
```

[port]
:ポート番号

[val]
:出力する値。0, 1, LOW, HIGHのいずれか

### レスポンス(node.js <- Arduino)

```js
{"msg":"OK","port":[port],"val":[val]}
```

[port]
:リクエストで指定したポート番号

[val]
:リクエストで指定した値(0 or 1)

### Sample code

```js
// Low level API
arduinode.send("do/write/0?val=1", function(err, result){
    if(err) throw err;
    console.log(result);
);

// High level API
arduinode.digitalWrite(0, 0,function(err, result){
    if(err) throw err;
    console.log(result);
});
```

### 対応するArduinoの操作

```c
digitalWrite([port], [val]);
```

*/
Arduinode.prototype.digitalWrite = function(port, val, callback) {
  var self = this;
  self.send("do/write/" + port + "?val=" + val, callback);
}

/***

## ピンモード変更

指定したポートのピンモードを変更します。

### リクエスト(node.js -> Arduino)

```txt
d/mode/[port]?type=[type]
```

[port]
:ポート番号

[type]
:INPUT、OUTPUT、INPUT_PULLUPのいずれか

INPUT
:入力ポートに設定する。内蔵プルアップ無効

INPUT_PULLUP
:入力ポートに設定する。内蔵プルアップ有効

OUTPUT
:出力ポートに設定する

### レスポンス(node.js <- Arduino)

```js
{"msg":"OK","type":[type]}
```

### Sample code

```js
// Low level API
arduinode.send("d/mode/0?type=INPUT", function(err, result){
    if(err) throw err;
    console.log(result);
);

// High level API
arduinode.pinMode(0, "INPUT", function(err, result){
    if(err) throw err;
    console.log(result);
});
```

### 対応するArduinoの操作

```c
pinMode([port], [type);
```

*/
Arduinode.prototype.pinMode = function(port, type, callback) {
  var self = this;
  self.send("d/mode/" + port + "?type=" + type, callback);
}

/***

# Arduinoそのものに関する操作

*/

/***

## シリアル切断

Arduinoを強制的にリセットすることでシリアルポート接続を切断する。

このAPIを使って切断してから出ないとnode.jsを終了できない。

### リクエスト(node.js -> Arduino)

```txt
system/reset
```

### レスポンス(node.js <- Arduino)

無し

### Sample code

```js
arduinode.close(function(){
    console.log("closed");
});
```

### 対応するArduinoの操作

無し.

Arduino内でスタックオーバーフローを発生させることで強制的にリセットを行なっている。

ArduinoはDTR信号を制御することでリセット可能だが、node-serialportでDTR信号を制御する方法が不明なので、現段階ではこの方法を取る。

*/
Arduinode.prototype.close = function(callback) {
  var self = this;
  var sendCmd = "system/reset\n";
  self._write("system/reset", function(){
    self.sp.close();
    callback();
  });
}

/*
 * 非公開
*/
Arduinode.prototype._write = function(cmd, callback){
  var self = this;
  var sendCmd = cmd.replace(/\n$/, "") + "\n";
  self.sp.write(sendCmd, function(err, writeBytes){
    if(err) throw err;

    if(writeBytes != sendCmd.length){
      var error = new Error();
      error.name = "Send error.";
      error.message = "Write bytes mismatch."
    throw error;
    }
    if(callback){
      callback();
    }
  });
}

module.exports.Arduinode = Arduinode;

/*
 * NOTE:
 *
 *
 * Arduinoのシリアルバッファが大きくないので、
 * 以下の様なコードを書くと直ぐにバッファが溢れて
 * 正常にコマンドが送信できなくなる.
 *
 *  arduinode.on("open", function(){
 *    for(var i = 0; i < 10; i++){
 *      arduinode.send("ai/read/" + i, function(err, resp){
 *        if(err) throw err;
 *        console.log(resp);
 *      });
 *    }
 *  });
 *
 */
<|endoftext|>


<commit_before>var url = require('url');
var util = require('./util.js');
var WebSocketServer = require('ws').Server;

function SinkServer(port) {
  var self = this;

  port = port || 8080;

  // Maps namespaces to rooms.
  this.rooms = {};

  // Start Websocket server.
  this.wss = new WebSocketServer({port: port});
  this.wss.on('connection', function(ws) {

    // Parse request url.
    var wsurl = url.parse(ws.upgradeReq.url, true);
    var namespace = wsurl.query.room;
    var room = self.getRoom(namespace);
    room.add(ws);

    // On a message, parse it.
    // [ (TYPE), (PROPERTY NAME), (PROPERTY VALUE), (VERSION #)? ]
    ws.on('message', function(message) {
      console.log('Received: %s', message);

      // Try to parse the message.
      try {
        message = JSON.parse(message);
      } catch (e) {
        return;
      }

      room.update(message, ws);
    });

    ws.on('close', function() {
      room.remove(ws);

      // Reset state.
      if (room.connections.length === 0) {
        delete self.rooms[namespace];
      }
    });

  });
  console.log('SinkServer running on port', port);
};

// Get a room.
SinkServer.prototype.getRoom = function(room) {
  if (!this.rooms[room]) {
    this.rooms[room] = new Room(room);
  }
  return this.rooms[room];
};

function Room(namespace) {
  // TODO: save history?
  this.namespace = namespace;
  this.connections = {};
  this.version = 1;
  this.object = {};
  // IDs for WS.
  this.count = 0;
};

// Adds a connection, sending it the updated object.
Room.prototype.add = function(ws) {
  ws.id = this.count;
  this.count += 1;

  // Send the entire object.
  ws.send(JSON.stringify(['init', this.constructObject(), this.version]));

  // Push socket to connections.
  this.connections[ws.id] = ws;
};

Room.prototype.constructObject = function() {
  return this.construct(this.object);
}

Room.prototype.construct = function(obj) {
  var ret;
  if (typeof(obj) === 'object' && obj) {
    ret = util.isArray(obj) ? [] : {};

    var keys = Object.keys(obj);
    for (var i = 0, ii = keys.length; i < ii; i += 1) {
      var key = keys[i];
      var next_obj = obj[key].value;
      ret[key] = this.construct(next_obj);
    }
  } else {
    ret = obj;
  }

  return ret;
};

// Remove a connection, sending it the updated object.
Room.prototype.remove = function(ws) {
  delete this.connections[ws.id];
};

// Updates server version and sends updates to all clients in room.
// TODO: account for multiple levels of properties.
Room.prototype.update = function(updates, from) {
  var version = updates[2];
  var type = updates[0];

  //console.log(JSON.stringify(updates));
  // Sync with self.
  updates = this.sync(updates, from)
  this.version += 1;
  //console.log(JSON.stringify(updates));

  // Update all clients.
  if (updates.length > 0) {
    var ids = Object.keys(this.connections);
    for (var i = 0, ii = ids.length; i < ii; i += 1) {
      var connection = this.connections[ids[i]];
      if (connection.id !== from.id) {
        connection.send(JSON.stringify([type, updates, this.version]));
      }
    }

    // Notify original client of success.
    from.send(JSON.stringify(['success', this.version]));
  }
};

Room.prototype.sync = function(updates, from) {
  var collisions = [];
  var version = updates[2];
  var type = updates[0];
  updates = updates[1];

  // Confirmed updates.
  var confirmed = [];

  // Update all properties.
  for (var i = 0, ii = updates.length; i < ii; i += 1) {
    var update = updates[i];

    // Check if collision.
    var previous;
    if (version && (previous = this.getLastVersion(update)) && version < previous.version) {
      collisions.push([update[0], this.construct(previous)]);

    } else {
      if (type === 'update') {
        this.updateObject(update);
      } else {
        this.deleteProperty(update);
      }
      confirmed.push(update);
    }
  }

  if (collisions.length > 0) {
    from.send(JSON.stringify(['collision', collisions, this.version]));
  }

  return confirmed;
};

// TODO: handle arrays.
Room.prototype.getLastVersion = function(update) {
  update = update[0].split(".");
  var obj;
  while (update.length && (obj = this.object[update.shift()])) {
    // Lalala.
  }
  return obj;
};

// Deletes a property.
Room.prototype.deleteProperty = function(update) {
  // TODO: error checks. here we assume everything can be found...
  update = update.split(".");
  var obj = this.object;

  while (update.length) {
    var key = update.shift();
    if (!update.length) {
      delete obj[key];
    } else {
      obj = obj[key].value;
    }
  }
}

Room.prototype.updateObject = function(update) {
  // deepcopy hack
  var value = JSON.parse(JSON.stringify(update[1]));
  update = update[0].split(".");
  var obj = this.object;

  while (update.length) {
    var key = update.shift();
    if (update.length && (!obj[key] || (typeof(obj[key].value) !== 'object' && typeof(obj[key] !== 'object')))) {
      obj[key] = { version: this.version, value: {} };
    } else if (!update.length) {
      obj[key] = { version: this.version, value: value };
    }
    obj = obj[key].value;
  }

  function toSpecialObj(_obj) {
    if (typeof(_obj) === 'object' && _obj) {
      var keys = Object.keys(_obj);
      for (var i = 0, ii = keys.length; i < ii; i += 1) {

        var key = keys[i];
        _obj[key] = { version: this.version, value: _obj[key] };
        toSpecialObj(_obj[key].value);
      }
    }
  }

  // Takes care of nested objects.
  toSpecialObj(obj);
};

exports.SinkServer = SinkServer;
<commit_msg>fix null error<commit_after>var url = require('url');
var util = require('./util.js');
var WebSocketServer = require('ws').Server;

function SinkServer(port) {
  var self = this;

  port = port || 8080;

  // Maps namespaces to rooms.
  this.rooms = {};

  // Start Websocket server.
  this.wss = new WebSocketServer({port: port});
  this.wss.on('connection', function(ws) {

    // Parse request url.
    var wsurl = url.parse(ws.upgradeReq.url, true);
    var namespace = wsurl.query.room;
    var room = self.getRoom(namespace);
    room.add(ws);

    // On a message, parse it.
    // [ (TYPE), (PROPERTY NAME), (PROPERTY VALUE), (VERSION #)? ]
    ws.on('message', function(message) {
      console.log('Received: %s', message);

      // Try to parse the message.
      try {
        message = JSON.parse(message);
      } catch (e) {
        return;
      }

      room.update(message, ws);
    });

    ws.on('close', function() {
      room.remove(ws);

      // Reset state.
      if (room.connections.length === 0) {
        delete self.rooms[namespace];
      }
    });

  });
  console.log('SinkServer running on port', port);
};

// Get a room.
SinkServer.prototype.getRoom = function(room) {
  if (!this.rooms[room]) {
    this.rooms[room] = new Room(room);
  }
  return this.rooms[room];
};

function Room(namespace) {
  // TODO: save history?
  this.namespace = namespace;
  this.connections = {};
  this.version = 1;
  this.object = {};
  // IDs for WS.
  this.count = 0;
};

// Adds a connection, sending it the updated object.
Room.prototype.add = function(ws) {
  ws.id = this.count;
  this.count += 1;

  // Send the entire object.
  ws.send(JSON.stringify(['init', this.constructObject(), this.version]));

  // Push socket to connections.
  this.connections[ws.id] = ws;
};

Room.prototype.constructObject = function() {
  return this.construct(this.object);
}

Room.prototype.construct = function(obj) {
  var ret;
  if (typeof(obj) === 'object' && obj) {
    ret = util.isArray(obj) ? [] : {};

    var keys = Object.keys(obj);
    for (var i = 0, ii = keys.length; i < ii; i += 1) {
      var key = keys[i];
      if (obj[key]) {
        var next_obj = obj[key].value;
        ret[key] = this.construct(next_obj);
      }
    }
  } else {
    ret = obj;
  }

  return ret;
};

// Remove a connection, sending it the updated object.
Room.prototype.remove = function(ws) {
  delete this.connections[ws.id];
};

// Updates server version and sends updates to all clients in room.
// TODO: account for multiple levels of properties.
Room.prototype.update = function(updates, from) {
  var version = updates[2];
  var type = updates[0];

  //console.log(JSON.stringify(updates));
  // Sync with self.
  updates = this.sync(updates, from)
  this.version += 1;
  //console.log(JSON.stringify(updates));

  // Update all clients.
  if (updates.length > 0) {
    var ids = Object.keys(this.connections);
    for (var i = 0, ii = ids.length; i < ii; i += 1) {
      var connection = this.connections[ids[i]];
      if (connection.id !== from.id) {
        connection.send(JSON.stringify([type, updates, this.version]));
      }
    }

    // Notify original client of success.
    from.send(JSON.stringify(['success', this.version]));
  }
};

Room.prototype.sync = function(updates, from) {
  var collisions = [];
  var version = updates[2];
  var type = updates[0];
  updates = updates[1];

  // Confirmed updates.
  var confirmed = [];

  // Update all properties.
  for (var i = 0, ii = updates.length; i < ii; i += 1) {
    var update = updates[i];

    // Check if collision.
    var previous;
    if (version && (previous = this.getLastVersion(update)) && version < previous.version) {
      collisions.push([update[0], this.construct(previous)]);

    } else {
      if (type === 'update') {
        this.updateObject(update);
      } else {
        this.deleteProperty(update);
      }
      confirmed.push(update);
    }
  }

  if (collisions.length > 0) {
    from.send(JSON.stringify(['collision', collisions, this.version]));
  }

  return confirmed;
};

// TODO: handle arrays.
Room.prototype.getLastVersion = function(update) {
  update = update[0].split(".");
  var obj;
  while (update.length && (obj = this.object[update.shift()])) {
    // Lalala.
  }
  return obj;
};

// Deletes a property.
Room.prototype.deleteProperty = function(update) {
  // TODO: error checks. here we assume everything can be found...
  update = update.split(".");
  var obj = this.object;

  while (update.length) {
    var key = update.shift();
    if (!update.length) {
      delete obj[key];
    } else {
      obj = obj[key].value;
    }
  }
}

Room.prototype.updateObject = function(update) {
  // deepcopy hack
  var value = JSON.parse(JSON.stringify(update[1]));
  update = update[0].split(".");
  var obj = this.object;

  while (update.length) {
    var key = update.shift();
    if (update.length && (!obj[key] || (typeof(obj[key].value) !== 'object' && typeof(obj[key] !== 'object')))) {
      obj[key] = { version: this.version, value: {} };
    } else if (!update.length) {
      obj[key] = { version: this.version, value: value };
    }
    obj = obj[key].value;
  }

  function toSpecialObj(_obj) {
    if (typeof(_obj) === 'object' && _obj) {
      var keys = Object.keys(_obj);
      for (var i = 0, ii = keys.length; i < ii; i += 1) {

        var key = keys[i];
        _obj[key] = { version: this.version, value: _obj[key] };
        toSpecialObj(_obj[key].value);
      }
    }
  }

  // Takes care of nested objects.
  toSpecialObj(obj);
};

exports.SinkServer = SinkServer;
<|endoftext|>


<commit_before>var $ = require('jquery-cdb-v3');
var cdb = require('cartodb.js-v3');
cdb.admin = require('cdb.admin');
var LocalStorage = require('../common/local_storage');
var HeaderView = require('../common/views/dashboard_header_view_static');
var SupportView = require('../common/support_view_static');
var FooterView = require('../common/footer_view_static');
var MamufasImportView = require('../common/mamufas_import/mamufas_import_view');
var BackgroundPollingView = require('../common/background_polling/background_polling_view');
var DashboardBackgroundPollingModel = require('./background_polling_model');
var ContentControllerView = require('./content_controller_view_static');
var HeaderViewModel = require('./header_view_model');
var VendorScriptsView = require('../common/vendor_scripts_view');

module.exports = cdb.core.View.extend({
  events: {
    'click': '_onClick'
  },

  initialize: function () {
    this._initModels();
    this._initViews();
    this._initBindings();
  },

  _initBindings: function () {
    this.router.model.bind('change', this._onRouterChange, this);
    this.add_related_model(this.router.model);
  },

  _initModels: function () {
    this.user = this.options.user;
    this.router = this.options.router;
    this.localStorage = new LocalStorage();

    // Update order and category attribute to router model
    this.router.model.set('order', this.localStorage.get('dashboard.order'), { silent: true });
    this.router.model.set('category', this.localStorage.get('dashboard.category'), { silent: true });
  },

  _onRouterChange: function (model, value) {
    this._fetchCollection(model, value);

    // Only create a visualization from an import if user is in maps section
    this._backgroundPollingView.createVis = this.router.model.isMaps();
  },

  _fetchCollection: function (model, value) {
    var params = this.router.model.attributes;

    // Get order from localStorage if it is not defined or
    // come from other type (tables or visualizations)
    var order = this.localStorage.get('dashboard.order') || 'updated_at';
    // Maps doesn't have size order, so if that order is set
    // in maps section we will show with 'updated_at' order
    if (params.content_type === 'maps' && order === 'size') {
      order = 'updated_at';
    }

    var types = params.content_type === 'datasets' ? 'table' : 'derived';

    // Requesting data library items?
    if (params.library) {
      types = 'remote';
    }

    // Supporting search in data library and user datasets at the same time
    if ((params.q || params.tag) && params.content_type === 'datasets') {
      types = 'table,remote';
    }

    // TODO: review, should collection params really be set here?
    this.collection.options.set({
      q: params.q,
      page: params.page || 1,
      tags: params.tag,
      per_page: this.collection['_' + (params.content_type === 'datasets' ? 'TABLES' : 'ITEMS') + '_PER_PAGE'],
      shared: params.shared,
      locked: params.liked ? '' : params.locked, // If not locked liked items are not rendered
      only_liked: params.liked,
      order: order,
      types: types,
      type: '',
      deepInsights: !!params.deepInsights
    });

    this.collection.fetch();
  },

  _initViews: function () {
    var backgroundPollingModel = new DashboardBackgroundPollingModel({
      showGeocodingDatasetURLButton: true,
      geocodingsPolling: true,
      importsPolling: true
    }, {
      user: this.user
    });

    this._backgroundPollingView = new BackgroundPollingView({
      model: backgroundPollingModel,
      // Only create a visualization from an import if user is in maps section
      createVis: this.router.model.isMaps(),
      user: this.user
    });
    backgroundPollingModel.bind('importCompleted', function () {
      this.collection.fetch();
      this.user.fetch();
    }, this);
    this.$el.append(this._backgroundPollingView.render().el);

    this.addView(this._backgroundPollingView);

    var mamufasView = new MamufasImportView({
      el: this.$el,
      user: this.user
    });

    cdb.god.bind('dialogOpened', function () {
      mamufasView.disable();
      backgroundPollingModel.stopPollings();
    }, this);
    cdb.god.bind('dialogClosed', function () {
      mamufasView.enable();
      backgroundPollingModel.startPollings();
    }, this);

    mamufasView.render();
    mamufasView.enable();

    var headerView = new HeaderView({
      model: this.user,
      viewModel: new HeaderViewModel(this.router),
      router: this.router,
      collection: this.collection,
      localStorage: this.localStorage
    });
    this.$('#app').prepend(headerView.render().el);

    this.controllerView = new ContentControllerView({
      // Pass the whole element for only calculating
      // the height is not 'fair'
      headerHeight: this.$('#header').height(),
      user: this.user,
      router: this.router,
      collection: this.collection,
      localStorage: this.localStorage
    });
    this.$('#app').append(this.controllerView.render().el);

    if (!cdb.config.get('cartodb_com_hosted')) {
      var supportView = new SupportView({
        user: this.user
      });
      this.$('#app').append(supportView.render().el);
    }

    var footerView = new FooterView();
    this.$('#app').append(footerView.render().el);

    var vendorScriptsView = new VendorScriptsView({
      config: this.options.config,
      assetsVersion: this.options.assetsVersion,
      user: this.user
    });
    this.$el.append(vendorScriptsView.render().el);
  },

  _onClick: function (event) {
    // Clicks outside of any dialog 'body' will fire a closeDialogs event
    if (this._isEventTriggeredOutsideOf(event, '.Dialog')) {
      cdb.god.trigger('closeDialogs');

      // If click outside the filters view should also unselect any selected items
      if (this._isEventTriggeredOutsideOf(event, '.js-skip-unselect-all')) {
        cdb.god.trigger('unselectAllItems');
      }
    }
  },

  _isEventTriggeredOutsideOf: function (event, selector) {
    return $(event.target).closest(selector).length === 0;
  }
});
<commit_msg>restore dashboard main view<commit_after>var $ = require('jquery-cdb-v3');
var cdb = require('cartodb.js-v3');
cdb.admin = require('cdb.admin');
var LocalStorage = require('../common/local_storage');
var HeaderView = require('../common/views/dashboard_header_view');
var SupportView = require('../common/support_view');
var MamufasImportView = require('../common/mamufas_import/mamufas_import_view');
var BackgroundPollingView = require('../common/background_polling/background_polling_view');
var DashboardBackgroundPollingModel = require('./background_polling_model');
var ContentControllerView = require('./content_controller_view');
var HeaderViewModel = require('./header_view_model');

module.exports = cdb.core.View.extend({

  events: {
    'click': '_onClick'
  },

  initialize: function() {
    this._initModels();
    this._initViews();
    this._initBindings();
  },

  _initBindings: function() {
    this.router.model.bind('change', this._onRouterChange, this);
    this.add_related_model(this.router.model);
  },

  _initModels: function() {
    this.user = this.options.user;
    this.router = this.options.router;
    this.localStorage = new LocalStorage();

    // Update order and category attribute to router model
    this.router.model.set('order',    this.localStorage.get('dashboard.order'), { silent: true });
    this.router.model.set('category', this.localStorage.get('dashboard.category'), { silent: true });
  },

  _onRouterChange: function(m, changes) {
    this._fetchCollection(m, changes);

    // Only create a visualization from an import if user is in maps section
    this._backgroundPollingView.createVis = this.router.model.isMaps();
  },

  _fetchCollection: function(m, changes) {
    var params = this.router.model.attributes;

    // Get order from localStorage if it is not defined or
    // come from other type (tables or visualizations)
    var order = this.localStorage.get("dashboard.order") || 'updated_at';
    // Maps doesn't have size order, so if that order is set
    // in maps section we will show with 'updated_at' order
    if (params.content_type === "maps" && order === "size") {
      order = 'updated_at'
    }

    var types = params.content_type === "datasets" ? 'table' : 'derived';

    // Requesting data library items?
    if (params.library) {
      types = 'remote';
    }

    // Supporting search in data library and user datasets at the same time
    if ((params.q || params.tag) && params.content_type === "datasets") {
      types = 'table,remote';
    }

    // TODO: review, should collection params really be set here?
    this.collection.options.set({
      q:              params.q,
      page:           params.page || 1,
      tags:           params.tag,
      per_page:       this.collection[ "_" + ( params.content_type === "datasets" ? 'TABLES' : 'ITEMS') + '_PER_PAGE'],
      shared:         params.shared,
      locked:         params.liked ? '' : params.locked, // If not locked liked items are not rendered
      only_liked:     params.liked,
      order:          order,
      types:          types,
      type:           '',
      deepInsights:   !!params.deepInsights
    });

    this.collection.fetch();
  },

  _initViews: function() {
    var backgroundPollingModel = new DashboardBackgroundPollingModel({
      showGeocodingDatasetURLButton: true,
      geocodingsPolling: true,
      importsPolling: true
    }, {
      user: this.user
    });

    this._backgroundPollingView = new BackgroundPollingView({
      model: backgroundPollingModel,
      // Only create a visualization from an import if user is in maps section
      createVis: this.router.model.isMaps(),
      user: this.user
    });
    backgroundPollingModel.bind('importCompleted', function() {
      this.collection.fetch();
      this.user.fetch();
    }, this);
    this.$el.append(this._backgroundPollingView.render().el);

    this.addView(this._backgroundPollingView);

    var mamufasView = new MamufasImportView({
      el: this.$el,
      user: this.user
    });

    cdb.god.bind('dialogOpened', function() {
      mamufasView.disable();
      backgroundPollingModel.stopPollings();
    }, this);
    cdb.god.bind('dialogClosed', function() {
      mamufasView.enable();
      backgroundPollingModel.startPollings();
    }, this);

    mamufasView.render();
    mamufasView.enable();

    var headerView = new HeaderView({
      el:             this.$('#header'), //pre-rendered in DOM by Rails app
      model:          this.user,
      viewModel:      new HeaderViewModel(this.router),
      router:         this.router,
      collection:     this.collection,
      localStorage:   this.localStorage
    });
    headerView.render();

    this.controllerView = new ContentControllerView({
      el:           this.$('#content-controller'),
      // Pass the whole element for only calculating
      // the height is not "fair"
      headerHeight: this.$('#header').height(),
      user:         this.user,
      router:       this.router,
      collection:   this.collection,
      localStorage: this.localStorage
    });

    this.controllerView.render();

    var supportView = new SupportView({
      el: this.$('#support-banner'),
      user: this.user
    });

    supportView.render();
  },

  _onClick: function(e) {
    // Clicks outside of any dialog "body" will fire a closeDialogs event
    if (this._isEventTriggeredOutsideOf(e, '.Dialog')) {
      cdb.god.trigger('closeDialogs');

      // If click outside the filters view should also unselect any selected items
      if (this._isEventTriggeredOutsideOf(e, '.js-skip-unselect-all')) {
        cdb.god.trigger('unselectAllItems');
      }
    }
  },

  _isEventTriggeredOutsideOf: function(ev, selector) {
    return $(ev.target).closest(selector).length === 0;
  }

});
<|endoftext|>


<commit_before>var env = process.env.NODE_ENV || 'development';
var host = require('os').hostname();

exports = module.exports = require('express');

exports['sanitizeHost'] = function sanitizeHost(app) {
    var hosts = app.config.host;
    if (hosts) {
        hosts.forEach(function(host, i) {
            if (typeof host === 'string') {
                hosts[i] = new RegExp('^' + hosts[i].replace(/\./g, '\\.').replace(/\*/g, '[a-z0-9_-]+') + '(:\\d+)?$', 'i');
                // Make sure we get the original host names when stringifying the host name matcher.
                hosts[i].toJSON = function() { return host; };
            }
        });
    }

    return function(req, res, next) {
        if (!req.headers.host) {
            return next();
        } else if (!hosts.length) {
            req.headers.host = host;
            return next();
        } else {
            for (var i = 0; i < hosts.length; i++) {
                if (hosts[i].test(req.headers.host)) {
                    return next();
                }
            }
        }

        res.send(400);
    };
};

exports['csrf'] = function csrf() {
    return function(req, res, next) {
        if (req.method === 'GET') {
            next();
        } else if (req.body && req.cookies['bones.token'] && req.body['bones.token'] === req.cookies['bones.token']) {
            delete req.body['bones.token'];
            next();
        } else {
            next(new Error.HTTP(403));
        }
    };
};

exports['fragmentRedirect'] = function fragmentRedirect() {
    return function(req, res, next) {
        // @see https://code.google.com/web/ajaxcrawling/docs/specification.html
        if (req.query._escaped_fragment_ === undefined) {
            next();
        } else {
            // Force the first char of the path to be a slash to prevent
            // foreign redirects.
            var path = '/' + req.query._escaped_fragment_.substr(1);
            res.redirect(path, 301);
        }
    };
};

exports['showError'] = function showError() {
    return function showError(err, req, res, next) {
        if (!err.status) err.status = 500;

        if ((req.headers.accept + '' || '').indexOf('json') >= 0) {
            res.writeHead(err.status, { 'Content-Type': 'application/json' });
            if (env === 'development') {
                res.end(JSON.stringify(err));
            } else {
                res.end(JSON.stringify({ message: err.message }));
            }
        } else {
            res.writeHead(err.status, { 'Content-Type': 'text/plain' });
            if (env === 'development') {
                res.end(err.stack);
            } else {
                res.end(err.message);
            }
        }
    };
};

exports['notFound'] = function notFound() {
    return function notFound(req, res, next) {
        next(new Error.HTTP(404));
    };
};
<commit_msg>convert hostname strings to parameters<commit_after>var env = process.env.NODE_ENV || 'development';
var host = require('os').hostname();

exports = module.exports = require('express');

exports['sanitizeHost'] = function sanitizeHost(app) {
    var hosts = app.config.host;
    if (!Array.isArray(hosts)) {
        hosts = app.config.host = [ hosts ];
    }
    if (hosts) {
        hosts.forEach(function(host, i) {
            if (typeof host === 'string') {
                hosts[i] = new RegExp('^' + hosts[i].replace(/\./g, '\\.').replace(/\*/g, '[a-z0-9_-]+') + '(:\\d+)?$', 'i');
                // Make sure we get the original host names when stringifying the host name matcher.
                hosts[i].toJSON = function() { return host; };
            }
        });
    }

    return function(req, res, next) {
        if (!req.headers.host) {
            return next();
        } else if (!hosts.length) {
            req.headers.host = host;
            return next();
        } else {
            for (var i = 0; i < hosts.length; i++) {
                if (hosts[i].test(req.headers.host)) {
                    return next();
                }
            }
        }

        res.send(400);
    };
};

exports['csrf'] = function csrf() {
    return function(req, res, next) {
        if (req.method === 'GET') {
            next();
        } else if (req.body && req.cookies['bones.token'] && req.body['bones.token'] === req.cookies['bones.token']) {
            delete req.body['bones.token'];
            next();
        } else {
            next(new Error.HTTP(403));
        }
    };
};

exports['fragmentRedirect'] = function fragmentRedirect() {
    return function(req, res, next) {
        // @see https://code.google.com/web/ajaxcrawling/docs/specification.html
        if (req.query._escaped_fragment_ === undefined) {
            next();
        } else {
            // Force the first char of the path to be a slash to prevent
            // foreign redirects.
            var path = '/' + req.query._escaped_fragment_.substr(1);
            res.redirect(path, 301);
        }
    };
};

exports['showError'] = function showError() {
    return function showError(err, req, res, next) {
        if (!err.status) err.status = 500;

        if ((req.headers.accept + '' || '').indexOf('json') >= 0) {
            res.writeHead(err.status, { 'Content-Type': 'application/json' });
            if (env === 'development') {
                res.end(JSON.stringify(err));
            } else {
                res.end(JSON.stringify({ message: err.message }));
            }
        } else {
            res.writeHead(err.status, { 'Content-Type': 'text/plain' });
            if (env === 'development') {
                res.end(err.stack);
            } else {
                res.end(err.message);
            }
        }
    };
};

exports['notFound'] = function notFound() {
    return function notFound(req, res, next) {
        next(new Error.HTTP(404));
    };
};
<|endoftext|>


<commit_before>var env = process.env.NODE_ENV || 'development';
var host = require('os').hostname();

exports = module.exports = require('express');

exports['sanitizeHost'] = function sanitizeHost(app) {
    var hosts = app.config.host;
    if (!hosts) {
        hosts = app.config.host = [];
    } else if (!Array.isArray(hosts)) {
        hosts = app.config.host = [ hosts ];
    }
    hosts.forEach(function(host, i) {
        if (typeof host === 'string') {
            hosts[i] = new RegExp('^' + hosts[i].replace(/\./g, '\\.').replace(/\*/g, '[a-z0-9_-]+') + '(:\\d+)?$', 'i');
            // Make sure we get the original host names when stringifying the host name matcher.
            hosts[i].toJSON = function() { return host; };
        }
    });

    return function(req, res, next) {
        if (!req.headers.host) {
            return next();
        } else if (!hosts.length) {
            // Check that the supplied hostname is harmless. If not, we'll
            // substitute it with the hostname reported by the machine.
            if (/^\w([\w-]*\w)?(\.\w([\w-]*\w)?)*(:\d+)?$/.test(req.headers.host)) {
                return next();
            }
        } else {
            for (var i = 0; i < hosts.length; i++) {
                if (hosts[i].test(req.headers.host)) {
                    return next();
                }
            }
        }

        res.send(400);
    };
};

exports['validateCSRFToken'] = function validateCSRFToken() {
    return function(req, res, next) {
        if (req.method === 'GET' || req.method === 'HEAD') {
            next();
        } else if (req.body && req.cookies['bones.token'] && req.body['bones.token'] === req.cookies['bones.token']) {
            delete req.body['bones.token'];
            next();
        } else {
            next(new Error.HTTP(403));
        }
    };
};

exports['fragmentRedirect'] = function fragmentRedirect() {
    return function(req, res, next) {
        // @see https://code.google.com/web/ajaxcrawling/docs/specification.html
        if (req.query._escaped_fragment_ === undefined) {
            next();
        } else {
            // Force the first char of the path to be a slash to prevent
            // foreign redirects.
            var path = '/' + req.query._escaped_fragment_.substr(1);
            res.redirect(path, 301);
        }
    };
};

exports['showError'] = function showError() {
    return function showError(err, req, res, next) {
        if (!err.status) err.status = 500;

        // Output unexpected errors to console but hide them from public eyes.
        if (err.status >= 500) {
            if (process.env.NODE_ENV != 'test') console.error(err.stack || err.toString());
            if (process.env.NODE_ENV == 'production') err.message = 'Internal Server Error';
        }

        if ((req.headers.accept + '' || '').indexOf('json') >= 0) {
            res.writeHead(err.status, { 'Content-Type': 'application/json' });
            if (env === 'development') {
                res.end(JSON.stringify(err));
            } else {
                res.end(JSON.stringify({ message: err.message }));
            }
        } else {
            res.writeHead(err.status, { 'Content-Type': 'text/plain' });
            if (env === 'development') {
                res.end(err.stack);
            } else {
                res.end(err.message);
            }
        }
    };
};

exports['notFound'] = function notFound() {
    return function notFound(req, res, next) {
        next(new Error.HTTP(404));
    };
};
<commit_msg>Don't write a body for HEAD requests.<commit_after>var env = process.env.NODE_ENV || 'development';
var host = require('os').hostname();

exports = module.exports = require('express');

exports['sanitizeHost'] = function sanitizeHost(app) {
    var hosts = app.config.host;
    if (!hosts) {
        hosts = app.config.host = [];
    } else if (!Array.isArray(hosts)) {
        hosts = app.config.host = [ hosts ];
    }
    hosts.forEach(function(host, i) {
        if (typeof host === 'string') {
            hosts[i] = new RegExp('^' + hosts[i].replace(/\./g, '\\.').replace(/\*/g, '[a-z0-9_-]+') + '(:\\d+)?$', 'i');
            // Make sure we get the original host names when stringifying the host name matcher.
            hosts[i].toJSON = function() { return host; };
        }
    });

    return function(req, res, next) {
        if (!req.headers.host) {
            return next();
        } else if (!hosts.length) {
            // Check that the supplied hostname is harmless. If not, we'll
            // substitute it with the hostname reported by the machine.
            if (/^\w([\w-]*\w)?(\.\w([\w-]*\w)?)*(:\d+)?$/.test(req.headers.host)) {
                return next();
            }
        } else {
            for (var i = 0; i < hosts.length; i++) {
                if (hosts[i].test(req.headers.host)) {
                    return next();
                }
            }
        }

        res.send(400);
    };
};

exports['validateCSRFToken'] = function validateCSRFToken() {
    return function(req, res, next) {
        if (req.method === 'GET' || req.method === 'HEAD') {
            next();
        } else if (req.body && req.cookies['bones.token'] && req.body['bones.token'] === req.cookies['bones.token']) {
            delete req.body['bones.token'];
            next();
        } else {
            next(new Error.HTTP(403));
        }
    };
};

exports['fragmentRedirect'] = function fragmentRedirect() {
    return function(req, res, next) {
        // @see https://code.google.com/web/ajaxcrawling/docs/specification.html
        if (req.query._escaped_fragment_ === undefined) {
            next();
        } else {
            // Force the first char of the path to be a slash to prevent
            // foreign redirects.
            var path = '/' + req.query._escaped_fragment_.substr(1);
            res.redirect(path, 301);
        }
    };
};

exports['showError'] = function showError() {
    return function showError(err, req, res, next) {
        if (!err.status) err.status = 500;

        // Output unexpected errors to console but hide them from public eyes.
        if (err.status >= 500) {
            if (process.env.NODE_ENV != 'test') console.error(err.stack || err.toString());
            if (process.env.NODE_ENV == 'production') err.message = 'Internal Server Error';
        }

        if ((req.headers.accept + '' || '').indexOf('json') >= 0) {
            res.writeHead(err.status, { 'Content-Type': 'application/json' });
            if (env === 'development') {
                res.end('HEAD' == req.method ? null : JSON.stringify(err));
            } else {
                res.end('HEAD' == req.method ? null : JSON.stringify({ message: err.message }));
            }
        } else {
            res.writeHead(err.status, { 'Content-Type': 'text/plain' });
            if (env === 'development') {
                res.end('HEAD' == req.method ? null : err.stack);
            } else {
                res.end('HEAD' == req.method ? null : err.message);
            }
        }
    };
};

exports['notFound'] = function notFound() {
    return function notFound(req, res, next) {
        next(new Error.HTTP(404));
    };
};
<|endoftext|>


<commit_before>const express = require('express')

const tasks = require('./routes/tasks');

var router = express.Router();              // get an instance of the express Router
const app = express()
var bodyParser = require('body-parser');

// configure app to use bodyParser()
// this will let us get the data from a POST
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());

//var port = process.env.PORT || 3000;

// middleware to use for all requests
router.use(function(req, res, next) {
    // do logging
    console.log('Something is happening.');
    next(); // make sure we go to the next routes and don't stop here
});

// test route to make sure everything is working (accessed at GET http://localhost:8080/api)
router.get('/', function(req, res) {
    res.json({ message: 'hooray! welcome to our api!' });
});

// more routes for our API will happen here

// REGISTER OUR ROUTES -------------------------------
// all of our routes will be prefixed with /api
app.use('/api', router);

//app.listen(port);
//console.log('Magic happens on port ' + port);
app.use('/api/tasks', tasks);

module.exports = app<commit_msg>removed temp file<commit_after><|endoftext|>


<commit_before>import { dispatch as d3_dispatch } from 'd3-dispatch';
import { select as d3_select } from 'd3-selection';

import { t } from '../../util/locale';
import {
    utilGetSetValue,
    utilNoAuto,
    utilRebind
} from '../../util';

export function uiFieldTextarea(field) {
    var dispatch = d3_dispatch('change'),
        input = d3_select(null);

    function textarea(selection) {
        input = selection.selectAll('textarea')
            .data([0]);

        input = input.enter()
            .append('textarea')
            .attr('id', 'preset-input-' + field.safeid)
            .attr('placeholder', field.placeholder() || t('inspector.unknown'))
            .attr('maxlength', 255)
            .call(utilNoAuto)
            .on('input', change(true))
            .on('blur', change())
            .on('change', change())
            .merge(input);
    }

    function change(onInput) {
        return function() {
            var t = {};
            t[field.key] = utilGetSetValue(input) || undefined;
            dispatch.call('change', this, t, onInput);
        };
    }

    textarea.tags = function(tags) {
        utilGetSetValue(input, tags[field.key] || '');
    };

    textarea.focus = function() {
        input.node().focus();
    };

    return utilRebind(textarea, dispatch, 'on');
}
<commit_msg>Avoid multiline variable declaration<commit_after>import { dispatch as d3_dispatch } from 'd3-dispatch';
import { select as d3_select } from 'd3-selection';

import { t } from '../../util/locale';
import {
    utilGetSetValue,
    utilNoAuto,
    utilRebind
} from '../../util';

export function uiFieldTextarea(field) {
    var dispatch = d3_dispatch('change');
    var input = d3_select(null);

    function textarea(selection) {
        input = selection.selectAll('textarea')
            .data([0]);

        input = input.enter()
            .append('textarea')
            .attr('id', 'preset-input-' + field.safeid)
            .attr('placeholder', field.placeholder() || t('inspector.unknown'))
            .attr('maxlength', 255)
            .call(utilNoAuto)
            .on('input', change(true))
            .on('blur', change())
            .on('change', change())
            .merge(input);
    }

    function change(onInput) {
        return function() {
            var t = {};
            t[field.key] = utilGetSetValue(input) || undefined;
            dispatch.call('change', this, t, onInput);
        };
    }

    textarea.tags = function(tags) {
        utilGetSetValue(input, tags[field.key] || '');
    };

    textarea.focus = function() {
        input.node().focus();
    };

    return utilRebind(textarea, dispatch, 'on');
}
<|endoftext|>