]> git.sur5r.net Git - minitube/blob - lib/js/js.cpp
New upstream version 3.9.3
[minitube] / lib / js / js.cpp
1 #include "js.h"
2
3 #include "cachedhttp.h"
4 #include "jsvm.h"
5
6 namespace {
7 Http &cachedHttp() {
8     static Http *h = [] {
9         CachedHttp *cachedHttp = new CachedHttp(Http::instance(), "js");
10         cachedHttp->setMaxSeconds(3600);
11         // Avoid expiring the cached js
12         cachedHttp->setMaxSize(0);
13
14         cachedHttp->getValidators().insert("application/javascript", [](const auto &reply) -> bool {
15             return !reply.body().isEmpty();
16         });
17
18         return cachedHttp;
19     }();
20     return *h;
21 }
22 } // namespace
23
24 JS &JS::instance() {
25     static thread_local JS i;
26     return i;
27 }
28
29 JS::JS(QObject *parent) : QObject(parent), engine(nullptr) {}
30
31 void JS::initialize(const QUrl &url) {
32     this->url = url;
33     initialize();
34 }
35
36 bool JS::checkError(const QJSValue &value) {
37     if (value.isError()) {
38         qWarning() << "Error" << value.toString();
39         qDebug() << value.property("stack").toString().splitRef('\n');
40         return true;
41     }
42     return false;
43 }
44
45 bool JS::isInitialized() {
46     if (ready) return true;
47     initialize();
48     return false;
49 }
50
51 JSResult &JS::callFunction(JSResult *result, const QString &name, const QJSValueList &args) {
52     if (!isInitialized()) {
53         qDebug() << "Not initialized";
54         QTimer::singleShot(1000, this,
55                            [this, result, name, args] { callFunction(result, name, args); });
56         return *result;
57     }
58
59     auto function = engine->evaluate(name);
60     if (!function.isCallable()) {
61         qWarning() << function.toString() << " is not callable";
62         QTimer::singleShot(0, result, [result, function] { result->setError(function); });
63         return *result;
64     }
65
66     auto args2 = args;
67     args2.prepend(engine->newQObject(result));
68     qDebug() << "Calling" << function.toString();
69     auto v = function.call(args2);
70     if (checkError(v)) QTimer::singleShot(0, result, [result, v] { result->setError(v); });
71
72     return *result;
73 }
74
75 void JS::resetNAM() {
76     class MyCookieJar : public QNetworkCookieJar {
77         bool insertCookie(const QNetworkCookie &cookie) {
78             if (cookie.name().contains("CONSENT")) {
79                 qDebug() << "Fixing CONSENT cookie" << cookie;
80                 auto cookie2 = cookie;
81                 cookie2.setValue(cookie.value().replace("PENDING", "YES"));
82                 return QNetworkCookieJar::insertCookie(cookie2);
83             }
84             return QNetworkCookieJar::insertCookie(cookie);
85         }
86     };
87
88     auto nam = getEngine().networkAccessManager();
89     nam->clearAccessCache();
90     nam->setCookieJar(new MyCookieJar());
91 }
92
93 void JS::initialize() {
94     if (url.isEmpty()) {
95         qDebug() << "No js url set";
96         return;
97     }
98
99     if (initializing) return;
100     initializing = true;
101     qDebug() << "Initializing";
102
103     if (engine) engine->deleteLater();
104     engine = new QQmlEngine(this);
105     engine->setNetworkAccessManagerFactory(&namFactory);
106     engine->globalObject().setProperty("global", engine->globalObject());
107
108     QJSValue timer = engine->newQObject(new JSTimer(engine));
109     engine->globalObject().setProperty("setTimeoutQt", timer.property("setTimeout"));
110     QJSValue setTimeoutWrapperFunction =
111             engine->evaluate("function setTimeout(cb, delay) {"
112                              "const args = Array.prototype.slice.call(arguments, 2);"
113                              "return setTimeoutQt(cb, delay, args);"
114                              "}");
115     checkError(setTimeoutWrapperFunction);
116     engine->globalObject().setProperty("clearTimeout", timer.property("clearTimeout"));
117
118     QJSValue vm = engine->newQObject(new JSVM(engine));
119     engine->globalObject().setProperty("runInContextQt", vm.property("runInContext"));
120
121     connect(cachedHttp().get(url), &HttpReply::finished, this, [this](auto &reply) {
122         if (!reply.isSuccessful()) {
123             emit initFailed("Cannot load JS");
124             qDebug() << "Cannot load JS";
125             initializing = false;
126             return;
127         }
128         auto value = engine->evaluate(reply.body());
129         if (!checkError(value)) {
130             qDebug() << "Initialized";
131             resetNAM();
132             ready = true;
133             emit initialized();
134         }
135         initializing = false;
136     });
137 }