pinme 2.0.0-beta.10 → 2.0.0-beta.12

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.
Files changed (2) hide show
  1. package/dist/index.js +905 -905
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1775,7 +1775,7 @@ var import_chalk22 = __toESM(require("chalk"));
1775
1775
  var import_figlet5 = __toESM(require("figlet"));
1776
1776
 
1777
1777
  // package.json
1778
- var version = "2.0.0-beta.10";
1778
+ var version = "2.0.0-beta.12";
1779
1779
 
1780
1780
  // bin/upload.ts
1781
1781
  var import_path7 = __toESM(require("path"));
@@ -5561,11 +5561,11 @@ var {
5561
5561
  } = axios_default;
5562
5562
 
5563
5563
  // bin/utils/uploadToIpfsSplit.ts
5564
- var import_fs_extra4 = __toESM(require("fs-extra"));
5565
- var import_path5 = __toESM(require("path"));
5564
+ var import_fs_extra5 = __toESM(require("fs-extra"));
5565
+ var import_path6 = __toESM(require("path"));
5566
5566
  var import_form_data2 = __toESM(require("form-data"));
5567
5567
  var import_ora = __toESM(require("ora"));
5568
- var crypto2 = __toESM(require("crypto"));
5568
+ var crypto3 = __toESM(require("crypto"));
5569
5569
 
5570
5570
  // bin/utils/uploadLimits.ts
5571
5571
  var import_fs = __toESM(require("fs"));
@@ -5746,722 +5746,331 @@ function getUid() {
5746
5746
  return getDeviceId();
5747
5747
  }
5748
5748
 
5749
- // bin/utils/uploadToIpfsSplit.ts
5750
- var IPFS_API_URL = "https://pinme.benny1996.win/api/v3";
5751
- var MAX_RETRIES = parseInt(process.env.MAX_RETRIES || "2");
5752
- var RETRY_DELAY = parseInt(process.env.RETRY_DELAY_MS || "1000");
5753
- var TIMEOUT = parseInt(process.env.TIMEOUT_MS || "600000");
5754
- var MAX_POLL_TIME = parseInt("5") * 60 * 1e3;
5755
- var POLL_INTERVAL = parseInt(process.env.POLL_INTERVAL_SECONDS || "2") * 1e3;
5756
- var PROGRESS_UPDATE_INTERVAL = 200;
5757
- var EXPECTED_UPLOAD_TIME = 6e4;
5758
- var MAX_PROGRESS = 0.9;
5759
- var StepProgressBar = class {
5760
- spinner;
5761
- fileName;
5762
- startTime;
5763
- currentStep = 0;
5764
- stepStartTime = 0;
5765
- progressInterval = null;
5766
- isSimulatingProgress = false;
5767
- simulationStartTime = 0;
5768
- constructor(fileName, isDirectory = false) {
5769
- this.fileName = fileName;
5770
- this.spinner = (0, import_ora.default)(`Preparing to upload ${fileName}...`).start();
5771
- this.startTime = Date.now();
5772
- this.stepStartTime = Date.now();
5773
- this.startProgress();
5774
- }
5775
- startStep(stepIndex, stepName) {
5776
- this.currentStep = stepIndex;
5777
- this.stepStartTime = Date.now();
5778
- }
5779
- updateProgress(progress, total) {
5780
- }
5781
- completeStep() {
5749
+ // bin/utils/webLogin.ts
5750
+ var import_crypto2 = __toESM(require("crypto"));
5751
+ var import_http4 = __toESM(require("http"));
5752
+ var import_url2 = require("url");
5753
+ var import_chalk3 = __toESM(require("chalk"));
5754
+ var import_child_process = require("child_process");
5755
+ var import_fs_extra4 = __toESM(require("fs-extra"));
5756
+ var import_os4 = __toESM(require("os"));
5757
+ var import_path5 = __toESM(require("path"));
5758
+ function openBrowser(url2) {
5759
+ const platform = process.platform;
5760
+ let command;
5761
+ if (platform === "darwin") {
5762
+ command = `open "${url2}"`;
5763
+ } else if (platform === "win32") {
5764
+ command = `start "" "${url2}"`;
5765
+ } else {
5766
+ command = `xdg-open "${url2}"`;
5782
5767
  }
5783
- // Start simulating progress to continue display after 90%
5784
- startSimulatingProgress() {
5785
- this.isSimulatingProgress = true;
5786
- this.simulationStartTime = Date.now();
5768
+ (0, import_child_process.exec)(command, (err) => {
5769
+ if (err) {
5770
+ console.log(import_chalk3.default.yellow(`Unable to open browser automatically. Please visit manually: ${url2}`));
5771
+ }
5772
+ });
5773
+ }
5774
+ var CONFIG_DIR2 = import_path5.default.join(import_os4.default.homedir(), ".pinme");
5775
+ var AUTH_FILE2 = import_path5.default.join(CONFIG_DIR2, "auth.json");
5776
+ var DEFAULT_OPTIONS = {
5777
+ apiBaseUrl: "https://pinme.benny1996.win/api/v4",
5778
+ webBaseUrl: process.env.PINME_WEB_URL || "http://localhost:5173",
5779
+ callbackPort: 34567,
5780
+ callbackPath: "/cli/callback"
5781
+ };
5782
+ var WebLoginManager = class {
5783
+ config;
5784
+ server = null;
5785
+ resolvePromise = null;
5786
+ rejectPromise = null;
5787
+ loginToken = "";
5788
+ constructor(options = {}) {
5789
+ this.config = { ...DEFAULT_OPTIONS, ...options };
5787
5790
  }
5788
- // Stop simulating progress
5789
- stopSimulatingProgress() {
5790
- this.isSimulatingProgress = false;
5791
+ async login() {
5792
+ console.log(import_chalk3.default.blue("Starting login flow...\n"));
5793
+ this.loginToken = this.generateLoginToken();
5794
+ console.log(import_chalk3.default.blue("Starting local callback server..."));
5795
+ await this.startCallbackServer();
5796
+ try {
5797
+ const loginUrl = this.buildLoginUrl();
5798
+ console.log(import_chalk3.default.blue("Opening browser..."));
5799
+ console.log(import_chalk3.default.white("If browser does not open automatically, please visit manually:"));
5800
+ console.log(import_chalk3.default.cyan(` ${loginUrl}
5801
+ `));
5802
+ openBrowser(loginUrl);
5803
+ console.log(import_chalk3.default.yellow("Please complete login in browser..."));
5804
+ console.log(import_chalk3.default.gray("Browser will close automatically after successful login.\n"));
5805
+ const authToken = await this.waitForCallback();
5806
+ const authConfig = this.parseAuthToken(authToken);
5807
+ this.saveAuthConfig(authConfig);
5808
+ console.log(import_chalk3.default.green("\nLogin successful!"));
5809
+ if (authConfig.email) {
5810
+ console.log(import_chalk3.default.green(`Welcome, ${authConfig.email}`));
5811
+ }
5812
+ console.log(import_chalk3.default.gray(`Address: ${authConfig.address}`));
5813
+ return authConfig;
5814
+ } catch (error) {
5815
+ console.error(import_chalk3.default.red(`
5816
+ Login failed: ${error.message}`));
5817
+ throw error;
5818
+ } finally {
5819
+ this.closeServer();
5820
+ }
5791
5821
  }
5792
- failStep(error) {
5793
- this.stopProgress();
5794
- this.spinner.fail(`Upload failed: ${error}`);
5822
+ generateLoginToken() {
5823
+ return import_crypto2.default.randomBytes(32).toString("hex");
5795
5824
  }
5796
- complete() {
5797
- this.stopProgress();
5798
- const totalTime = Math.floor((Date.now() - this.startTime) / 1e3);
5799
- const progressBar = this.createProgressBar(1);
5800
- this.spinner.succeed(
5801
- `Upload completed ${progressBar} 100% (${totalTime}s)`
5802
- );
5825
+ async startCallbackServer() {
5826
+ return new Promise((resolve, reject) => {
5827
+ this.server = import_http4.default.createServer(async (req, res) => {
5828
+ try {
5829
+ const url2 = new import_url2.URL(req.url || "", `http://localhost:${this.config.callbackPort}`);
5830
+ if (url2.pathname === this.config.callbackPath) {
5831
+ const authToken = url2.searchParams.get("token");
5832
+ const error = url2.searchParams.get("error");
5833
+ const loginToken = url2.searchParams.get("login_token");
5834
+ if (error) {
5835
+ res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
5836
+ res.end(this.getErrorHtml(error));
5837
+ if (this.rejectPromise) {
5838
+ this.rejectPromise(new Error(error));
5839
+ }
5840
+ return;
5841
+ }
5842
+ if (!authToken) {
5843
+ res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
5844
+ res.end(this.getErrorHtml("Auth token not received"));
5845
+ if (this.rejectPromise) {
5846
+ this.rejectPromise(new Error("Auth token not received"));
5847
+ }
5848
+ return;
5849
+ }
5850
+ if (loginToken !== this.loginToken) {
5851
+ res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
5852
+ res.end(this.getErrorHtml("Login token invalid or expired"));
5853
+ if (this.rejectPromise) {
5854
+ this.rejectPromise(new Error("Login token invalid or expired"));
5855
+ }
5856
+ return;
5857
+ }
5858
+ res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
5859
+ res.end(this.getSuccessHtml());
5860
+ if (this.resolvePromise) {
5861
+ this.resolvePromise(authToken);
5862
+ }
5863
+ } else {
5864
+ res.writeHead(404, { "Content-Type": "text/plain" });
5865
+ res.end("Not Found");
5866
+ }
5867
+ } catch (err) {
5868
+ console.error("Callback error:", err);
5869
+ res.writeHead(500, { "Content-Type": "text/plain" });
5870
+ res.end("Internal Server Error");
5871
+ }
5872
+ });
5873
+ this.server.on("error", (err) => {
5874
+ reject(err);
5875
+ });
5876
+ this.server.listen(this.config.callbackPort, "127.0.0.1", () => {
5877
+ console.log(import_chalk3.default.gray(`Local server: http://localhost:${this.config.callbackPort}`));
5878
+ resolve();
5879
+ });
5880
+ setTimeout(() => {
5881
+ if (this.rejectPromise) {
5882
+ this.rejectPromise(new Error("Login timeout, please try again"));
5883
+ }
5884
+ this.closeServer();
5885
+ }, 10 * 60 * 1e3);
5886
+ });
5803
5887
  }
5804
- fail(error) {
5805
- this.stopProgress();
5806
- const totalTime = Math.floor((Date.now() - this.startTime) / 1e3);
5807
- this.spinner.fail(`Upload failed: ${error} (${totalTime}s)`);
5888
+ buildLoginUrl() {
5889
+ const callbackUrl = `http://localhost:${this.config.callbackPort}${this.config.callbackPath}`;
5890
+ const url2 = `${this.config.webBaseUrl}/#/cli-login?login_token=${this.loginToken}&callback_url=${encodeURIComponent(callbackUrl)}`;
5891
+ return url2;
5808
5892
  }
5809
- startProgress() {
5810
- this.progressInterval = setInterval(() => {
5811
- const elapsed = Date.now() - this.startTime;
5812
- let progress;
5813
- if (this.isSimulatingProgress) {
5814
- const simulationElapsed = Date.now() - this.simulationStartTime;
5815
- const simulationProgress = Math.min(simulationElapsed / 6e4, 1);
5816
- progress = 0.9 + simulationProgress * 0.09;
5817
- } else {
5818
- progress = this.calculateProgress(elapsed);
5819
- }
5820
- const duration = this.formatDuration(Math.floor(elapsed / 1e3));
5821
- const progressBar = this.createProgressBar(progress);
5822
- this.spinner.text = `Uploading ${this.fileName}... ${progressBar} ${Math.round(progress * 100)}% (${duration})`;
5823
- }, PROGRESS_UPDATE_INTERVAL);
5893
+ waitForCallback() {
5894
+ return new Promise((resolve, reject) => {
5895
+ this.resolvePromise = resolve;
5896
+ this.rejectPromise = reject;
5897
+ });
5824
5898
  }
5825
- stopProgress() {
5826
- if (this.progressInterval) {
5827
- clearInterval(this.progressInterval);
5828
- this.progressInterval = null;
5899
+ parseAuthToken(authToken) {
5900
+ const firstDash = authToken.indexOf("-");
5901
+ if (firstDash <= 0 || firstDash === authToken.length - 1) {
5902
+ throw new Error("Invalid token format");
5829
5903
  }
5904
+ const address = authToken.slice(0, firstDash).trim();
5905
+ const token = authToken.slice(firstDash + 1).trim();
5906
+ if (!address || !token) {
5907
+ throw new Error("Token parsing failed: address or token is empty");
5908
+ }
5909
+ return { address, token };
5830
5910
  }
5831
- calculateProgress(elapsed) {
5832
- return Math.min(
5833
- elapsed / EXPECTED_UPLOAD_TIME * MAX_PROGRESS,
5834
- MAX_PROGRESS
5835
- );
5836
- }
5837
- createProgressBar(progress, width = 20) {
5838
- const percentage = Math.min(progress, 1);
5839
- const filledWidth = Math.round(width * percentage);
5840
- const emptyWidth = width - filledWidth;
5841
- return `[${"\u2588".repeat(filledWidth)}${"\u2591".repeat(emptyWidth)}]`;
5911
+ saveAuthConfig(config) {
5912
+ import_fs_extra4.default.ensureDirSync(CONFIG_DIR2);
5913
+ import_fs_extra4.default.writeJsonSync(AUTH_FILE2, config, { spaces: 2 });
5914
+ import_fs_extra4.default.chmodSync(AUTH_FILE2, 384);
5842
5915
  }
5843
- formatDuration(seconds) {
5844
- if (seconds < 60) {
5845
- return `${seconds}s`;
5846
- } else if (seconds < 3600) {
5847
- const minutes = Math.floor(seconds / 60);
5848
- const remainingSeconds = seconds % 60;
5849
- return `${minutes}m ${remainingSeconds}s`;
5850
- } else {
5851
- const hours = Math.floor(seconds / 3600);
5852
- const minutes = Math.floor(seconds % 3600 / 60);
5853
- const remainingSeconds = seconds % 60;
5854
- return `${hours}h ${minutes}m ${remainingSeconds}s`;
5916
+ closeServer() {
5917
+ if (this.server) {
5918
+ this.server.close();
5919
+ this.server = null;
5855
5920
  }
5856
5921
  }
5857
- };
5858
- async function calculateMD5(filePath) {
5859
- return new Promise((resolve, reject) => {
5860
- const hash = crypto2.createHash("md5");
5861
- const stream4 = import_fs_extra4.default.createReadStream(filePath);
5862
- stream4.on("data", hash.update.bind(hash));
5863
- stream4.on("end", () => resolve(hash.digest("hex")));
5864
- stream4.on("error", reject);
5865
- });
5866
- }
5867
- async function compressDirectory(sourcePath) {
5868
- return new Promise((resolve, reject) => {
5869
- const tempDir = require("os").tmpdir();
5870
- if (!import_fs_extra4.default.existsSync(tempDir)) {
5871
- import_fs_extra4.default.mkdirSync(tempDir, { recursive: true });
5872
- }
5873
- const outputPath = import_path5.default.join(
5874
- tempDir,
5875
- `pinme_${import_path5.default.basename(sourcePath)}_${Date.now()}.zip`
5876
- );
5877
- const output = import_fs_extra4.default.createWriteStream(outputPath);
5878
- const zlib2 = require("zlib");
5879
- const gzip = zlib2.createGzip({ level: 9 });
5880
- output.on("close", () => resolve(outputPath));
5881
- gzip.on("error", reject);
5882
- gzip.pipe(output);
5883
- const stats = import_fs_extra4.default.statSync(sourcePath);
5884
- if (stats.isDirectory()) {
5885
- const archive = require("archiver");
5886
- const archiveStream = archive("zip", { zlib: { level: 9 } });
5887
- archiveStream.on("error", reject);
5888
- archiveStream.pipe(output);
5889
- archiveStream.directory(sourcePath, false);
5890
- archiveStream.finalize();
5891
- } else {
5892
- const fileStream = import_fs_extra4.default.createReadStream(sourcePath);
5893
- fileStream.pipe(gzip);
5894
- }
5895
- });
5896
- }
5897
- async function initChunkSession(filePath, deviceId, isDirectory = false) {
5898
- const stats = import_fs_extra4.default.statSync(filePath);
5899
- const fileName = import_path5.default.basename(filePath);
5900
- const fileSize = stats.size;
5901
- const md5 = await calculateMD5(filePath);
5902
- try {
5903
- const response = await axios_default.post(
5904
- `${IPFS_API_URL}/chunk/init`,
5905
- {
5906
- file_name: fileName,
5907
- file_size: fileSize,
5908
- md5,
5909
- is_directory: isDirectory,
5910
- uid: deviceId
5911
- },
5912
- {
5913
- timeout: TIMEOUT,
5914
- headers: { "Content-Type": "application/json" }
5915
- }
5916
- );
5917
- const { code, msg, data } = response.data;
5918
- if (code === 200 && data) {
5919
- return data;
5920
- }
5921
- throw new Error(`Session initialization failed: ${msg} (code: ${code})`);
5922
- } catch (error) {
5923
- if (axios_default.isAxiosError(error)) {
5924
- throw new Error(`Network error: ${error.message}`);
5925
- }
5926
- throw error;
5927
- }
5928
- }
5929
- async function uploadChunkWithAbort(sessionId, chunkIndex, chunkData, deviceId, signal, retryCount = 0) {
5930
- try {
5931
- if (signal.aborted) {
5932
- throw new Error("Request cancelled");
5933
- }
5934
- const form = new import_form_data2.default();
5935
- form.append("session_id", sessionId);
5936
- form.append("chunk_index", chunkIndex.toString());
5937
- form.append("uid", deviceId);
5938
- form.append("chunk", chunkData, {
5939
- filename: `chunk_${chunkIndex}`,
5940
- contentType: "application/octet-stream"
5941
- });
5942
- const response = await axios_default.post(
5943
- `${IPFS_API_URL}/chunk/upload`,
5944
- form,
5945
- {
5946
- headers: { ...form.getHeaders() },
5947
- timeout: TIMEOUT,
5948
- signal
5949
- }
5950
- );
5951
- const { code, msg, data } = response.data;
5952
- if (code === 200 && data) {
5953
- return data;
5954
- }
5955
- throw new Error(`Chunk upload failed: ${msg} (code: ${code})`);
5956
- } catch (error) {
5957
- if (error.name === "CanceledError" || signal.aborted) {
5958
- throw new Error("Request cancelled");
5959
- }
5960
- if (retryCount < MAX_RETRIES) {
5961
- await delayWithAbortCheck(RETRY_DELAY, signal);
5962
- return uploadChunkWithAbort(
5963
- sessionId,
5964
- chunkIndex,
5965
- chunkData,
5966
- deviceId,
5967
- signal,
5968
- retryCount + 1
5969
- );
5970
- }
5971
- throw new Error(
5972
- `Chunk ${chunkIndex + 1} upload failed after ${MAX_RETRIES} retries: ${error.message}`
5973
- );
5974
- }
5975
- }
5976
- async function delayWithAbortCheck(delay, signal) {
5977
- return new Promise((resolve, reject) => {
5978
- const timeoutId = setTimeout(() => {
5979
- if (signal.aborted) {
5980
- reject(new Error("Request cancelled"));
5981
- } else {
5982
- resolve();
5983
- }
5984
- }, delay);
5985
- if (signal.aborted) {
5986
- clearTimeout(timeoutId);
5987
- reject(new Error("Request cancelled"));
5988
- return;
5989
- }
5990
- const checkInterval = setInterval(() => {
5991
- if (signal.aborted) {
5992
- clearTimeout(timeoutId);
5993
- clearInterval(checkInterval);
5994
- reject(new Error("Request cancelled"));
5995
- }
5996
- }, 50);
5997
- });
5998
- }
5999
- async function uploadFileChunks(filePath, sessionId, totalChunks, chunkSize, deviceId, progressBar) {
6000
- const fileData = import_fs_extra4.default.readFileSync(filePath);
6001
- const abortController = new AbortController();
6002
- let completedCount = 0;
6003
- let hasFatalError = false;
6004
- let fatalError = null;
6005
- const uploadTasks = Array.from({ length: totalChunks }, (_, chunkIndex) => {
6006
- const start = chunkIndex * chunkSize;
6007
- const end = Math.min(start + chunkSize, fileData.length);
6008
- const chunkData = fileData.slice(start, end);
6009
- return async () => {
6010
- if (abortController.signal.aborted) return;
6011
- try {
6012
- await uploadChunkWithAbort(
6013
- sessionId,
6014
- chunkIndex,
6015
- chunkData,
6016
- deviceId,
6017
- abortController.signal
6018
- );
6019
- if (abortController.signal.aborted) return;
6020
- completedCount++;
6021
- progressBar.updateProgress(completedCount, totalChunks);
6022
- } catch (error) {
6023
- if (error.name === "AbortError" || abortController.signal.aborted) {
6024
- return;
6025
- }
6026
- hasFatalError = true;
6027
- fatalError = `Chunk ${chunkIndex + 1}/${totalChunks} upload failed: ${error.message}`;
6028
- abortController.abort();
6029
- throw new Error(fatalError);
6030
- }
6031
- };
6032
- });
6033
- try {
6034
- const results = await Promise.allSettled(uploadTasks.map((task) => task()));
6035
- const failedResults = results.filter(
6036
- (result) => result.status === "rejected"
6037
- );
6038
- if (failedResults.length > 0) {
6039
- const firstFailure = failedResults[0];
6040
- throw new Error(
6041
- firstFailure.reason.message || "Error occurred during upload"
6042
- );
6043
- }
6044
- if (hasFatalError) {
6045
- throw new Error(fatalError || "Unknown error occurred during upload");
6046
- }
6047
- } catch (error) {
6048
- throw fatalError ? new Error(fatalError) : error;
6049
- }
6050
- }
6051
- async function completeChunkUpload(sessionId, deviceId, importAsCar = false) {
6052
- var _a;
6053
- try {
6054
- const requestBody = { session_id: sessionId, uid: deviceId };
6055
- const projectName = (_a = process.env.PINME_PROJECT_NAME) == null ? void 0 : _a.trim();
6056
- if (importAsCar) {
6057
- requestBody.import_as_car = true;
6058
- }
6059
- if (projectName) {
6060
- requestBody.project_name = projectName;
6061
- }
6062
- console.log(
6063
- `[chunk/complete] payload=${JSON.stringify({
6064
- session_id: requestBody.session_id,
6065
- uid: requestBody.uid,
6066
- import_as_car: !!requestBody.import_as_car,
6067
- project_name: requestBody.project_name || ""
6068
- })}`
6069
- );
6070
- const response = await axios_default.post(
6071
- `${IPFS_API_URL}/chunk/complete`,
6072
- requestBody,
6073
- {
6074
- timeout: TIMEOUT,
6075
- headers: { "Content-Type": "application/json" }
6076
- }
6077
- );
6078
- const { code, msg, data } = response.data;
6079
- if (code === 200 && data) {
6080
- return data.trace_id;
6081
- }
6082
- throw new Error(`Complete upload failed: ${msg} (code: ${code})`);
6083
- } catch (error) {
6084
- if (axios_default.isAxiosError(error)) {
6085
- throw new Error(`Network error: ${error.message}`);
6086
- }
6087
- throw error;
6088
- }
6089
- }
6090
- async function getChunkStatus(sessionId, deviceId) {
6091
- try {
6092
- const response = await axios_default.get(
6093
- `${IPFS_API_URL}/up_status`,
6094
- {
6095
- params: { trace_id: sessionId, uid: deviceId },
6096
- timeout: TIMEOUT,
6097
- headers: { "Content-Type": "application/json" }
6098
- }
6099
- );
6100
- const { code, msg, data } = response.data;
6101
- if (code === 200) {
6102
- return data;
6103
- }
6104
- throw new Error(`Server returned error: ${msg} (code: ${code})`);
6105
- } catch (error) {
6106
- if (axios_default.isAxiosError(error)) {
6107
- throw new Error(`Network error: ${error.message}`);
5922
+ // HTML templates
5923
+ getSuccessHtml() {
5924
+ return `
5925
+ <!DOCTYPE html>
5926
+ <html>
5927
+ <head>
5928
+ <title>Login Success - PinMe</title>
5929
+ <style>
5930
+ * { margin: 0; padding: 0; box-sizing: border-box; }
5931
+ body {
5932
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
5933
+ display: flex;
5934
+ justify-content: center;
5935
+ align-items: center;
5936
+ min-height: 100vh;
5937
+ background: #000;
5938
+ overflow: hidden;
6108
5939
  }
6109
- throw error;
6110
- }
6111
- }
6112
- async function monitorChunkProgress(traceId, deviceId, progressBar) {
6113
- let consecutiveErrors = 0;
6114
- const startTime = Date.now();
6115
- if (progressBar) {
6116
- progressBar.startSimulatingProgress();
6117
- }
6118
- try {
6119
- while (Date.now() - startTime < MAX_POLL_TIME) {
6120
- try {
6121
- const status = await getChunkStatus(traceId, deviceId);
6122
- consecutiveErrors = 0;
6123
- if (status.is_ready && status.upload_rst.Hash) {
6124
- if (progressBar) {
6125
- progressBar.stopSimulatingProgress();
6126
- }
6127
- return {
6128
- hash: status.upload_rst.Hash,
6129
- shortUrl: status.upload_rst.ShortUrl
6130
- };
6131
- }
6132
- } catch (error) {
6133
- consecutiveErrors++;
6134
- if (consecutiveErrors > 10) {
6135
- throw new Error(`Polling failed: ${error.message}`);
6136
- }
6137
- }
6138
- await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL));
5940
+ .bg {
5941
+ position: fixed;
5942
+ top: 0;
5943
+ left: 0;
5944
+ width: 100%;
5945
+ height: 100%;
5946
+ background:
5947
+ radial-gradient(ellipse at 20% 80%, rgba(120, 0, 255, 0.3) 0%, transparent 50%),
5948
+ radial-gradient(ellipse at 80% 20%, rgba(0, 200, 255, 0.3) 0%, transparent 50%),
5949
+ radial-gradient(ellipse at 50% 50%, rgba(255, 0, 150, 0.15) 0%, transparent 60%);
5950
+ animation: bgPulse 6s ease-in-out infinite;
6139
5951
  }
6140
- const maxPollTimeMinutes = Math.floor(MAX_POLL_TIME / (60 * 1e3));
6141
- throw new Error(`Polling timeout after ${maxPollTimeMinutes} minutes`);
6142
- } finally {
6143
- if (progressBar) {
6144
- progressBar.stopSimulatingProgress();
5952
+ @keyframes bgPulse {
5953
+ 0%, 100% { opacity: 1; transform: scale(1); }
5954
+ 50% { opacity: 0.8; transform: scale(1.05); }
6145
5955
  }
6146
- }
6147
- }
6148
- async function uploadDirectoryInChunks(directoryPath, deviceId, importAsCar = false) {
6149
- const sizeCheck = checkDirectorySizeLimit(directoryPath);
6150
- if (sizeCheck.exceeds) {
6151
- throw new Error(
6152
- `Directory ${directoryPath} exceeds size limit ${formatSize(
6153
- sizeCheck.limit
6154
- )} (size: ${formatSize(sizeCheck.size)})`
6155
- );
6156
- }
6157
- const progressBar = new StepProgressBar(import_path5.default.basename(directoryPath), true);
6158
- try {
6159
- progressBar.startStep(0, "Preparing compression");
6160
- const compressedPath = await compressDirectory(directoryPath);
6161
- progressBar.completeStep();
6162
- progressBar.startStep(1, "Initializing session");
6163
- const sessionInfo = await initChunkSession(compressedPath, deviceId, true);
6164
- progressBar.completeStep();
6165
- progressBar.startStep(2, "Chunk upload");
6166
- await uploadFileChunks(
6167
- compressedPath,
6168
- sessionInfo.session_id,
6169
- sessionInfo.total_chunks,
6170
- sessionInfo.chunk_size,
6171
- deviceId,
6172
- progressBar
6173
- );
6174
- progressBar.completeStep();
6175
- progressBar.startStep(3, "Completing upload");
6176
- const traceId = await completeChunkUpload(sessionInfo.session_id, deviceId, importAsCar);
6177
- progressBar.completeStep();
6178
- progressBar.startStep(4, "Waiting for processing");
6179
- const result = await monitorChunkProgress(traceId, deviceId, progressBar);
6180
- progressBar.completeStep();
6181
- try {
6182
- import_fs_extra4.default.unlinkSync(compressedPath);
6183
- } catch (error) {
5956
+ .grid {
5957
+ position: fixed;
5958
+ top: 0;
5959
+ left: 0;
5960
+ width: 200%;
5961
+ height: 200%;
5962
+ background-image:
5963
+ linear-gradient(rgba(0, 200, 255, 0.03) 1px, transparent 1px),
5964
+ linear-gradient(90deg, rgba(0, 200, 255, 0.03) 1px, transparent 1px);
5965
+ background-size: 50px 50px;
5966
+ transform: perspective(500px) rotateX(60deg) translateY(-50%) translateZ(-200px);
5967
+ animation: gridMove 20s linear infinite;
6184
5968
  }
6185
- const uploadData = {
6186
- path: directoryPath,
6187
- filename: import_path5.default.basename(directoryPath),
6188
- contentHash: (result == null ? void 0 : result.hash) || "unknown",
6189
- size: sizeCheck.size,
6190
- fileCount: 0,
6191
- isDirectory: true,
6192
- shortUrl: (result == null ? void 0 : result.shortUrl) || null
6193
- };
6194
- saveUploadHistory(uploadData);
6195
- if (!(result == null ? void 0 : result.hash)) {
6196
- throw new Error("Server did not return valid hash value");
5969
+ @keyframes gridMove {
5970
+ 0% { transform: perspective(500px) rotateX(60deg) translateY(0) translateZ(-200px); }
5971
+ 100% { transform: perspective(500px) rotateX(60deg) translateY(50px) translateZ(-200px); }
6197
5972
  }
6198
- progressBar.complete();
6199
- return result;
6200
- } catch (error) {
6201
- progressBar.fail(error.message);
6202
- throw error;
6203
- }
6204
- }
6205
- async function uploadFileInChunks(filePath, deviceId, importAsCar = false) {
6206
- const sizeCheck = checkFileSizeLimit(filePath);
6207
- if (sizeCheck.exceeds) {
6208
- throw new Error(
6209
- `File ${filePath} exceeds size limit ${formatSize(
6210
- sizeCheck.limit
6211
- )} (size: ${formatSize(sizeCheck.size)})`
6212
- );
6213
- }
6214
- const fileName = import_path5.default.basename(filePath);
6215
- const progressBar = new StepProgressBar(fileName, false);
6216
- try {
6217
- progressBar.startStep(0, "Initializing session");
6218
- const sessionInfo = await initChunkSession(filePath, deviceId, false);
6219
- progressBar.completeStep();
6220
- progressBar.startStep(1, "Chunk upload");
6221
- await uploadFileChunks(
6222
- filePath,
6223
- sessionInfo.session_id,
6224
- sessionInfo.total_chunks,
6225
- sessionInfo.chunk_size,
6226
- deviceId,
6227
- progressBar
6228
- );
6229
- progressBar.completeStep();
6230
- progressBar.startStep(2, "Completing upload");
6231
- const traceId = await completeChunkUpload(sessionInfo.session_id, deviceId, importAsCar);
6232
- progressBar.completeStep();
6233
- progressBar.startStep(3, "Waiting for processing");
6234
- const result = await monitorChunkProgress(traceId, deviceId, progressBar);
6235
- progressBar.completeStep();
6236
- const uploadData = {
6237
- path: filePath,
6238
- filename: fileName,
6239
- contentHash: (result == null ? void 0 : result.hash) || "unknown",
6240
- previewHash: null,
6241
- size: sizeCheck.size,
6242
- fileCount: 1,
6243
- isDirectory: false,
6244
- shortUrl: (result == null ? void 0 : result.shortUrl) || null
6245
- };
6246
- saveUploadHistory(uploadData);
6247
- if (!(result == null ? void 0 : result.hash)) {
6248
- throw new Error("Server did not return valid hash value");
5973
+ .container {
5974
+ position: relative;
5975
+ z-index: 10;
5976
+ background: linear-gradient(135deg, rgba(20, 20, 40, 0.9) 0%, rgba(10, 10, 30, 0.95) 100%);
5977
+ padding: 3.5rem 4rem;
5978
+ border-radius: 32px;
5979
+ box-shadow:
5980
+ 0 0 60px rgba(0, 200, 255, 0.15),
5981
+ 0 25px 50px rgba(0, 0, 0, 0.5),
5982
+ inset 0 1px 0 rgba(255, 255, 255, 0.1),
5983
+ inset 0 -1px 0 rgba(0, 200, 255, 0.1);
5984
+ text-align: center;
5985
+ border: 1px solid rgba(0, 200, 255, 0.2);
5986
+ max-width: 440px;
5987
+ backdrop-filter: blur(30px);
6249
5988
  }
6250
- progressBar.complete();
6251
- return result;
6252
- } catch (error) {
6253
- progressBar.fail(error.message);
6254
- throw error;
6255
- }
6256
- }
6257
- async function uploadToIpfsSplit_default(filePath, importAsCar = false) {
6258
- const deviceId = getUid();
6259
- if (!deviceId) {
6260
- throw new Error("Device ID not found");
6261
- }
6262
- try {
6263
- const isDirectory = import_fs_extra4.default.statSync(filePath).isDirectory();
6264
- const result = isDirectory ? await uploadDirectoryInChunks(filePath, deviceId, importAsCar) : await uploadFileInChunks(filePath, deviceId, importAsCar);
6265
- if (result == null ? void 0 : result.hash) {
6266
- return {
6267
- contentHash: result.hash,
6268
- previewHash: null,
6269
- shortUrl: result.shortUrl
6270
- };
5989
+ .container::before {
5990
+ content: '';
5991
+ position: absolute;
5992
+ top: -1px;
5993
+ left: -1px;
5994
+ right: -1px;
5995
+ bottom: -1px;
5996
+ border-radius: 32px;
5997
+ background: linear-gradient(135deg, rgba(0, 200, 255, 0.5), rgba(255, 0, 150, 0.5), rgba(120, 0, 255, 0.5));
5998
+ z-index: -1;
5999
+ opacity: 0.5;
6000
+ animation: borderGlow 3s ease-in-out infinite;
6271
6001
  }
6272
- return null;
6273
- } catch (error) {
6274
- return null;
6275
- }
6276
- }
6277
-
6278
- // bin/upload.ts
6279
- var import_fs2 = __toESM(require("fs"));
6280
- var import_crypto_js = __toESM(require("crypto-js"));
6281
-
6282
- // bin/utils/pinmeApi.ts
6283
- var import_chalk4 = __toESM(require("chalk"));
6284
-
6285
- // bin/utils/webLogin.ts
6286
- var import_crypto2 = __toESM(require("crypto"));
6287
- var import_http4 = __toESM(require("http"));
6288
- var import_url2 = require("url");
6289
- var import_chalk3 = __toESM(require("chalk"));
6290
- var import_child_process = require("child_process");
6291
- var import_fs_extra5 = __toESM(require("fs-extra"));
6292
- var import_os4 = __toESM(require("os"));
6293
- var import_path6 = __toESM(require("path"));
6294
- function openBrowser(url2) {
6295
- const platform = process.platform;
6296
- let command;
6297
- if (platform === "darwin") {
6298
- command = `open "${url2}"`;
6299
- } else if (platform === "win32") {
6300
- command = `start "" "${url2}"`;
6301
- } else {
6302
- command = `xdg-open "${url2}"`;
6303
- }
6304
- (0, import_child_process.exec)(command, (err) => {
6305
- if (err) {
6306
- console.log(import_chalk3.default.yellow(`Unable to open browser automatically. Please visit manually: ${url2}`));
6002
+ @keyframes borderGlow {
6003
+ 0%, 100% { opacity: 0.3; }
6004
+ 50% { opacity: 0.7; }
6307
6005
  }
6308
- });
6309
- }
6310
- var CONFIG_DIR2 = import_path6.default.join(import_os4.default.homedir(), ".pinme");
6311
- var AUTH_FILE2 = import_path6.default.join(CONFIG_DIR2, "auth.json");
6312
- var DEFAULT_OPTIONS = {
6313
- apiBaseUrl: "https://pinme.benny1996.win/api/v4",
6314
- webBaseUrl: process.env.PINME_WEB_URL || "http://localhost:5173",
6315
- callbackPort: 34567,
6316
- callbackPath: "/cli/callback"
6317
- };
6318
- var WebLoginManager = class {
6319
- config;
6320
- server = null;
6321
- resolvePromise = null;
6322
- rejectPromise = null;
6323
- loginToken = "";
6324
- constructor(options = {}) {
6325
- this.config = { ...DEFAULT_OPTIONS, ...options };
6326
- }
6327
- async login() {
6328
- console.log(import_chalk3.default.blue("Starting login flow...\n"));
6329
- this.loginToken = this.generateLoginToken();
6330
- console.log(import_chalk3.default.blue("Starting local callback server..."));
6331
- await this.startCallbackServer();
6332
- try {
6333
- const loginUrl = this.buildLoginUrl();
6334
- console.log(import_chalk3.default.blue("Opening browser..."));
6335
- console.log(import_chalk3.default.white("If browser does not open automatically, please visit manually:"));
6336
- console.log(import_chalk3.default.cyan(` ${loginUrl}
6337
- `));
6338
- openBrowser(loginUrl);
6339
- console.log(import_chalk3.default.yellow("Please complete login in browser..."));
6340
- console.log(import_chalk3.default.gray("Browser will close automatically after successful login.\n"));
6341
- const authToken = await this.waitForCallback();
6342
- const authConfig = this.parseAuthToken(authToken);
6343
- this.saveAuthConfig(authConfig);
6344
- console.log(import_chalk3.default.green("\nLogin successful!"));
6345
- if (authConfig.email) {
6346
- console.log(import_chalk3.default.green(`Welcome, ${authConfig.email}`));
6347
- }
6348
- console.log(import_chalk3.default.gray(`Address: ${authConfig.address}`));
6349
- return authConfig;
6350
- } catch (error) {
6351
- console.error(import_chalk3.default.red(`
6352
- Login failed: ${error.message}`));
6353
- throw error;
6354
- } finally {
6355
- this.closeServer();
6006
+ .success-icon {
6007
+ font-size: 5rem;
6008
+ margin-bottom: 1.5rem;
6009
+ animation: bounceIn 0.8s cubic-bezier(0.68, -0.55, 0.265, 1.55);
6010
+ filter: drop-shadow(0 0 20px rgba(0, 200, 255, 0.5));
6356
6011
  }
6357
- }
6358
- generateLoginToken() {
6359
- return import_crypto2.default.randomBytes(32).toString("hex");
6360
- }
6361
- async startCallbackServer() {
6362
- return new Promise((resolve, reject) => {
6363
- this.server = import_http4.default.createServer(async (req, res) => {
6364
- try {
6365
- const url2 = new import_url2.URL(req.url || "", `http://localhost:${this.config.callbackPort}`);
6366
- if (url2.pathname === this.config.callbackPath) {
6367
- const authToken = url2.searchParams.get("token");
6368
- const error = url2.searchParams.get("error");
6369
- const loginToken = url2.searchParams.get("login_token");
6370
- if (error) {
6371
- res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
6372
- res.end(this.getErrorHtml(error));
6373
- if (this.rejectPromise) {
6374
- this.rejectPromise(new Error(error));
6375
- }
6376
- return;
6377
- }
6378
- if (!authToken) {
6379
- res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
6380
- res.end(this.getErrorHtml("Auth token not received"));
6381
- if (this.rejectPromise) {
6382
- this.rejectPromise(new Error("Auth token not received"));
6383
- }
6384
- return;
6385
- }
6386
- if (loginToken !== this.loginToken) {
6387
- res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
6388
- res.end(this.getErrorHtml("Login token invalid or expired"));
6389
- if (this.rejectPromise) {
6390
- this.rejectPromise(new Error("Login token invalid or expired"));
6391
- }
6392
- return;
6393
- }
6394
- res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
6395
- res.end(this.getSuccessHtml());
6396
- if (this.resolvePromise) {
6397
- this.resolvePromise(authToken);
6398
- }
6399
- } else {
6400
- res.writeHead(404, { "Content-Type": "text/plain" });
6401
- res.end("Not Found");
6402
- }
6403
- } catch (err) {
6404
- console.error("Callback error:", err);
6405
- res.writeHead(500, { "Content-Type": "text/plain" });
6406
- res.end("Internal Server Error");
6407
- }
6408
- });
6409
- this.server.on("error", (err) => {
6410
- reject(err);
6411
- });
6412
- this.server.listen(this.config.callbackPort, "127.0.0.1", () => {
6413
- console.log(import_chalk3.default.gray(`Local server: http://localhost:${this.config.callbackPort}`));
6414
- resolve();
6415
- });
6416
- setTimeout(() => {
6417
- if (this.rejectPromise) {
6418
- this.rejectPromise(new Error("Login timeout, please try again"));
6419
- }
6420
- this.closeServer();
6421
- }, 10 * 60 * 1e3);
6422
- });
6423
- }
6424
- buildLoginUrl() {
6425
- const callbackUrl = `http://localhost:${this.config.callbackPort}${this.config.callbackPath}`;
6426
- const url2 = `${this.config.webBaseUrl}/#/cli-login?login_token=${this.loginToken}&callback_url=${encodeURIComponent(callbackUrl)}`;
6427
- return url2;
6428
- }
6429
- waitForCallback() {
6430
- return new Promise((resolve, reject) => {
6431
- this.resolvePromise = resolve;
6432
- this.rejectPromise = reject;
6433
- });
6434
- }
6435
- parseAuthToken(authToken) {
6436
- const firstDash = authToken.indexOf("-");
6437
- if (firstDash <= 0 || firstDash === authToken.length - 1) {
6438
- throw new Error("Invalid token format");
6012
+ @keyframes bounceIn {
6013
+ 0% { transform: scale(0); opacity: 0; }
6014
+ 50% { transform: scale(1.2); }
6015
+ 100% { transform: scale(1); opacity: 1; }
6439
6016
  }
6440
- const address = authToken.slice(0, firstDash).trim();
6441
- const token = authToken.slice(firstDash + 1).trim();
6442
- if (!address || !token) {
6443
- throw new Error("Token parsing failed: address or token is empty");
6017
+ h1 {
6018
+ color: #fff;
6019
+ font-size: 2.2rem;
6020
+ font-weight: 700;
6021
+ margin: 0 0 0.75rem 0;
6022
+ background: linear-gradient(90deg, #fff, #00d4ff);
6023
+ -webkit-background-clip: text;
6024
+ -webkit-text-fill-color: transparent;
6025
+ background-clip: text;
6444
6026
  }
6445
- return { address, token };
6446
- }
6447
- saveAuthConfig(config) {
6448
- import_fs_extra5.default.ensureDirSync(CONFIG_DIR2);
6449
- import_fs_extra5.default.writeJsonSync(AUTH_FILE2, config, { spaces: 2 });
6450
- import_fs_extra5.default.chmodSync(AUTH_FILE2, 384);
6451
- }
6452
- closeServer() {
6453
- if (this.server) {
6454
- this.server.close();
6455
- this.server = null;
6027
+ p {
6028
+ color: rgba(255, 255, 255, 0.6);
6029
+ font-size: 1.1rem;
6030
+ margin: 0 0 2rem 0;
6031
+ line-height: 1.6;
6032
+ }
6033
+ .highlight { color: #00d4ff; font-weight: 600; }
6034
+ .sparkle {
6035
+ position: absolute;
6036
+ width: 4px;
6037
+ height: 4px;
6038
+ background: #00d4ff;
6039
+ border-radius: 50%;
6040
+ animation: sparkle 2s ease-in-out infinite;
6041
+ }
6042
+ .sparkle:nth-child(1) { top: 20%; left: 10%; animation-delay: 0s; }
6043
+ .sparkle:nth-child(2) { top: 30%; right: 15%; animation-delay: 0.5s; }
6044
+ .sparkle:nth-child(3) { bottom: 25%; left: 20%; animation-delay: 1s; }
6045
+ .sparkle:nth-child(4) { bottom: 35%; right: 10%; animation-delay: 1.5s; }
6046
+ @keyframes sparkle {
6047
+ 0%, 100% { opacity: 0; transform: scale(0); }
6048
+ 50% { opacity: 1; transform: scale(1); }
6456
6049
  }
6050
+ </style>
6051
+ </head>
6052
+ <body>
6053
+ <div class="bg"></div>
6054
+ <div class="grid"></div>
6055
+ <div class="container">
6056
+ <div class="sparkle"></div>
6057
+ <div class="sparkle"></div>
6058
+ <div class="sparkle"></div>
6059
+ <div class="sparkle"></div>
6060
+ <div class="success-icon">\u{1F389}</div>
6061
+ <h1>Welcome to PinMe</h1>
6062
+ <p>You are now logged in! <span class="highlight">\u{1F680}</span><br>Return to your terminal to continue.</p>
6063
+ </div>
6064
+ </body>
6065
+ </html>`;
6457
6066
  }
6458
- // HTML templates
6459
- getSuccessHtml() {
6067
+ getErrorHtml(error) {
6068
+ const encodedError = encodeURIComponent(error);
6460
6069
  return `
6461
6070
  <!DOCTYPE html>
6462
6071
  <html>
6463
6072
  <head>
6464
- <title>Login Success - PinMe</title>
6073
+ <title>Login Failed - PinMe</title>
6465
6074
  <style>
6466
6075
  * { margin: 0; padding: 0; box-sizing: border-box; }
6467
6076
  body {
@@ -6480,9 +6089,9 @@ Login failed: ${error.message}`));
6480
6089
  width: 100%;
6481
6090
  height: 100%;
6482
6091
  background:
6483
- radial-gradient(ellipse at 20% 80%, rgba(120, 0, 255, 0.3) 0%, transparent 50%),
6484
- radial-gradient(ellipse at 80% 20%, rgba(0, 200, 255, 0.3) 0%, transparent 50%),
6485
- radial-gradient(ellipse at 50% 50%, rgba(255, 0, 150, 0.15) 0%, transparent 60%);
6092
+ radial-gradient(ellipse at 20% 80%, rgba(255, 50, 50, 0.2) 0%, transparent 50%),
6093
+ radial-gradient(ellipse at 80% 20%, rgba(255, 100, 50, 0.2) 0%, transparent 50%),
6094
+ radial-gradient(ellipse at 50% 50%, rgba(100, 0, 50, 0.15) 0%, transparent 60%);
6486
6095
  animation: bgPulse 6s ease-in-out infinite;
6487
6096
  }
6488
6097
  @keyframes bgPulse {
@@ -6496,8 +6105,8 @@ Login failed: ${error.message}`));
6496
6105
  width: 200%;
6497
6106
  height: 200%;
6498
6107
  background-image:
6499
- linear-gradient(rgba(0, 200, 255, 0.03) 1px, transparent 1px),
6500
- linear-gradient(90deg, rgba(0, 200, 255, 0.03) 1px, transparent 1px);
6108
+ linear-gradient(rgba(255, 80, 80, 0.03) 1px, transparent 1px),
6109
+ linear-gradient(90deg, rgba(255, 80, 80, 0.03) 1px, transparent 1px);
6501
6110
  background-size: 50px 50px;
6502
6111
  transform: perspective(500px) rotateX(60deg) translateY(-50%) translateZ(-200px);
6503
6112
  animation: gridMove 20s linear infinite;
@@ -6509,16 +6118,16 @@ Login failed: ${error.message}`));
6509
6118
  .container {
6510
6119
  position: relative;
6511
6120
  z-index: 10;
6512
- background: linear-gradient(135deg, rgba(20, 20, 40, 0.9) 0%, rgba(10, 10, 30, 0.95) 100%);
6121
+ background: linear-gradient(135deg, rgba(40, 20, 20, 0.9) 0%, rgba(30, 10, 10, 0.95) 100%);
6513
6122
  padding: 3.5rem 4rem;
6514
6123
  border-radius: 32px;
6515
6124
  box-shadow:
6516
- 0 0 60px rgba(0, 200, 255, 0.15),
6125
+ 0 0 60px rgba(255, 50, 50, 0.15),
6517
6126
  0 25px 50px rgba(0, 0, 0, 0.5),
6518
6127
  inset 0 1px 0 rgba(255, 255, 255, 0.1),
6519
- inset 0 -1px 0 rgba(0, 200, 255, 0.1);
6128
+ inset 0 -1px 0 rgba(255, 50, 50, 0.1);
6520
6129
  text-align: center;
6521
- border: 1px solid rgba(0, 200, 255, 0.2);
6130
+ border: 1px solid rgba(255, 50, 50, 0.2);
6522
6131
  max-width: 440px;
6523
6132
  backdrop-filter: blur(30px);
6524
6133
  }
@@ -6530,7 +6139,7 @@ Login failed: ${error.message}`));
6530
6139
  right: -1px;
6531
6140
  bottom: -1px;
6532
6141
  border-radius: 32px;
6533
- background: linear-gradient(135deg, rgba(0, 200, 255, 0.5), rgba(255, 0, 150, 0.5), rgba(120, 0, 255, 0.5));
6142
+ background: linear-gradient(135deg, rgba(255, 50, 50, 0.5), rgba(255, 150, 50, 0.5), rgba(150, 0, 50, 0.5));
6534
6143
  z-index: -1;
6535
6144
  opacity: 0.5;
6536
6145
  animation: borderGlow 3s ease-in-out infinite;
@@ -6539,246 +6148,640 @@ Login failed: ${error.message}`));
6539
6148
  0%, 100% { opacity: 0.3; }
6540
6149
  50% { opacity: 0.7; }
6541
6150
  }
6542
- .success-icon {
6151
+ .error-icon {
6543
6152
  font-size: 5rem;
6544
6153
  margin-bottom: 1.5rem;
6545
- animation: bounceIn 0.8s cubic-bezier(0.68, -0.55, 0.265, 1.55);
6546
- filter: drop-shadow(0 0 20px rgba(0, 200, 255, 0.5));
6154
+ animation: shake 0.5s ease-in-out;
6155
+ filter: drop-shadow(0 0 20px rgba(255, 50, 50, 0.5));
6547
6156
  }
6548
- @keyframes bounceIn {
6549
- 0% { transform: scale(0); opacity: 0; }
6550
- 50% { transform: scale(1.2); }
6551
- 100% { transform: scale(1); opacity: 1; }
6157
+ @keyframes shake {
6158
+ 0%, 100% { transform: translateX(0); }
6159
+ 20% { transform: translateX(-10px) rotate(-5deg); }
6160
+ 40% { transform: translateX(10px) rotate(5deg); }
6161
+ 60% { transform: translateX(-10px) rotate(-5deg); }
6162
+ 80% { transform: translateX(10px) rotate(5deg); }
6552
6163
  }
6553
6164
  h1 {
6554
6165
  color: #fff;
6555
6166
  font-size: 2.2rem;
6556
6167
  font-weight: 700;
6557
6168
  margin: 0 0 0.75rem 0;
6558
- background: linear-gradient(90deg, #fff, #00d4ff);
6169
+ background: linear-gradient(90deg, #fff, #ff5050);
6559
6170
  -webkit-background-clip: text;
6560
6171
  -webkit-text-fill-color: transparent;
6561
6172
  background-clip: text;
6562
6173
  }
6563
- p {
6564
- color: rgba(255, 255, 255, 0.6);
6565
- font-size: 1.1rem;
6174
+ .error {
6175
+ color: #ff6b6b;
6176
+ font-size: 1rem;
6566
6177
  margin: 0 0 2rem 0;
6567
- line-height: 1.6;
6178
+ padding: 1.25rem;
6179
+ background: rgba(255, 50, 50, 0.1);
6180
+ border-radius: 16px;
6181
+ border: 1px solid rgba(255, 50, 50, 0.2);
6182
+ font-weight: 500;
6183
+ box-shadow: 0 0 20px rgba(255, 50, 50, 0.1);
6568
6184
  }
6569
- .highlight { color: #00d4ff; font-weight: 600; }
6570
- .sparkle {
6571
- position: absolute;
6572
- width: 4px;
6573
- height: 4px;
6574
- background: #00d4ff;
6575
- border-radius: 50%;
6576
- animation: sparkle 2s ease-in-out infinite;
6185
+ </style>
6186
+ </head>
6187
+ <body>
6188
+ <div class="bg"></div>
6189
+ <div class="grid"></div>
6190
+ <div class="container">
6191
+ <div class="error-icon">\u{1F635}</div>
6192
+ <h1>Oops!</h1>
6193
+ <div class="error">${error}</div>
6194
+ </div>
6195
+ </body>
6196
+ </html>`;
6197
+ }
6198
+ };
6199
+ var webLoginManager = new WebLoginManager();
6200
+ function setAuthToken(combined) {
6201
+ const firstDash = combined.indexOf("-");
6202
+ if (firstDash <= 0 || firstDash === combined.length - 1) {
6203
+ throw new Error('Invalid token format. Expected "<address>-<jwt>".');
6204
+ }
6205
+ const address = combined.slice(0, firstDash).trim();
6206
+ const token = combined.slice(firstDash + 1).trim();
6207
+ if (!address || !token) {
6208
+ throw new Error("Invalid token content. Address or token is empty.");
6209
+ }
6210
+ const config = { address, token };
6211
+ import_fs_extra4.default.ensureDirSync(CONFIG_DIR2);
6212
+ import_fs_extra4.default.writeJsonSync(AUTH_FILE2, config, { spaces: 2 });
6213
+ return config;
6214
+ }
6215
+ function getAuthConfig2() {
6216
+ try {
6217
+ if (!import_fs_extra4.default.existsSync(AUTH_FILE2)) return null;
6218
+ const data = import_fs_extra4.default.readJsonSync(AUTH_FILE2);
6219
+ if (!(data == null ? void 0 : data.address) || !(data == null ? void 0 : data.token)) return null;
6220
+ return data;
6221
+ } catch {
6222
+ return null;
6223
+ }
6224
+ }
6225
+ function clearAuthToken() {
6226
+ try {
6227
+ if (import_fs_extra4.default.existsSync(AUTH_FILE2)) {
6228
+ import_fs_extra4.default.removeSync(AUTH_FILE2);
6229
+ }
6230
+ } catch (error) {
6231
+ console.error(`Failed to clear auth token: ${error}`);
6232
+ }
6233
+ }
6234
+ function getAuthHeaders() {
6235
+ const conf = getAuthConfig2();
6236
+ if (!conf) {
6237
+ throw new Error("Auth not set. Run: pinme login");
6238
+ }
6239
+ return {
6240
+ "token-address": conf.address,
6241
+ "authentication-tokens": conf.token
6242
+ };
6243
+ }
6244
+
6245
+ // bin/utils/uploadToIpfsSplit.ts
6246
+ var IPFS_API_URL = "https://pinme.benny1996.win/api/v3";
6247
+ var MAX_RETRIES = parseInt(process.env.MAX_RETRIES || "2");
6248
+ var RETRY_DELAY = parseInt(process.env.RETRY_DELAY_MS || "1000");
6249
+ var TIMEOUT = parseInt(process.env.TIMEOUT_MS || "600000");
6250
+ var MAX_POLL_TIME = parseInt("5") * 60 * 1e3;
6251
+ var POLL_INTERVAL = parseInt(process.env.POLL_INTERVAL_SECONDS || "2") * 1e3;
6252
+ var PROGRESS_UPDATE_INTERVAL = 200;
6253
+ var EXPECTED_UPLOAD_TIME = 6e4;
6254
+ var MAX_PROGRESS = 0.9;
6255
+ var StepProgressBar = class {
6256
+ spinner;
6257
+ fileName;
6258
+ startTime;
6259
+ currentStep = 0;
6260
+ stepStartTime = 0;
6261
+ progressInterval = null;
6262
+ isSimulatingProgress = false;
6263
+ simulationStartTime = 0;
6264
+ constructor(fileName, isDirectory = false) {
6265
+ this.fileName = fileName;
6266
+ this.spinner = (0, import_ora.default)(`Preparing to upload ${fileName}...`).start();
6267
+ this.startTime = Date.now();
6268
+ this.stepStartTime = Date.now();
6269
+ this.startProgress();
6270
+ }
6271
+ startStep(stepIndex, stepName) {
6272
+ this.currentStep = stepIndex;
6273
+ this.stepStartTime = Date.now();
6274
+ }
6275
+ updateProgress(progress, total) {
6276
+ }
6277
+ completeStep() {
6278
+ }
6279
+ // Start simulating progress to continue display after 90%
6280
+ startSimulatingProgress() {
6281
+ this.isSimulatingProgress = true;
6282
+ this.simulationStartTime = Date.now();
6283
+ }
6284
+ // Stop simulating progress
6285
+ stopSimulatingProgress() {
6286
+ this.isSimulatingProgress = false;
6287
+ }
6288
+ failStep(error) {
6289
+ this.stopProgress();
6290
+ this.spinner.fail(`Upload failed: ${error}`);
6291
+ }
6292
+ complete() {
6293
+ this.stopProgress();
6294
+ const totalTime = Math.floor((Date.now() - this.startTime) / 1e3);
6295
+ const progressBar = this.createProgressBar(1);
6296
+ this.spinner.succeed(
6297
+ `Upload completed ${progressBar} 100% (${totalTime}s)`
6298
+ );
6299
+ }
6300
+ fail(error) {
6301
+ this.stopProgress();
6302
+ const totalTime = Math.floor((Date.now() - this.startTime) / 1e3);
6303
+ this.spinner.fail(`Upload failed: ${error} (${totalTime}s)`);
6304
+ }
6305
+ startProgress() {
6306
+ this.progressInterval = setInterval(() => {
6307
+ const elapsed = Date.now() - this.startTime;
6308
+ let progress;
6309
+ if (this.isSimulatingProgress) {
6310
+ const simulationElapsed = Date.now() - this.simulationStartTime;
6311
+ const simulationProgress = Math.min(simulationElapsed / 6e4, 1);
6312
+ progress = 0.9 + simulationProgress * 0.09;
6313
+ } else {
6314
+ progress = this.calculateProgress(elapsed);
6315
+ }
6316
+ const duration = this.formatDuration(Math.floor(elapsed / 1e3));
6317
+ const progressBar = this.createProgressBar(progress);
6318
+ this.spinner.text = `Uploading ${this.fileName}... ${progressBar} ${Math.round(progress * 100)}% (${duration})`;
6319
+ }, PROGRESS_UPDATE_INTERVAL);
6320
+ }
6321
+ stopProgress() {
6322
+ if (this.progressInterval) {
6323
+ clearInterval(this.progressInterval);
6324
+ this.progressInterval = null;
6325
+ }
6326
+ }
6327
+ calculateProgress(elapsed) {
6328
+ return Math.min(
6329
+ elapsed / EXPECTED_UPLOAD_TIME * MAX_PROGRESS,
6330
+ MAX_PROGRESS
6331
+ );
6332
+ }
6333
+ createProgressBar(progress, width = 20) {
6334
+ const percentage = Math.min(progress, 1);
6335
+ const filledWidth = Math.round(width * percentage);
6336
+ const emptyWidth = width - filledWidth;
6337
+ return `[${"\u2588".repeat(filledWidth)}${"\u2591".repeat(emptyWidth)}]`;
6338
+ }
6339
+ formatDuration(seconds) {
6340
+ if (seconds < 60) {
6341
+ return `${seconds}s`;
6342
+ } else if (seconds < 3600) {
6343
+ const minutes = Math.floor(seconds / 60);
6344
+ const remainingSeconds = seconds % 60;
6345
+ return `${minutes}m ${remainingSeconds}s`;
6346
+ } else {
6347
+ const hours = Math.floor(seconds / 3600);
6348
+ const minutes = Math.floor(seconds % 3600 / 60);
6349
+ const remainingSeconds = seconds % 60;
6350
+ return `${hours}h ${minutes}m ${remainingSeconds}s`;
6351
+ }
6352
+ }
6353
+ };
6354
+ async function calculateMD5(filePath) {
6355
+ return new Promise((resolve, reject) => {
6356
+ const hash = crypto3.createHash("md5");
6357
+ const stream4 = import_fs_extra5.default.createReadStream(filePath);
6358
+ stream4.on("data", hash.update.bind(hash));
6359
+ stream4.on("end", () => resolve(hash.digest("hex")));
6360
+ stream4.on("error", reject);
6361
+ });
6362
+ }
6363
+ async function compressDirectory(sourcePath) {
6364
+ return new Promise((resolve, reject) => {
6365
+ const tempDir = require("os").tmpdir();
6366
+ if (!import_fs_extra5.default.existsSync(tempDir)) {
6367
+ import_fs_extra5.default.mkdirSync(tempDir, { recursive: true });
6368
+ }
6369
+ const outputPath = import_path6.default.join(
6370
+ tempDir,
6371
+ `pinme_${import_path6.default.basename(sourcePath)}_${Date.now()}.zip`
6372
+ );
6373
+ const output = import_fs_extra5.default.createWriteStream(outputPath);
6374
+ const zlib2 = require("zlib");
6375
+ const gzip = zlib2.createGzip({ level: 9 });
6376
+ output.on("close", () => resolve(outputPath));
6377
+ gzip.on("error", reject);
6378
+ gzip.pipe(output);
6379
+ const stats = import_fs_extra5.default.statSync(sourcePath);
6380
+ if (stats.isDirectory()) {
6381
+ const archive = require("archiver");
6382
+ const archiveStream = archive("zip", { zlib: { level: 9 } });
6383
+ archiveStream.on("error", reject);
6384
+ archiveStream.pipe(output);
6385
+ archiveStream.directory(sourcePath, false);
6386
+ archiveStream.finalize();
6387
+ } else {
6388
+ const fileStream = import_fs_extra5.default.createReadStream(sourcePath);
6389
+ fileStream.pipe(gzip);
6390
+ }
6391
+ });
6392
+ }
6393
+ async function initChunkSession(filePath, deviceId, isDirectory = false) {
6394
+ const stats = import_fs_extra5.default.statSync(filePath);
6395
+ const fileName = import_path6.default.basename(filePath);
6396
+ const fileSize = stats.size;
6397
+ const md5 = await calculateMD5(filePath);
6398
+ try {
6399
+ const response = await axios_default.post(
6400
+ `${IPFS_API_URL}/chunk/init`,
6401
+ {
6402
+ file_name: fileName,
6403
+ file_size: fileSize,
6404
+ md5,
6405
+ is_directory: isDirectory,
6406
+ uid: deviceId
6407
+ },
6408
+ {
6409
+ timeout: TIMEOUT,
6410
+ headers: { "Content-Type": "application/json" }
6411
+ }
6412
+ );
6413
+ const { code, msg, data } = response.data;
6414
+ if (code === 200 && data) {
6415
+ return data;
6577
6416
  }
6578
- .sparkle:nth-child(1) { top: 20%; left: 10%; animation-delay: 0s; }
6579
- .sparkle:nth-child(2) { top: 30%; right: 15%; animation-delay: 0.5s; }
6580
- .sparkle:nth-child(3) { bottom: 25%; left: 20%; animation-delay: 1s; }
6581
- .sparkle:nth-child(4) { bottom: 35%; right: 10%; animation-delay: 1.5s; }
6582
- @keyframes sparkle {
6583
- 0%, 100% { opacity: 0; transform: scale(0); }
6584
- 50% { opacity: 1; transform: scale(1); }
6417
+ throw new Error(`Session initialization failed: ${msg} (code: ${code})`);
6418
+ } catch (error) {
6419
+ if (axios_default.isAxiosError(error)) {
6420
+ throw new Error(`Network error: ${error.message}`);
6585
6421
  }
6586
- </style>
6587
- </head>
6588
- <body>
6589
- <div class="bg"></div>
6590
- <div class="grid"></div>
6591
- <div class="container">
6592
- <div class="sparkle"></div>
6593
- <div class="sparkle"></div>
6594
- <div class="sparkle"></div>
6595
- <div class="sparkle"></div>
6596
- <div class="success-icon">\u{1F389}</div>
6597
- <h1>Welcome to PinMe</h1>
6598
- <p>You are now logged in! <span class="highlight">\u{1F680}</span><br>Return to your terminal to continue.</p>
6599
- </div>
6600
- </body>
6601
- </html>`;
6422
+ throw error;
6602
6423
  }
6603
- getErrorHtml(error) {
6604
- const encodedError = encodeURIComponent(error);
6605
- return `
6606
- <!DOCTYPE html>
6607
- <html>
6608
- <head>
6609
- <title>Login Failed - PinMe</title>
6610
- <style>
6611
- * { margin: 0; padding: 0; box-sizing: border-box; }
6612
- body {
6613
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
6614
- display: flex;
6615
- justify-content: center;
6616
- align-items: center;
6617
- min-height: 100vh;
6618
- background: #000;
6619
- overflow: hidden;
6424
+ }
6425
+ async function uploadChunkWithAbort(sessionId, chunkIndex, chunkData, deviceId, signal, retryCount = 0) {
6426
+ try {
6427
+ if (signal.aborted) {
6428
+ throw new Error("Request cancelled");
6620
6429
  }
6621
- .bg {
6622
- position: fixed;
6623
- top: 0;
6624
- left: 0;
6625
- width: 100%;
6626
- height: 100%;
6627
- background:
6628
- radial-gradient(ellipse at 20% 80%, rgba(255, 50, 50, 0.2) 0%, transparent 50%),
6629
- radial-gradient(ellipse at 80% 20%, rgba(255, 100, 50, 0.2) 0%, transparent 50%),
6630
- radial-gradient(ellipse at 50% 50%, rgba(100, 0, 50, 0.15) 0%, transparent 60%);
6631
- animation: bgPulse 6s ease-in-out infinite;
6430
+ const form = new import_form_data2.default();
6431
+ form.append("session_id", sessionId);
6432
+ form.append("chunk_index", chunkIndex.toString());
6433
+ form.append("uid", deviceId);
6434
+ form.append("chunk", chunkData, {
6435
+ filename: `chunk_${chunkIndex}`,
6436
+ contentType: "application/octet-stream"
6437
+ });
6438
+ const response = await axios_default.post(
6439
+ `${IPFS_API_URL}/chunk/upload`,
6440
+ form,
6441
+ {
6442
+ headers: { ...form.getHeaders() },
6443
+ timeout: TIMEOUT,
6444
+ signal
6445
+ }
6446
+ );
6447
+ const { code, msg, data } = response.data;
6448
+ if (code === 200 && data) {
6449
+ return data;
6632
6450
  }
6633
- @keyframes bgPulse {
6634
- 0%, 100% { opacity: 1; transform: scale(1); }
6635
- 50% { opacity: 0.8; transform: scale(1.05); }
6451
+ throw new Error(`Chunk upload failed: ${msg} (code: ${code})`);
6452
+ } catch (error) {
6453
+ if (error.name === "CanceledError" || signal.aborted) {
6454
+ throw new Error("Request cancelled");
6636
6455
  }
6637
- .grid {
6638
- position: fixed;
6639
- top: 0;
6640
- left: 0;
6641
- width: 200%;
6642
- height: 200%;
6643
- background-image:
6644
- linear-gradient(rgba(255, 80, 80, 0.03) 1px, transparent 1px),
6645
- linear-gradient(90deg, rgba(255, 80, 80, 0.03) 1px, transparent 1px);
6646
- background-size: 50px 50px;
6647
- transform: perspective(500px) rotateX(60deg) translateY(-50%) translateZ(-200px);
6648
- animation: gridMove 20s linear infinite;
6456
+ if (retryCount < MAX_RETRIES) {
6457
+ await delayWithAbortCheck(RETRY_DELAY, signal);
6458
+ return uploadChunkWithAbort(
6459
+ sessionId,
6460
+ chunkIndex,
6461
+ chunkData,
6462
+ deviceId,
6463
+ signal,
6464
+ retryCount + 1
6465
+ );
6649
6466
  }
6650
- @keyframes gridMove {
6651
- 0% { transform: perspective(500px) rotateX(60deg) translateY(0) translateZ(-200px); }
6652
- 100% { transform: perspective(500px) rotateX(60deg) translateY(50px) translateZ(-200px); }
6467
+ throw new Error(
6468
+ `Chunk ${chunkIndex + 1} upload failed after ${MAX_RETRIES} retries: ${error.message}`
6469
+ );
6470
+ }
6471
+ }
6472
+ async function delayWithAbortCheck(delay, signal) {
6473
+ return new Promise((resolve, reject) => {
6474
+ const timeoutId = setTimeout(() => {
6475
+ if (signal.aborted) {
6476
+ reject(new Error("Request cancelled"));
6477
+ } else {
6478
+ resolve();
6479
+ }
6480
+ }, delay);
6481
+ if (signal.aborted) {
6482
+ clearTimeout(timeoutId);
6483
+ reject(new Error("Request cancelled"));
6484
+ return;
6653
6485
  }
6654
- .container {
6655
- position: relative;
6656
- z-index: 10;
6657
- background: linear-gradient(135deg, rgba(40, 20, 20, 0.9) 0%, rgba(30, 10, 10, 0.95) 100%);
6658
- padding: 3.5rem 4rem;
6659
- border-radius: 32px;
6660
- box-shadow:
6661
- 0 0 60px rgba(255, 50, 50, 0.15),
6662
- 0 25px 50px rgba(0, 0, 0, 0.5),
6663
- inset 0 1px 0 rgba(255, 255, 255, 0.1),
6664
- inset 0 -1px 0 rgba(255, 50, 50, 0.1);
6665
- text-align: center;
6666
- border: 1px solid rgba(255, 50, 50, 0.2);
6667
- max-width: 440px;
6668
- backdrop-filter: blur(30px);
6486
+ const checkInterval = setInterval(() => {
6487
+ if (signal.aborted) {
6488
+ clearTimeout(timeoutId);
6489
+ clearInterval(checkInterval);
6490
+ reject(new Error("Request cancelled"));
6491
+ }
6492
+ }, 50);
6493
+ });
6494
+ }
6495
+ async function uploadFileChunks(filePath, sessionId, totalChunks, chunkSize, deviceId, progressBar) {
6496
+ const fileData = import_fs_extra5.default.readFileSync(filePath);
6497
+ const abortController = new AbortController();
6498
+ let completedCount = 0;
6499
+ let hasFatalError = false;
6500
+ let fatalError = null;
6501
+ const uploadTasks = Array.from({ length: totalChunks }, (_, chunkIndex) => {
6502
+ const start = chunkIndex * chunkSize;
6503
+ const end = Math.min(start + chunkSize, fileData.length);
6504
+ const chunkData = fileData.slice(start, end);
6505
+ return async () => {
6506
+ if (abortController.signal.aborted) return;
6507
+ try {
6508
+ await uploadChunkWithAbort(
6509
+ sessionId,
6510
+ chunkIndex,
6511
+ chunkData,
6512
+ deviceId,
6513
+ abortController.signal
6514
+ );
6515
+ if (abortController.signal.aborted) return;
6516
+ completedCount++;
6517
+ progressBar.updateProgress(completedCount, totalChunks);
6518
+ } catch (error) {
6519
+ if (error.name === "AbortError" || abortController.signal.aborted) {
6520
+ return;
6521
+ }
6522
+ hasFatalError = true;
6523
+ fatalError = `Chunk ${chunkIndex + 1}/${totalChunks} upload failed: ${error.message}`;
6524
+ abortController.abort();
6525
+ throw new Error(fatalError);
6526
+ }
6527
+ };
6528
+ });
6529
+ try {
6530
+ const results = await Promise.allSettled(uploadTasks.map((task) => task()));
6531
+ const failedResults = results.filter(
6532
+ (result) => result.status === "rejected"
6533
+ );
6534
+ if (failedResults.length > 0) {
6535
+ const firstFailure = failedResults[0];
6536
+ throw new Error(
6537
+ firstFailure.reason.message || "Error occurred during upload"
6538
+ );
6669
6539
  }
6670
- .container::before {
6671
- content: '';
6672
- position: absolute;
6673
- top: -1px;
6674
- left: -1px;
6675
- right: -1px;
6676
- bottom: -1px;
6677
- border-radius: 32px;
6678
- background: linear-gradient(135deg, rgba(255, 50, 50, 0.5), rgba(255, 150, 50, 0.5), rgba(150, 0, 50, 0.5));
6679
- z-index: -1;
6680
- opacity: 0.5;
6681
- animation: borderGlow 3s ease-in-out infinite;
6540
+ if (hasFatalError) {
6541
+ throw new Error(fatalError || "Unknown error occurred during upload");
6682
6542
  }
6683
- @keyframes borderGlow {
6684
- 0%, 100% { opacity: 0.3; }
6685
- 50% { opacity: 0.7; }
6543
+ } catch (error) {
6544
+ throw fatalError ? new Error(fatalError) : error;
6545
+ }
6546
+ }
6547
+ async function completeChunkUpload(sessionId, deviceId, importAsCar = false) {
6548
+ var _a;
6549
+ try {
6550
+ const requestBody = { session_id: sessionId, uid: deviceId };
6551
+ const projectName = (_a = process.env.PINME_PROJECT_NAME) == null ? void 0 : _a.trim();
6552
+ let authHeaders = {};
6553
+ if (importAsCar) {
6554
+ requestBody.import_as_car = true;
6686
6555
  }
6687
- .error-icon {
6688
- font-size: 5rem;
6689
- margin-bottom: 1.5rem;
6690
- animation: shake 0.5s ease-in-out;
6691
- filter: drop-shadow(0 0 20px rgba(255, 50, 50, 0.5));
6556
+ if (projectName) {
6557
+ requestBody.project_name = projectName;
6558
+ authHeaders = getAuthHeaders();
6559
+ }
6560
+ console.log(
6561
+ `[chunk/complete] payload=${JSON.stringify({
6562
+ session_id: requestBody.session_id,
6563
+ uid: requestBody.uid,
6564
+ import_as_car: !!requestBody.import_as_car,
6565
+ project_name: requestBody.project_name || ""
6566
+ })}`
6567
+ );
6568
+ const response = await axios_default.post(
6569
+ `${IPFS_API_URL}/chunk/complete`,
6570
+ requestBody,
6571
+ {
6572
+ timeout: TIMEOUT,
6573
+ headers: {
6574
+ "Content-Type": "application/json",
6575
+ ...authHeaders
6576
+ }
6577
+ }
6578
+ );
6579
+ const { code, msg, data } = response.data;
6580
+ if (code === 200 && data) {
6581
+ return data.trace_id;
6692
6582
  }
6693
- @keyframes shake {
6694
- 0%, 100% { transform: translateX(0); }
6695
- 20% { transform: translateX(-10px) rotate(-5deg); }
6696
- 40% { transform: translateX(10px) rotate(5deg); }
6697
- 60% { transform: translateX(-10px) rotate(-5deg); }
6698
- 80% { transform: translateX(10px) rotate(5deg); }
6583
+ throw new Error(`Complete upload failed: ${msg} (code: ${code})`);
6584
+ } catch (error) {
6585
+ if (axios_default.isAxiosError(error)) {
6586
+ throw new Error(`Network error: ${error.message}`);
6699
6587
  }
6700
- h1 {
6701
- color: #fff;
6702
- font-size: 2.2rem;
6703
- font-weight: 700;
6704
- margin: 0 0 0.75rem 0;
6705
- background: linear-gradient(90deg, #fff, #ff5050);
6706
- -webkit-background-clip: text;
6707
- -webkit-text-fill-color: transparent;
6708
- background-clip: text;
6588
+ throw error;
6589
+ }
6590
+ }
6591
+ async function getChunkStatus(sessionId, deviceId) {
6592
+ try {
6593
+ const response = await axios_default.get(
6594
+ `${IPFS_API_URL}/up_status`,
6595
+ {
6596
+ params: { trace_id: sessionId, uid: deviceId },
6597
+ timeout: TIMEOUT,
6598
+ headers: { "Content-Type": "application/json" }
6599
+ }
6600
+ );
6601
+ const { code, msg, data } = response.data;
6602
+ if (code === 200) {
6603
+ return data;
6709
6604
  }
6710
- .error {
6711
- color: #ff6b6b;
6712
- font-size: 1rem;
6713
- margin: 0 0 2rem 0;
6714
- padding: 1.25rem;
6715
- background: rgba(255, 50, 50, 0.1);
6716
- border-radius: 16px;
6717
- border: 1px solid rgba(255, 50, 50, 0.2);
6718
- font-weight: 500;
6719
- box-shadow: 0 0 20px rgba(255, 50, 50, 0.1);
6605
+ throw new Error(`Server returned error: ${msg} (code: ${code})`);
6606
+ } catch (error) {
6607
+ if (axios_default.isAxiosError(error)) {
6608
+ throw new Error(`Network error: ${error.message}`);
6720
6609
  }
6721
- </style>
6722
- </head>
6723
- <body>
6724
- <div class="bg"></div>
6725
- <div class="grid"></div>
6726
- <div class="container">
6727
- <div class="error-icon">\u{1F635}</div>
6728
- <h1>Oops!</h1>
6729
- <div class="error">${error}</div>
6730
- </div>
6731
- </body>
6732
- </html>`;
6610
+ throw error;
6733
6611
  }
6734
- };
6735
- var webLoginManager = new WebLoginManager();
6736
- function setAuthToken(combined) {
6737
- const firstDash = combined.indexOf("-");
6738
- if (firstDash <= 0 || firstDash === combined.length - 1) {
6739
- throw new Error('Invalid token format. Expected "<address>-<jwt>".');
6612
+ }
6613
+ async function monitorChunkProgress(traceId, deviceId, progressBar) {
6614
+ let consecutiveErrors = 0;
6615
+ const startTime = Date.now();
6616
+ if (progressBar) {
6617
+ progressBar.startSimulatingProgress();
6740
6618
  }
6741
- const address = combined.slice(0, firstDash).trim();
6742
- const token = combined.slice(firstDash + 1).trim();
6743
- if (!address || !token) {
6744
- throw new Error("Invalid token content. Address or token is empty.");
6619
+ try {
6620
+ while (Date.now() - startTime < MAX_POLL_TIME) {
6621
+ try {
6622
+ const status = await getChunkStatus(traceId, deviceId);
6623
+ consecutiveErrors = 0;
6624
+ if (status.is_ready && status.upload_rst.Hash) {
6625
+ if (progressBar) {
6626
+ progressBar.stopSimulatingProgress();
6627
+ }
6628
+ return {
6629
+ hash: status.upload_rst.Hash,
6630
+ shortUrl: status.upload_rst.ShortUrl
6631
+ };
6632
+ }
6633
+ } catch (error) {
6634
+ consecutiveErrors++;
6635
+ if (consecutiveErrors > 10) {
6636
+ throw new Error(`Polling failed: ${error.message}`);
6637
+ }
6638
+ }
6639
+ await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL));
6640
+ }
6641
+ const maxPollTimeMinutes = Math.floor(MAX_POLL_TIME / (60 * 1e3));
6642
+ throw new Error(`Polling timeout after ${maxPollTimeMinutes} minutes`);
6643
+ } finally {
6644
+ if (progressBar) {
6645
+ progressBar.stopSimulatingProgress();
6646
+ }
6745
6647
  }
6746
- const config = { address, token };
6747
- import_fs_extra5.default.ensureDirSync(CONFIG_DIR2);
6748
- import_fs_extra5.default.writeJsonSync(AUTH_FILE2, config, { spaces: 2 });
6749
- return config;
6750
6648
  }
6751
- function getAuthConfig2() {
6649
+ async function uploadDirectoryInChunks(directoryPath, deviceId, importAsCar = false) {
6650
+ const sizeCheck = checkDirectorySizeLimit(directoryPath);
6651
+ if (sizeCheck.exceeds) {
6652
+ throw new Error(
6653
+ `Directory ${directoryPath} exceeds size limit ${formatSize(
6654
+ sizeCheck.limit
6655
+ )} (size: ${formatSize(sizeCheck.size)})`
6656
+ );
6657
+ }
6658
+ const progressBar = new StepProgressBar(import_path6.default.basename(directoryPath), true);
6752
6659
  try {
6753
- if (!import_fs_extra5.default.existsSync(AUTH_FILE2)) return null;
6754
- const data = import_fs_extra5.default.readJsonSync(AUTH_FILE2);
6755
- if (!(data == null ? void 0 : data.address) || !(data == null ? void 0 : data.token)) return null;
6756
- return data;
6757
- } catch {
6758
- return null;
6660
+ progressBar.startStep(0, "Preparing compression");
6661
+ const compressedPath = await compressDirectory(directoryPath);
6662
+ progressBar.completeStep();
6663
+ progressBar.startStep(1, "Initializing session");
6664
+ const sessionInfo = await initChunkSession(compressedPath, deviceId, true);
6665
+ progressBar.completeStep();
6666
+ progressBar.startStep(2, "Chunk upload");
6667
+ await uploadFileChunks(
6668
+ compressedPath,
6669
+ sessionInfo.session_id,
6670
+ sessionInfo.total_chunks,
6671
+ sessionInfo.chunk_size,
6672
+ deviceId,
6673
+ progressBar
6674
+ );
6675
+ progressBar.completeStep();
6676
+ progressBar.startStep(3, "Completing upload");
6677
+ const traceId = await completeChunkUpload(sessionInfo.session_id, deviceId, importAsCar);
6678
+ progressBar.completeStep();
6679
+ progressBar.startStep(4, "Waiting for processing");
6680
+ const result = await monitorChunkProgress(traceId, deviceId, progressBar);
6681
+ progressBar.completeStep();
6682
+ try {
6683
+ import_fs_extra5.default.unlinkSync(compressedPath);
6684
+ } catch (error) {
6685
+ }
6686
+ const uploadData = {
6687
+ path: directoryPath,
6688
+ filename: import_path6.default.basename(directoryPath),
6689
+ contentHash: (result == null ? void 0 : result.hash) || "unknown",
6690
+ size: sizeCheck.size,
6691
+ fileCount: 0,
6692
+ isDirectory: true,
6693
+ shortUrl: (result == null ? void 0 : result.shortUrl) || null
6694
+ };
6695
+ saveUploadHistory(uploadData);
6696
+ if (!(result == null ? void 0 : result.hash)) {
6697
+ throw new Error("Server did not return valid hash value");
6698
+ }
6699
+ progressBar.complete();
6700
+ return result;
6701
+ } catch (error) {
6702
+ progressBar.fail(error.message);
6703
+ throw error;
6759
6704
  }
6760
6705
  }
6761
- function clearAuthToken() {
6706
+ async function uploadFileInChunks(filePath, deviceId, importAsCar = false) {
6707
+ const sizeCheck = checkFileSizeLimit(filePath);
6708
+ if (sizeCheck.exceeds) {
6709
+ throw new Error(
6710
+ `File ${filePath} exceeds size limit ${formatSize(
6711
+ sizeCheck.limit
6712
+ )} (size: ${formatSize(sizeCheck.size)})`
6713
+ );
6714
+ }
6715
+ const fileName = import_path6.default.basename(filePath);
6716
+ const progressBar = new StepProgressBar(fileName, false);
6762
6717
  try {
6763
- if (import_fs_extra5.default.existsSync(AUTH_FILE2)) {
6764
- import_fs_extra5.default.removeSync(AUTH_FILE2);
6718
+ progressBar.startStep(0, "Initializing session");
6719
+ const sessionInfo = await initChunkSession(filePath, deviceId, false);
6720
+ progressBar.completeStep();
6721
+ progressBar.startStep(1, "Chunk upload");
6722
+ await uploadFileChunks(
6723
+ filePath,
6724
+ sessionInfo.session_id,
6725
+ sessionInfo.total_chunks,
6726
+ sessionInfo.chunk_size,
6727
+ deviceId,
6728
+ progressBar
6729
+ );
6730
+ progressBar.completeStep();
6731
+ progressBar.startStep(2, "Completing upload");
6732
+ const traceId = await completeChunkUpload(sessionInfo.session_id, deviceId, importAsCar);
6733
+ progressBar.completeStep();
6734
+ progressBar.startStep(3, "Waiting for processing");
6735
+ const result = await monitorChunkProgress(traceId, deviceId, progressBar);
6736
+ progressBar.completeStep();
6737
+ const uploadData = {
6738
+ path: filePath,
6739
+ filename: fileName,
6740
+ contentHash: (result == null ? void 0 : result.hash) || "unknown",
6741
+ previewHash: null,
6742
+ size: sizeCheck.size,
6743
+ fileCount: 1,
6744
+ isDirectory: false,
6745
+ shortUrl: (result == null ? void 0 : result.shortUrl) || null
6746
+ };
6747
+ saveUploadHistory(uploadData);
6748
+ if (!(result == null ? void 0 : result.hash)) {
6749
+ throw new Error("Server did not return valid hash value");
6765
6750
  }
6751
+ progressBar.complete();
6752
+ return result;
6766
6753
  } catch (error) {
6767
- console.error(`Failed to clear auth token: ${error}`);
6754
+ progressBar.fail(error.message);
6755
+ throw error;
6768
6756
  }
6769
6757
  }
6770
- function getAuthHeaders() {
6771
- const conf = getAuthConfig2();
6772
- if (!conf) {
6773
- throw new Error("Auth not set. Run: pinme login");
6758
+ async function uploadToIpfsSplit_default(filePath, importAsCar = false) {
6759
+ const deviceId = getUid();
6760
+ if (!deviceId) {
6761
+ throw new Error("Device ID not found");
6762
+ }
6763
+ try {
6764
+ const isDirectory = import_fs_extra5.default.statSync(filePath).isDirectory();
6765
+ const result = isDirectory ? await uploadDirectoryInChunks(filePath, deviceId, importAsCar) : await uploadFileInChunks(filePath, deviceId, importAsCar);
6766
+ if (result == null ? void 0 : result.hash) {
6767
+ return {
6768
+ contentHash: result.hash,
6769
+ previewHash: null,
6770
+ shortUrl: result.shortUrl
6771
+ };
6772
+ }
6773
+ return null;
6774
+ } catch (error) {
6775
+ return null;
6774
6776
  }
6775
- return {
6776
- "token-address": conf.address,
6777
- "authentication-tokens": conf.token
6778
- };
6779
6777
  }
6780
6778
 
6779
+ // bin/upload.ts
6780
+ var import_fs2 = __toESM(require("fs"));
6781
+ var import_crypto_js = __toESM(require("crypto-js"));
6782
+
6781
6783
  // bin/utils/pinmeApi.ts
6784
+ var import_chalk4 = __toESM(require("chalk"));
6782
6785
  var DEFAULT_BASE = "https://pinme.benny1996.win/api/v4";
6783
6786
  var TOKEN_EXPIRED_CODES = [
6784
6787
  401,
@@ -8496,7 +8499,11 @@ Directory "${projectName}" already exists.`));
8496
8499
  try {
8497
8500
  (0, import_child_process2.execSync)("pinme upload ./dist", {
8498
8501
  cwd: frontendDir,
8499
- stdio: "inherit"
8502
+ stdio: "inherit",
8503
+ env: {
8504
+ ...process.env,
8505
+ PINME_PROJECT_NAME: workerData.project_name
8506
+ }
8500
8507
  });
8501
8508
  console.log(import_chalk16.default.green(" Frontend uploaded to IPFS"));
8502
8509
  } catch (error) {
@@ -8511,7 +8518,7 @@ Project Details:`));
8511
8518
  console.log(import_chalk16.default.gray(`
8512
8519
  Next steps:`));
8513
8520
  console.log(import_chalk16.default.gray(` cd ${projectName}`));
8514
- console.log(import_chalk16.default.gray(` pinme save # \u9996\u6B21\u90E8\u7F72\u540E\u7AEF + \u524D\u7AEF`));
8521
+ console.log(import_chalk16.default.gray(` pinme save`));
8515
8522
  process.exit(0);
8516
8523
  } catch (error) {
8517
8524
  console.error(import_chalk16.default.red(`
@@ -9188,14 +9195,7 @@ Project: ${data.data.project_name}`));
9188
9195
  console.log(import_chalk21.default.gray(` Domain deleted: ${data.data.domain_deleted ? "\u2705" : "\u274C"}`));
9189
9196
  console.log(import_chalk21.default.gray(` Worker deleted: ${data.data.worker_deleted ? "\u2705" : "\u274C"}`));
9190
9197
  console.log(import_chalk21.default.gray(` Database deleted: ${data.data.database_deleted ? "\u2705" : "\u274C"}`));
9191
- const projectDir = process.cwd();
9192
- if (import_fs_extra11.default.existsSync(projectDir)) {
9193
- console.log(import_chalk21.default.blue("\nDeleting local project directory..."));
9194
- const parentDir = import_path16.default.dirname(projectDir);
9195
- process.chdir(parentDir);
9196
- await import_fs_extra11.default.remove(projectDir);
9197
- console.log(import_chalk21.default.green(`\u2705 Local directory deleted: ${projectDir}`));
9198
- }
9198
+ console.log(import_chalk21.default.gray("\nLocal files are kept unchanged."));
9199
9199
  } else {
9200
9200
  const errorMsg = (data == null ? void 0 : data.msg) || "Failed to delete project";
9201
9201
  throw new Error(errorMsg);