Skip to content

Commit 9235d19

Browse files
committed
Add: cloudflared tunnel
1 parent 11908a2 commit 9235d19

File tree

3 files changed

+121
-6
lines changed

3 files changed

+121
-6
lines changed

β€Ž.env.example

+1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
SERVER_PORT=3040
2+
CLOUDFLARED=true
23

34
PROXY=false
45
PROXY_HOST=proxy.example.com

β€Ž.gitignore

+2-1
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,5 @@ dist/
1111
*.js.map
1212
.parcel-cache
1313
db.json
14-
.env
14+
.env
15+
cloudflared

β€Žsrc/app.ts

+118-5
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
import express, { Request, Response, NextFunction } from "express";
2+
import { ChildProcessWithoutNullStreams, spawn } from "child_process";
3+
import fs from "fs";
4+
import path from "path";
25
import bodyParser from "body-parser";
36
import axios from "axios";
47
import https from "https";
8+
import os from "os";
59
import { encode } from "gpt-3-encoder";
610
import { randomUUID } from "crypto";
711
import { config } from "dotenv";
@@ -14,6 +18,7 @@ const baseUrl = "https://chat.openai.com";
1418
const apiUrl = `${baseUrl}/backend-anon/conversation`;
1519
const refreshInterval = 60000; // Interval to refresh token in ms
1620
const errorWait = 120000; // Wait time in ms after an error
21+
let cloudflared: ChildProcessWithoutNullStreams;
1722

1823
// Initialize global variables to store the session token and device ID
1924
let token: string;
@@ -133,7 +138,7 @@ async function handleChatCompletion(req: Request, res: Response) {
133138
try {
134139
const body = {
135140
action: "next",
136-
messages: req.body.messages.map((message) => ({
141+
messages: req.body.messages.map((message: { role: any; content: any }) => ({
137142
author: { role: message.role },
138143
content: { content_type: "text", parts: [message.content] },
139144
})),
@@ -322,17 +327,125 @@ app.use((req, res) =>
322327
})
323328
);
324329

330+
async function DownloadCloudflared(): Promise<string> {
331+
const platform = os.platform();
332+
let url: string;
333+
334+
if (platform === "win32") {
335+
const arch = os.arch() === "x64" ? "amd64" : "386";
336+
url = `https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-windows-${arch}.exe`;
337+
} else {
338+
let arch = os.arch();
339+
switch (arch) {
340+
case "x64":
341+
arch = "amd64";
342+
break;
343+
case "arm":
344+
case "arm64":
345+
break;
346+
default:
347+
arch = "amd64"; // Default to amd64 if unknown architecture
348+
}
349+
const platformLower = platform.toLowerCase();
350+
url = `https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-${platformLower}-${arch}`;
351+
}
352+
353+
const fileName = platform === "win32" ? "cloudflared.exe" : "cloudflared";
354+
const filePath = path.resolve(fileName);
355+
356+
if (fs.existsSync(filePath)) {
357+
return filePath;
358+
}
359+
360+
try {
361+
const response = await axiosInstance({
362+
method: "get",
363+
url: url,
364+
responseType: "stream",
365+
});
366+
367+
const writer = fs.createWriteStream(filePath);
368+
369+
response.data.pipe(writer);
370+
371+
return new Promise<string>((resolve, reject) => {
372+
writer.on("finish", () => {
373+
if (platform !== "win32") {
374+
fs.chmodSync(filePath, 0o755);
375+
}
376+
resolve(filePath);
377+
});
378+
379+
writer.on("error", reject);
380+
});
381+
} catch (error: any) {
382+
// console.error("Failed to download file:", error.message);
383+
return null;
384+
}
385+
}
386+
387+
async function StartCloudflaredTunnel(cloudflaredPath: string): Promise<string> {
388+
const localUrl = `http://localhost:${port}`;
389+
return new Promise<string>((resolve, reject) => {
390+
cloudflared = spawn(cloudflaredPath, ["tunnel", "--url", localUrl]);
391+
392+
cloudflared.stdout.on("data", (data: any) => {
393+
const output = data.toString();
394+
// console.log("Cloudflared Output:", output);
395+
396+
// Adjusted regex to specifically match URLs that end with .trycloudflare.com
397+
const urlMatch = output.match(/https:\/\/[^\s]+\.trycloudflare\.com/);
398+
if (urlMatch) {
399+
let url = urlMatch[0];
400+
resolve(url);
401+
}
402+
});
403+
404+
cloudflared.stderr.on("data", (data: any) => {
405+
const output = data.toString();
406+
// console.error("Error from cloudflared:", output);
407+
408+
const urlMatch = output.match(/https:\/\/[^\s]+\.trycloudflare\.com/);
409+
if (urlMatch) {
410+
let url = urlMatch[0];
411+
resolve(url);
412+
}
413+
});
414+
415+
cloudflared.on("close", (code: any) => {
416+
resolve(null);
417+
// console.log(`Cloudflared tunnel process exited with code ${code}`);
418+
});
419+
});
420+
}
421+
325422
// Start the server and the session ID refresh loop
326-
app.listen(port, () => {
423+
app.listen(port, async () => {
424+
if (process.env.CLOUDFLARED === undefined) process.env.CLOUDFLARED = "true";
425+
let cloudflared = process.env.CLOUDFLARED === "true";
426+
let filePath: string;
427+
let publicURL: string;
428+
if (cloudflared) {
429+
filePath = await DownloadCloudflared();
430+
publicURL = await StartCloudflaredTunnel(filePath);
431+
}
432+
327433
console.log(`πŸ’‘ Server is running at http://localhost:${port}`);
328434
console.log();
329-
console.log(`πŸ”— Base URL: http://localhost:${port}/v1`);
330-
console.log(`πŸ”— ChatCompletion Endpoint: http://localhost:${port}/v1/chat/completions`);
435+
console.log(`πŸ”— Local Base URL: http://localhost:${port}/v1`);
436+
console.log(`πŸ”— Local Endpoint: http://localhost:${port}/v1/chat/completions`);
331437
console.log();
438+
if (cloudflared && publicURL) console.log(`πŸ”— Public Base URL: ${publicURL}/v1`);
439+
if (cloudflared && publicURL) console.log(`πŸ”— Public Endpoint: ${publicURL}/v1/chat/completions`);
440+
else if (cloudflared && !publicURL) {
441+
console.log("πŸ”— Public Endpoint: (Failed to start cloudflared tunnel, please restart the server.)");
442+
if (filePath) fs.unlinkSync(filePath);
443+
}
444+
if (cloudflared && publicURL) console.log();
332445
console.log("πŸ“ Author: Pawan.Krd");
333446
console.log(`🌐 Discord server: https://discord.gg/pawan`);
334447
console.log("🌍 GitHub Repository: https://github.com/PawanOsman/ChatGPT");
335-
console.log(`Don't forget to ⭐ star the repository if you like this project!`);
448+
console.log(`πŸ’– Don't forget to star the repository if you like this project!`);
336449
console.log();
337450

338451
setTimeout(async () => {

0 commit comments

Comments
Β (0)