summaryrefslogtreecommitdiff
path: root/platform/node/src/node_request.cpp
blob: fa560ed4e73ee3bfba7b00ad1f5efe6c2b70e436 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
#include "node_request.hpp"
#include <mbgl/storage/response.hpp>
#include <mbgl/util/chrono.hpp>

#include <cmath>
#include <iostream>

namespace node_mbgl {

////////////////////////////////////////////////////////////////////////////////////////////////
// Static Node Methods

Nan::Persistent<v8::Function> NodeRequest::constructor;

NAN_MODULE_INIT(NodeRequest::Init) {
    v8::Local<v8::FunctionTemplate> tpl = Nan::New<v8::FunctionTemplate>(New);

    tpl->InstanceTemplate()->SetInternalFieldCount(1);
    tpl->SetClassName(Nan::New("Request").ToLocalChecked());

    constructor.Reset(tpl->GetFunction());
    Nan::Set(target, Nan::New("Request").ToLocalChecked(), tpl->GetFunction());
}

NAN_METHOD(NodeRequest::New) {
    auto req = new NodeRequest(*reinterpret_cast<mbgl::FileSource::Callback*>(info[0].As<v8::External>()->Value()));
    req->Wrap(info.This());
    info.GetReturnValue().Set(info.This());
}

v8::Handle<v8::Object> NodeRequest::Create(const mbgl::Resource& resource, mbgl::FileSource::Callback callback) {
    Nan::EscapableHandleScope scope;

    v8::Local<v8::Value> argv[] = {
        Nan::New<v8::External>(const_cast<mbgl::FileSource::Callback*>(&callback))
    };
    auto instance = Nan::New(constructor)->NewInstance(1, argv);

    Nan::Set(instance, Nan::New("url").ToLocalChecked(), Nan::New(resource.url).ToLocalChecked());
    Nan::Set(instance, Nan::New("kind").ToLocalChecked(), Nan::New<v8::Integer>(int(resource.kind)));

    return scope.Escape(instance);
}

NAN_METHOD(NodeRequest::Respond) {
    using Error = mbgl::Response::Error;

    // Move out of the object so callback() can only be fired once.
    auto request = Nan::ObjectWrap::Unwrap<NodeRequest>(info.Data().As<v8::Object>());
    auto callback = std::move(request->callback);
    if (!callback) {
        info.GetReturnValue().SetUndefined();
        return;
    }

    mbgl::Response response;

    if (info.Length() < 1) {
        response.noContent = true;

    } else if (info[0]->BooleanValue()) {
        std::unique_ptr<Nan::Utf8String> message;

        // Store the error string.
        if (info[0]->IsObject()) {
            auto err = info[0]->ToObject();
            if (Nan::Has(err, Nan::New("message").ToLocalChecked()).FromJust()) {
                message = std::make_unique<Nan::Utf8String>(
                    Nan::Get(err, Nan::New("message").ToLocalChecked())
                        .ToLocalChecked()
                        ->ToString());
            }
        }

        if (!message) {
            message = std::make_unique<Nan::Utf8String>(info[0]->ToString());
        }
        response.error = std::make_unique<Error>(
            Error::Reason::Other, std::string{ **message, size_t(message->length()) });

    } else if (info.Length() < 2 || !info[1]->IsObject()) {
        return Nan::ThrowTypeError("Second argument must be a response object");

    } else {
        auto res = info[1]->ToObject();

        if (Nan::Has(res, Nan::New("modified").ToLocalChecked()).FromJust()) {
            const double modified = Nan::Get(res, Nan::New("modified").ToLocalChecked()).ToLocalChecked()->ToNumber()->Value();
            if (!std::isnan(modified)) {
                response.modified = mbgl::Timestamp{ mbgl::Seconds(
                    static_cast<mbgl::Seconds::rep>(modified / 1000)) };
            }
        }

        if (Nan::Has(res, Nan::New("expires").ToLocalChecked()).FromJust()) {
            const double expires = Nan::Get(res, Nan::New("expires").ToLocalChecked()).ToLocalChecked()->ToNumber()->Value();
            if (!std::isnan(expires)) {
                response.expires = mbgl::Timestamp{ mbgl::Seconds(
                    static_cast<mbgl::Seconds::rep>(expires / 1000)) };
            }
        }

        if (Nan::Has(res, Nan::New("etag").ToLocalChecked()).FromJust()) {
            auto etagHandle = Nan::Get(res, Nan::New("etag").ToLocalChecked()).ToLocalChecked();
            if (etagHandle->BooleanValue()) {
                const Nan::Utf8String etag { etagHandle->ToString() };
                response.etag = std::string { *etag, size_t(etag.length()) };
            }
        }

        if (Nan::Has(res, Nan::New("data").ToLocalChecked()).FromJust()) {
            auto dataHandle = Nan::Get(res, Nan::New("data").ToLocalChecked()).ToLocalChecked();
            if (node::Buffer::HasInstance(dataHandle)) {
                response.data = std::make_shared<std::string>(
                    node::Buffer::Data(dataHandle),
                    node::Buffer::Length(dataHandle)
                );
            } else {
                return Nan::ThrowTypeError("Response data must be a Buffer");
            }
        }
    }

    // Send the response object to the NodeFileSource object
    callback(response);
    info.GetReturnValue().SetUndefined();
}

////////////////////////////////////////////////////////////////////////////////////////////////
// Instance

NodeRequest::NodeAsyncRequest::NodeAsyncRequest(NodeRequest* request_) : request(request_) {
    assert(request);
    // Make sure the JS object has a pointer to this so that it can remove its pointer in the
    // destructor
    request->asyncRequest = this;
}

NodeRequest::NodeAsyncRequest::~NodeAsyncRequest() {
    if (request) {
        // Remove the callback function because the AsyncRequest was canceled and we are no longer
        // interested in the result.
        request->callback = {};
        request->asyncRequest = nullptr;
    }
}

NodeRequest::NodeRequest(mbgl::FileSource::Callback callback_)
    : callback(callback_) {
}

NodeRequest::~NodeRequest() {
    // When this object gets garbage collected, make sure that the AsyncRequest can no longer
    // attempt to remove the callback function this object was holding (it can't be fired anymore).
    if (asyncRequest) {
        asyncRequest->request = nullptr;
    }
}

} // namespace node_mbgl