UNPKG

5.53 kBJavaScriptView Raw
1
2(function (self, factory) {
3 const plugin = factory(self)
4
5 if (typeof exports === 'object' && typeof exports['nodeName'] !== 'string') {
6 module.exports = plugin
7 } else {
8 if ('_hyperscript' in self) self._hyperscript.use(plugin)
9 }
10})(typeof self !== 'undefined' ? self : this, self => {
11
12 function genUUID() {
13 return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function (c) {
14 var r = (Math.random() * 16) | 0,
15 v = c == "x" ? r : (r & 0x3) | 0x8;
16 return v.toString(16);
17 });
18 }
19
20 function parseUrl(url) {
21 var finalUrl = url;
22 if (finalUrl.indexOf("/") === 0) { // complete absolute paths without scheme only
23 var basePart = window.location.hostname + (window.location.port ? ':' + window.location.port : '');
24 if (window.location.protocol === 'https:') {
25 finalUrl = "wss://" + basePart + finalUrl;
26 } else if (window.location.protocol === 'http:') {
27 finalUrl = "ws://" + basePart + finalUrl;
28 }
29 }
30 return finalUrl;
31 }
32
33 function createSocket(url) {
34 var parsedUrl = parseUrl(url.evaluate());
35 return new WebSocket(parsedUrl);
36 }
37
38 /**
39 * @param {HyperscriptObject} _hyperscript
40 */
41 return _hyperscript => {
42
43 /** @type {(string | symbol)[]} */
44 var PROXY_BLACKLIST = ["then", "catch", "length", "asyncWrapper", "toJSON"];
45
46 _hyperscript.addFeature("socket", function (parser, runtime, tokens) {
47 function getProxy(timeout) {
48 return new Proxy(
49 {},
50 {
51 get: function (obj, property) {
52 if (PROXY_BLACKLIST.indexOf(property) >= 0) {
53 return null;
54 } else if (property === "noTimeout") {
55 return getProxy(-1);
56 } else if (property === "timeout") {
57 return function (i) {
58 return getProxy(parseInt(i));
59 };
60 } else {
61 return function () {
62 var uuid = genUUID();
63 var args = [];
64 for (var i = 0; i < arguments.length; i++) {
65 args.push(arguments[i]);
66 }
67 var rpcInfo = {
68 iid: uuid,
69 function: property,
70 args: args,
71 };
72 socket = socket ? socket : createSocket(url); //recreate socket if needed
73 socket.send(JSON.stringify(rpcInfo));
74
75 var promise = new Promise(function (resolve, reject) {
76 promises[uuid] = {
77 resolve: resolve,
78 reject: reject,
79 };
80 });
81
82 if (timeout >= 0) {
83 setTimeout(function () {
84 if (promises[uuid]) {
85 promises[uuid].reject("Timed out");
86 }
87 delete promises[uuid];
88 }, timeout); // TODO configurable?
89 }
90 return promise;
91 };
92 }
93 },
94 }
95 );
96 }
97
98 if (tokens.matchToken("socket")) {
99 var name = parser.requireElement("dotOrColonPath", tokens);
100 var qualifiedName = name.evaluate();
101 var nameSpace = qualifiedName.split(".");
102 var socketName = nameSpace.pop();
103
104 var promises = {};
105 var url = parser.requireElement("stringLike", tokens);
106
107 var defaultTimeout = 10000;
108 if (tokens.matchToken("with")) {
109 tokens.requireToken("timeout");
110 defaultTimeout = parser.requireElement("expression", tokens).evaluate();
111 }
112
113 if (tokens.matchToken("on")) {
114 tokens.requireToken("message");
115 if (tokens.matchToken("as")) {
116 tokens.requireToken("json");
117 var jsonMessages = true;
118 }
119 var messageHandler = parser.requireElement("commandList", tokens);
120 var implicitReturn = {
121 type: "implicitReturn",
122 op: function (context) {
123 return runtime.HALT;
124 },
125 execute: function (context) {
126 // do nothing
127 },
128 };
129 var end = messageHandler;
130 while (end.next) {
131 end = end.next;
132 }
133 end.next = implicitReturn;
134 // TODO set parent?
135 // parser.setParent(implicitReturn, initFeature);
136 }
137
138 var socket = createSocket(url);
139 var rpcProxy = getProxy(defaultTimeout);
140
141 var socketObject = {
142 raw: socket,
143 dispatchEvent: function (evt) {
144 var details = evt.detail;
145 // remove hyperscript internals
146 delete details.sender;
147 delete details._namedArgList_;
148 socket.send(JSON.stringify(Object.assign({ type: evt.type }, details)));
149 },
150 rpc: rpcProxy,
151 };
152
153 var socketFeature = {
154 name: socketName,
155 socket: socketObject,
156 install: function (target) {
157 runtime.assignToNamespace(target, nameSpace, socketName, socketObject);
158 },
159 };
160
161 socket.onmessage = function (evt) {
162 var data = evt.data;
163 try {
164 var dataAsJson = JSON.parse(data);
165 } catch (e) {
166 // not JSON
167 }
168
169 // RPC reply
170 if (dataAsJson && dataAsJson.iid) {
171 if (dataAsJson.throw) {
172 promises[dataAsJson.iid].reject(dataAsJson.throw);
173 } else {
174 promises[dataAsJson.iid].resolve(dataAsJson.return);
175 }
176 delete promises[dataAsJson.iid];
177 }
178
179 if (messageHandler) {
180 var context = runtime.makeContext(socketObject, socketFeature, socketObject);
181 if (jsonMessages) {
182 if (dataAsJson) {
183 context.message = dataAsJson;
184 context.result = dataAsJson;
185 } else {
186 throw "Received non-JSON message from socket: " + data;
187 }
188 } else {
189 context.message = data;
190 context.result = data;
191 }
192 messageHandler.execute(context);
193 }
194 };
195
196 // clear socket on close to be recreated
197 socket.addEventListener("close", function (e) {
198 socket = null;
199 });
200
201 return socketFeature;
202 }
203 });
204 }
205})