chemicaljs 1.0.3 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,5 +1,5 @@
1
1
  # Chemical
2
- Create your own web proxy using Ultraviolet fast, easy, and no experience required.
2
+ Easily create your own web proxy with no experience required.
3
3
 
4
4
  ## Setup
5
5
 
@@ -62,10 +62,22 @@ In your project create a folder to store your static assets. Create an index.htm
62
62
  <script src="/chemical.js"></script>
63
63
  ```
64
64
 
65
- 2. In a inline script or javascript file, encode a URL with Chemical using `window.chemicalEncode`.
65
+ If you want to set the wisp server to an external server just change the `wisp` attribute.
66
+
67
+ ```html
68
+ <script wisp="wss://wisp.mercurywork.shop" src="/chemical.js"></script>
69
+ ```
70
+
71
+ 2. In a inline script or javascript file, encode a URL with Chemical using the async function `window.chemicalEncode`.
72
+
73
+ ```js
74
+ await window.chemicalEncode("https://example.com")
75
+ ```
76
+
77
+ Optional: Change service to `ultraviolet`, `scramjet`, or `rammerhead`.
66
78
 
67
79
  ```js
68
- window.chemicalEncode("https://example.com")
80
+ await window.chemicalEncode("https://example.com", "rammerhead")
69
81
  ```
70
82
 
71
83
  3. You may want to check if Chemical has loaded before encoding a URL.
@@ -92,9 +104,9 @@ Below is a simple example of a simple input that redirects to the encoded URL wh
92
104
  <script>
93
105
  const search = document.getElementById("search");
94
106
 
95
- search.addEventListener("keydown", function (e) {
107
+ search.addEventListener("keydown", async function (e) {
96
108
  if (e.key == "Enter" && window.chemicalLoaded && e.target.value) {
97
- window.location = window.chemicalEncode(e.target.value)
109
+ window.location = await window.chemicalEncode(e.target.value)
98
110
  }
99
111
  })
100
112
  </script>
@@ -104,7 +116,7 @@ Below is a simple example of a simple input that redirects to the encoded URL wh
104
116
 
105
117
  Some features that may come in the future are:
106
118
 
107
- - More proxy services.
119
+ - Server options to disable services.
108
120
  - Easy client components.
109
121
  - Easy tab cloaking components.
110
122
 
@@ -0,0 +1,235 @@
1
+ window.chemicalLoaded = false;
2
+
3
+ function rammerheadEncode(baseUrl) {
4
+ const mod = (n, m) => ((n % m) + m) % m
5
+ const baseDictionary =
6
+ "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz~-"
7
+ const shuffledIndicator = "_rhs"
8
+ const generateDictionary = function () {
9
+ let str = ""
10
+ const split = baseDictionary.split("")
11
+ while (split.length > 0) {
12
+ str += split.splice(Math.floor(Math.random() * split.length), 1)[0]
13
+ }
14
+ return str
15
+ }
16
+ class StrShuffler {
17
+ constructor(dictionary = generateDictionary()) {
18
+ this.dictionary = dictionary
19
+ }
20
+ shuffle(str) {
21
+ if (str.startsWith(shuffledIndicator)) {
22
+ return str
23
+ }
24
+ let shuffledStr = ""
25
+ for (let i = 0; i < str.length; i++) {
26
+ const char = str.charAt(i)
27
+ const idx = baseDictionary.indexOf(char)
28
+ if (char === "%" && str.length - i >= 3) {
29
+ shuffledStr += char
30
+ shuffledStr += str.charAt(++i)
31
+ shuffledStr += str.charAt(++i)
32
+ } else if (idx === -1) {
33
+ shuffledStr += char
34
+ } else {
35
+ shuffledStr += this.dictionary.charAt(
36
+ mod(idx + i, baseDictionary.length)
37
+ )
38
+ }
39
+ }
40
+ return shuffledIndicator + shuffledStr
41
+ }
42
+ unshuffle(str) {
43
+ if (!str.startsWith(shuffledIndicator)) {
44
+ return str
45
+ }
46
+
47
+ str = str.slice(shuffledIndicator.length)
48
+
49
+ let unshuffledStr = ""
50
+ for (let i = 0; i < str.length; i++) {
51
+ const char = str.charAt(i)
52
+ const idx = this.dictionary.indexOf(char)
53
+ if (char === "%" && str.length - i >= 3) {
54
+ unshuffledStr += char
55
+ unshuffledStr += str.charAt(++i)
56
+ unshuffledStr += str.charAt(++i)
57
+ } else if (idx === -1) {
58
+ unshuffledStr += char
59
+ } else {
60
+ unshuffledStr += baseDictionary.charAt(
61
+ mod(idx - i, baseDictionary.length)
62
+ )
63
+ }
64
+ }
65
+ return unshuffledStr
66
+ }
67
+ }
68
+ function get(url, callback, shush = false) {
69
+ var request = new XMLHttpRequest()
70
+ request.open("GET", url, true)
71
+ request.send()
72
+
73
+ request.onerror = function () {
74
+ if (!shush) console.log("Cannot communicate with the server")
75
+ }
76
+ request.onload = function () {
77
+ if (request.status === 200) {
78
+ callback(request.responseText)
79
+ } else {
80
+ if (!shush)
81
+ console.log(
82
+ 'unexpected server response to not match "200". Server says "' +
83
+ request.responseText +
84
+ '"'
85
+ )
86
+ }
87
+ }
88
+ }
89
+ var api = {
90
+ newsession(callback) {
91
+ get("/newsession", callback)
92
+ },
93
+ sessionexists(id, callback) {
94
+ get("/sessionexists?id=" + encodeURIComponent(id), function (res) {
95
+ if (res === "exists") return callback(true)
96
+ if (res === "not found") return callback(false)
97
+ console.log("unexpected response from server. received" + res)
98
+ })
99
+ },
100
+ shuffleDict(id, callback) {
101
+ console.log("Shuffling", id)
102
+ get("/api/shuffleDict?id=" + encodeURIComponent(id), function (res) {
103
+ callback(JSON.parse(res))
104
+ })
105
+ }
106
+ }
107
+ var localStorageKey = "rammerhead_sessionids"
108
+ var localStorageKeyDefault = "rammerhead_default_sessionid"
109
+ var sessionIdsStore = {
110
+ get() {
111
+ var rawData = localStorage.getItem(localStorageKey)
112
+ if (!rawData) return []
113
+ try {
114
+ var data = JSON.parse(rawData)
115
+ if (!Array.isArray(data)) throw "getout"
116
+ return data
117
+ } catch (e) {
118
+ return []
119
+ }
120
+ },
121
+ set(data) {
122
+ if (!data || !Array.isArray(data)) throw new TypeError("must be array")
123
+ localStorage.setItem(localStorageKey, JSON.stringify(data))
124
+ },
125
+ getDefault() {
126
+ var sessionId = localStorage.getItem(localStorageKeyDefault)
127
+ if (sessionId) {
128
+ var data = sessionIdsStore.get()
129
+ data.filter(function (e) {
130
+ return e.id === sessionId
131
+ })
132
+ if (data.length) return data[0]
133
+ }
134
+ return null
135
+ },
136
+ setDefault(id) {
137
+ localStorage.setItem(localStorageKeyDefault, id)
138
+ }
139
+ }
140
+ function addSession(id) {
141
+ var data = sessionIdsStore.get()
142
+ data.unshift({ id: id, createdOn: new Date().toLocaleString() })
143
+ sessionIdsStore.set(data)
144
+ }
145
+ function getSessionId() {
146
+ return new Promise(resolve => {
147
+ var id = localStorage.getItem("session-string")
148
+ api.sessionexists(id, function (value) {
149
+ if (!value) {
150
+ console.log("Session validation failed")
151
+ api.newsession(function (id) {
152
+ addSession(id)
153
+ localStorage.setItem("session-string", id)
154
+ console.log(id)
155
+ console.log("^ new id")
156
+ resolve(id)
157
+ })
158
+ } else {
159
+ resolve(id)
160
+ }
161
+ })
162
+ })
163
+ }
164
+ var ProxyHref
165
+
166
+ return getSessionId().then(id => {
167
+ return new Promise(resolve => {
168
+ api.shuffleDict(id, function (shuffleDict) {
169
+ var shuffler = new StrShuffler(shuffleDict)
170
+ ProxyHref = "/" + id + "/" + shuffler.shuffle(baseUrl)
171
+ resolve(ProxyHref)
172
+ })
173
+ })
174
+ })
175
+ }
176
+
177
+ async function chemicalEncode(url, service = "ultraviolet") {
178
+ switch (service) {
179
+ case "ultraviolet":
180
+ return (
181
+ window.location.origin +
182
+ window.__uv$config.prefix +
183
+ window.__uv$config.encodeUrl(url)
184
+ );
185
+ break;
186
+ case "rammerhead":
187
+ return window.location.origin + (await rammerheadEncode(url));
188
+ break;
189
+ case "scramjet":
190
+ return (
191
+ window.location.origin +
192
+ window.__scramjet$config.prefix +
193
+ window.__scramjet$config.codec.encode(url)
194
+ );
195
+ break;
196
+ }
197
+ }
198
+
199
+ let wispURL = document.currentScript.getAttribute("wisp");
200
+
201
+ async function registerSW() {
202
+ const connection = new window.BareMux.BareMuxConnection("/baremux/worker.js");
203
+ await connection.setTransport("/epoxy/module.js", [{ wisp: wispURL || (location.protocol === "https:" ? "wss" : "ws") + "://" + location.host + "/wisp/" }]);
204
+
205
+ if ("serviceWorker" in navigator) {
206
+ await navigator.serviceWorker.register("/chemical.sw.js");
207
+ } else {
208
+ console.error("Service worker failed to register.")
209
+ }
210
+ }
211
+
212
+ async function loadScript(src) {
213
+ await new Promise((resolve, reject) => {
214
+ const script = document.createElement("script");
215
+ script.src = src;
216
+ script.onload = () => {
217
+ resolve();
218
+ };
219
+ script.onerror = () => {
220
+ reject();
221
+ };
222
+ document.head.appendChild(script);
223
+ });
224
+ }
225
+
226
+ (async () => {
227
+ await loadScript("/baremux/index.js");
228
+ await loadScript("/uv/uv.bundle.js");
229
+ await loadScript("/uv/uv.config.js");
230
+ await loadScript("/scramjet/scramjet.codecs.js");
231
+ await loadScript("/scramjet/scramjet.config.js");
232
+ await registerSW();
233
+ chemicalLoaded = true;
234
+ window.dispatchEvent(new Event("chemicalLoaded"));
235
+ })();
@@ -5,9 +5,9 @@
5
5
  <script>
6
6
  const search = document.getElementById("search");
7
7
 
8
- search.addEventListener("keydown", function (e) {
8
+ search.addEventListener("keydown", async function (e) {
9
9
  if (e.key == "Enter" && window.chemicalLoaded && e.target.value) {
10
- window.location = window.chemicalEncode(e.target.value)
10
+ window.location = await window.chemicalEncode(e.target.value, "rammerhead")
11
11
  }
12
12
  })
13
13
  </script>
package/index.js CHANGED
@@ -3,25 +3,69 @@ import { createServer } from "node:http";
3
3
  import express from "express";
4
4
  import wisp from "wisp-server-node";
5
5
  import { baremuxPath } from "@mercuryworkshop/bare-mux/node";
6
- import { libcurlPath } from "@mercuryworkshop/libcurl-transport";
7
6
  import { epoxyPath } from "@mercuryworkshop/epoxy-transport";
8
7
  import { uvPath } from "@titaniumnetwork-dev/ultraviolet";
8
+ import { scramjetPath } from "@mercuryworkshop/scramjet";
9
+ import createRammerhead from "rammerhead/src/server/index.js";
9
10
 
10
11
  class ChemicalServer {
11
12
  constructor() {
13
+ const rh = createRammerhead()
14
+
15
+ const rammerheadScopes = [
16
+ "/rammerhead.js",
17
+ "/hammerhead.js",
18
+ "/transport-worker.js",
19
+ "/task.js",
20
+ "/iframe-task.js",
21
+ "/worker-hammerhead.js",
22
+ "/messaging",
23
+ "/sessionexists",
24
+ "/deletesession",
25
+ "/newsession",
26
+ "/editsession",
27
+ "/needpassword",
28
+ "/syncLocalStorage",
29
+ "/api/shuffleDict"
30
+ ]
31
+
32
+ const rammerheadSession = /^\/[a-z0-9]{32}/
33
+
34
+ const shouldRouteRh = req => {
35
+ const url = new URL(req.url, "http://0.0.0.0")
36
+ return (
37
+ rammerheadScopes.includes(url.pathname) ||
38
+ rammerheadSession.test(url.pathname)
39
+ )
40
+ }
41
+
42
+ const routeRhRequest = (req, res) => {
43
+ rh.emit("request", req, res)
44
+ }
45
+
46
+ const routeRhUpgrade = (req, socket, head) => {
47
+ rh.emit("upgrade", req, socket, head)
48
+ }
49
+
12
50
  this.server = createServer();
13
51
  this.app = express();
14
- this.app.use(express.static(resolve(import.meta.dirname, "public")));
52
+ this.app.use(express.static(resolve(import.meta.dirname, "client")));
15
53
  this.app.use("/baremux/", express.static(baremuxPath));
16
- this.app.use("/libcurl/", express.static(libcurlPath));
17
54
  this.app.use("/epoxy/", express.static(epoxyPath));
18
55
  this.app.use("/uv/", express.static(uvPath));
56
+ this.app.use("/scramjet/", express.static(scramjetPath));
19
57
  this.server.on("request", (req, res) => {
20
- this.app(req, res);
58
+ if (shouldRouteRh(req)) {
59
+ routeRhRequest(req, res);
60
+ } else {
61
+ this.app(req, res);
62
+ }
21
63
  });
22
64
  this.server.on("upgrade", (req, socket, head) => {
23
65
  if (req.url && req.url.endsWith("/wisp/")) {
24
66
  wisp.routeRequest(req, socket, head);
67
+ } else if (shouldRouteRh(req)) {
68
+ routeRhUpgrade(req, socket, head);
25
69
  } else {
26
70
  socket.end();
27
71
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "chemicaljs",
3
- "version": "1.0.3",
4
- "description": "Create your own web proxy using Ultraviolet fast, easy, and no experience required.",
3
+ "version": "1.1.0",
4
+ "description": "Easily create your own web proxy with no experience required.",
5
5
  "repository": {
6
6
  "type": "git",
7
7
  "url": "git+https://github.com/chemicaljs/chemical.git"
@@ -11,11 +11,12 @@
11
11
  "author": "Nebelung",
12
12
  "license": "MIT",
13
13
  "dependencies": {
14
- "@mercuryworkshop/bare-mux": "^1.1.4",
15
- "@mercuryworkshop/epoxy-transport": "^2.0.6",
16
- "@mercuryworkshop/libcurl-transport": "^1.3.2",
17
- "@titaniumnetwork-dev/ultraviolet": "^3.1.5",
14
+ "@mercuryworkshop/bare-mux": "^2.0.0",
15
+ "@mercuryworkshop/epoxy-transport": "^2.1.0",
16
+ "@mercuryworkshop/scramjet": "^1.0.2",
17
+ "@titaniumnetwork-dev/ultraviolet": "^3.2.0",
18
18
  "express": "^4.19.2",
19
+ "rammerhead": "file:rammerhead.tgz",
19
20
  "wisp-server-node": "^1.1.0"
20
21
  }
21
22
  }
package/rammerhead.tgz ADDED
Binary file
@@ -1,44 +0,0 @@
1
- window.chemicalLoaded = false;
2
-
3
- function chemicalEncode(url) {
4
- return window.location.origin + window.__uv$config.prefix + window.__uv$config.encodeUrl(url)
5
- }
6
-
7
- let wispURL = document.currentScript.getAttribute("wisp");
8
-
9
- async function registerSW() {
10
- if ("serviceWorker" in navigator) {
11
- await navigator.serviceWorker.register("/chemical.sw.js");
12
-
13
- await window.BareMux.SetTransport("CurlMod.LibcurlClient", {
14
- wisp: wispURL || (location.protocol === "https:" ? "wss" : "ws") + "://" + location.host + "/wisp/",
15
- });
16
- } else {
17
- console.error("Service worker failed to register.")
18
- }
19
- }
20
-
21
- async function loadScript(src) {
22
- await new Promise((resolve, reject) => {
23
- const script = document.createElement("script");
24
- script.src = src;
25
- script.onload = () => {
26
- resolve();
27
- };
28
- script.onerror = () => {
29
- reject();
30
- };
31
- document.head.appendChild(script);
32
- });
33
- }
34
-
35
- (async () => {
36
- await loadScript("/baremux/bare.cjs");
37
- await loadScript("/libcurl/index.js");
38
- await loadScript("/epoxy/index.js");
39
- await loadScript("/uv/uv.bundle.js");
40
- await loadScript("/uv/uv.config.js");
41
- await registerSW();
42
- chemicalLoaded = true;
43
- window.dispatchEvent(new Event("chemicalLoaded"));
44
- })();
File without changes
File without changes