291025/implement_electron
This commit is contained in:
parent
20b3e4ad07
commit
a39f306f8d
502
.vite/build/neptune-web-wallet.cjs
Normal file
502
.vite/build/neptune-web-wallet.cjs
Normal file
@ -0,0 +1,502 @@
|
||||
"use strict";
|
||||
const require$$3$1 = require("electron");
|
||||
const path$1 = require("node:path");
|
||||
const require$$0$1 = require("path");
|
||||
const require$$1$1 = require("child_process");
|
||||
const require$$0 = require("tty");
|
||||
const require$$1 = require("util");
|
||||
const require$$3 = require("fs");
|
||||
const require$$4 = require("net");
|
||||
function getDefaultExportFromCjs(x) {
|
||||
return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, "default") ? x["default"] : x;
|
||||
}
|
||||
var src = { exports: {} };
|
||||
var browser = { exports: {} };
|
||||
var debug$1 = { exports: {} };
|
||||
var ms;
|
||||
var hasRequiredMs;
|
||||
function requireMs() {
|
||||
if (hasRequiredMs) return ms;
|
||||
hasRequiredMs = 1;
|
||||
var s = 1e3;
|
||||
var m = s * 60;
|
||||
var h = m * 60;
|
||||
var d = h * 24;
|
||||
var y = d * 365.25;
|
||||
ms = function(val, options) {
|
||||
options = options || {};
|
||||
var type = typeof val;
|
||||
if (type === "string" && val.length > 0) {
|
||||
return parse(val);
|
||||
} else if (type === "number" && isNaN(val) === false) {
|
||||
return options.long ? fmtLong(val) : fmtShort(val);
|
||||
}
|
||||
throw new Error(
|
||||
"val is not a non-empty string or a valid number. val=" + JSON.stringify(val)
|
||||
);
|
||||
};
|
||||
function parse(str) {
|
||||
str = String(str);
|
||||
if (str.length > 100) {
|
||||
return;
|
||||
}
|
||||
var match = /^((?:\d+)?\.?\d+) *(milliseconds?|msecs?|ms|seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h|days?|d|years?|yrs?|y)?$/i.exec(
|
||||
str
|
||||
);
|
||||
if (!match) {
|
||||
return;
|
||||
}
|
||||
var n = parseFloat(match[1]);
|
||||
var type = (match[2] || "ms").toLowerCase();
|
||||
switch (type) {
|
||||
case "years":
|
||||
case "year":
|
||||
case "yrs":
|
||||
case "yr":
|
||||
case "y":
|
||||
return n * y;
|
||||
case "days":
|
||||
case "day":
|
||||
case "d":
|
||||
return n * d;
|
||||
case "hours":
|
||||
case "hour":
|
||||
case "hrs":
|
||||
case "hr":
|
||||
case "h":
|
||||
return n * h;
|
||||
case "minutes":
|
||||
case "minute":
|
||||
case "mins":
|
||||
case "min":
|
||||
case "m":
|
||||
return n * m;
|
||||
case "seconds":
|
||||
case "second":
|
||||
case "secs":
|
||||
case "sec":
|
||||
case "s":
|
||||
return n * s;
|
||||
case "milliseconds":
|
||||
case "millisecond":
|
||||
case "msecs":
|
||||
case "msec":
|
||||
case "ms":
|
||||
return n;
|
||||
default:
|
||||
return void 0;
|
||||
}
|
||||
}
|
||||
function fmtShort(ms2) {
|
||||
if (ms2 >= d) {
|
||||
return Math.round(ms2 / d) + "d";
|
||||
}
|
||||
if (ms2 >= h) {
|
||||
return Math.round(ms2 / h) + "h";
|
||||
}
|
||||
if (ms2 >= m) {
|
||||
return Math.round(ms2 / m) + "m";
|
||||
}
|
||||
if (ms2 >= s) {
|
||||
return Math.round(ms2 / s) + "s";
|
||||
}
|
||||
return ms2 + "ms";
|
||||
}
|
||||
function fmtLong(ms2) {
|
||||
return plural(ms2, d, "day") || plural(ms2, h, "hour") || plural(ms2, m, "minute") || plural(ms2, s, "second") || ms2 + " ms";
|
||||
}
|
||||
function plural(ms2, n, name) {
|
||||
if (ms2 < n) {
|
||||
return;
|
||||
}
|
||||
if (ms2 < n * 1.5) {
|
||||
return Math.floor(ms2 / n) + " " + name;
|
||||
}
|
||||
return Math.ceil(ms2 / n) + " " + name + "s";
|
||||
}
|
||||
return ms;
|
||||
}
|
||||
var hasRequiredDebug;
|
||||
function requireDebug() {
|
||||
if (hasRequiredDebug) return debug$1.exports;
|
||||
hasRequiredDebug = 1;
|
||||
(function(module2, exports2) {
|
||||
exports2 = module2.exports = createDebug.debug = createDebug["default"] = createDebug;
|
||||
exports2.coerce = coerce;
|
||||
exports2.disable = disable;
|
||||
exports2.enable = enable;
|
||||
exports2.enabled = enabled;
|
||||
exports2.humanize = requireMs();
|
||||
exports2.names = [];
|
||||
exports2.skips = [];
|
||||
exports2.formatters = {};
|
||||
var prevTime;
|
||||
function selectColor(namespace) {
|
||||
var hash = 0, i;
|
||||
for (i in namespace) {
|
||||
hash = (hash << 5) - hash + namespace.charCodeAt(i);
|
||||
hash |= 0;
|
||||
}
|
||||
return exports2.colors[Math.abs(hash) % exports2.colors.length];
|
||||
}
|
||||
function createDebug(namespace) {
|
||||
function debug2() {
|
||||
if (!debug2.enabled) return;
|
||||
var self = debug2;
|
||||
var curr = +/* @__PURE__ */ new Date();
|
||||
var ms2 = curr - (prevTime || curr);
|
||||
self.diff = ms2;
|
||||
self.prev = prevTime;
|
||||
self.curr = curr;
|
||||
prevTime = curr;
|
||||
var args = new Array(arguments.length);
|
||||
for (var i = 0; i < args.length; i++) {
|
||||
args[i] = arguments[i];
|
||||
}
|
||||
args[0] = exports2.coerce(args[0]);
|
||||
if ("string" !== typeof args[0]) {
|
||||
args.unshift("%O");
|
||||
}
|
||||
var index = 0;
|
||||
args[0] = args[0].replace(/%([a-zA-Z%])/g, function(match, format) {
|
||||
if (match === "%%") return match;
|
||||
index++;
|
||||
var formatter = exports2.formatters[format];
|
||||
if ("function" === typeof formatter) {
|
||||
var val = args[index];
|
||||
match = formatter.call(self, val);
|
||||
args.splice(index, 1);
|
||||
index--;
|
||||
}
|
||||
return match;
|
||||
});
|
||||
exports2.formatArgs.call(self, args);
|
||||
var logFn = debug2.log || exports2.log || console.log.bind(console);
|
||||
logFn.apply(self, args);
|
||||
}
|
||||
debug2.namespace = namespace;
|
||||
debug2.enabled = exports2.enabled(namespace);
|
||||
debug2.useColors = exports2.useColors();
|
||||
debug2.color = selectColor(namespace);
|
||||
if ("function" === typeof exports2.init) {
|
||||
exports2.init(debug2);
|
||||
}
|
||||
return debug2;
|
||||
}
|
||||
function enable(namespaces) {
|
||||
exports2.save(namespaces);
|
||||
exports2.names = [];
|
||||
exports2.skips = [];
|
||||
var split = (typeof namespaces === "string" ? namespaces : "").split(/[\s,]+/);
|
||||
var len = split.length;
|
||||
for (var i = 0; i < len; i++) {
|
||||
if (!split[i]) continue;
|
||||
namespaces = split[i].replace(/\*/g, ".*?");
|
||||
if (namespaces[0] === "-") {
|
||||
exports2.skips.push(new RegExp("^" + namespaces.substr(1) + "$"));
|
||||
} else {
|
||||
exports2.names.push(new RegExp("^" + namespaces + "$"));
|
||||
}
|
||||
}
|
||||
}
|
||||
function disable() {
|
||||
exports2.enable("");
|
||||
}
|
||||
function enabled(name) {
|
||||
var i, len;
|
||||
for (i = 0, len = exports2.skips.length; i < len; i++) {
|
||||
if (exports2.skips[i].test(name)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
for (i = 0, len = exports2.names.length; i < len; i++) {
|
||||
if (exports2.names[i].test(name)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
function coerce(val) {
|
||||
if (val instanceof Error) return val.stack || val.message;
|
||||
return val;
|
||||
}
|
||||
})(debug$1, debug$1.exports);
|
||||
return debug$1.exports;
|
||||
}
|
||||
var hasRequiredBrowser;
|
||||
function requireBrowser() {
|
||||
if (hasRequiredBrowser) return browser.exports;
|
||||
hasRequiredBrowser = 1;
|
||||
(function(module2, exports2) {
|
||||
exports2 = module2.exports = requireDebug();
|
||||
exports2.log = log;
|
||||
exports2.formatArgs = formatArgs;
|
||||
exports2.save = save;
|
||||
exports2.load = load;
|
||||
exports2.useColors = useColors;
|
||||
exports2.storage = "undefined" != typeof chrome && "undefined" != typeof chrome.storage ? chrome.storage.local : localstorage();
|
||||
exports2.colors = [
|
||||
"lightseagreen",
|
||||
"forestgreen",
|
||||
"goldenrod",
|
||||
"dodgerblue",
|
||||
"darkorchid",
|
||||
"crimson"
|
||||
];
|
||||
function useColors() {
|
||||
if (typeof window !== "undefined" && window.process && window.process.type === "renderer") {
|
||||
return true;
|
||||
}
|
||||
return typeof document !== "undefined" && document.documentElement && document.documentElement.style && document.documentElement.style.WebkitAppearance || // is firebug? http://stackoverflow.com/a/398120/376773
|
||||
typeof window !== "undefined" && window.console && (window.console.firebug || window.console.exception && window.console.table) || // is firefox >= v31?
|
||||
// https://developer.mozilla.org/en-US/docs/Tools/Web_Console#Styling_messages
|
||||
typeof navigator !== "undefined" && navigator.userAgent && navigator.userAgent.toLowerCase().match(/firefox\/(\d+)/) && parseInt(RegExp.$1, 10) >= 31 || // double check webkit in userAgent just in case we are in a worker
|
||||
typeof navigator !== "undefined" && navigator.userAgent && navigator.userAgent.toLowerCase().match(/applewebkit\/(\d+)/);
|
||||
}
|
||||
exports2.formatters.j = function(v) {
|
||||
try {
|
||||
return JSON.stringify(v);
|
||||
} catch (err) {
|
||||
return "[UnexpectedJSONParseError]: " + err.message;
|
||||
}
|
||||
};
|
||||
function formatArgs(args) {
|
||||
var useColors2 = this.useColors;
|
||||
args[0] = (useColors2 ? "%c" : "") + this.namespace + (useColors2 ? " %c" : " ") + args[0] + (useColors2 ? "%c " : " ") + "+" + exports2.humanize(this.diff);
|
||||
if (!useColors2) return;
|
||||
var c = "color: " + this.color;
|
||||
args.splice(1, 0, c, "color: inherit");
|
||||
var index = 0;
|
||||
var lastC = 0;
|
||||
args[0].replace(/%[a-zA-Z%]/g, function(match) {
|
||||
if ("%%" === match) return;
|
||||
index++;
|
||||
if ("%c" === match) {
|
||||
lastC = index;
|
||||
}
|
||||
});
|
||||
args.splice(lastC, 0, c);
|
||||
}
|
||||
function log() {
|
||||
return "object" === typeof console && console.log && Function.prototype.apply.call(console.log, console, arguments);
|
||||
}
|
||||
function save(namespaces) {
|
||||
try {
|
||||
if (null == namespaces) {
|
||||
exports2.storage.removeItem("debug");
|
||||
} else {
|
||||
exports2.storage.debug = namespaces;
|
||||
}
|
||||
} catch (e) {
|
||||
}
|
||||
}
|
||||
function load() {
|
||||
var r;
|
||||
try {
|
||||
r = exports2.storage.debug;
|
||||
} catch (e) {
|
||||
}
|
||||
if (!r && typeof process !== "undefined" && "env" in process) {
|
||||
r = process.env.DEBUG;
|
||||
}
|
||||
return r;
|
||||
}
|
||||
exports2.enable(load());
|
||||
function localstorage() {
|
||||
try {
|
||||
return window.localStorage;
|
||||
} catch (e) {
|
||||
}
|
||||
}
|
||||
})(browser, browser.exports);
|
||||
return browser.exports;
|
||||
}
|
||||
var node = { exports: {} };
|
||||
var hasRequiredNode;
|
||||
function requireNode() {
|
||||
if (hasRequiredNode) return node.exports;
|
||||
hasRequiredNode = 1;
|
||||
(function(module2, exports2) {
|
||||
var tty = require$$0;
|
||||
var util = require$$1;
|
||||
exports2 = module2.exports = requireDebug();
|
||||
exports2.init = init;
|
||||
exports2.log = log;
|
||||
exports2.formatArgs = formatArgs;
|
||||
exports2.save = save;
|
||||
exports2.load = load;
|
||||
exports2.useColors = useColors;
|
||||
exports2.colors = [6, 2, 3, 4, 5, 1];
|
||||
exports2.inspectOpts = Object.keys(process.env).filter(function(key) {
|
||||
return /^debug_/i.test(key);
|
||||
}).reduce(function(obj, key) {
|
||||
var prop = key.substring(6).toLowerCase().replace(/_([a-z])/g, function(_, k) {
|
||||
return k.toUpperCase();
|
||||
});
|
||||
var val = process.env[key];
|
||||
if (/^(yes|on|true|enabled)$/i.test(val)) val = true;
|
||||
else if (/^(no|off|false|disabled)$/i.test(val)) val = false;
|
||||
else if (val === "null") val = null;
|
||||
else val = Number(val);
|
||||
obj[prop] = val;
|
||||
return obj;
|
||||
}, {});
|
||||
var fd = parseInt(process.env.DEBUG_FD, 10) || 2;
|
||||
if (1 !== fd && 2 !== fd) {
|
||||
util.deprecate(function() {
|
||||
}, "except for stderr(2) and stdout(1), any other usage of DEBUG_FD is deprecated. Override debug.log if you want to use a different log function (https://git.io/debug_fd)")();
|
||||
}
|
||||
var stream = 1 === fd ? process.stdout : 2 === fd ? process.stderr : createWritableStdioStream(fd);
|
||||
function useColors() {
|
||||
return "colors" in exports2.inspectOpts ? Boolean(exports2.inspectOpts.colors) : tty.isatty(fd);
|
||||
}
|
||||
exports2.formatters.o = function(v) {
|
||||
this.inspectOpts.colors = this.useColors;
|
||||
return util.inspect(v, this.inspectOpts).split("\n").map(function(str) {
|
||||
return str.trim();
|
||||
}).join(" ");
|
||||
};
|
||||
exports2.formatters.O = function(v) {
|
||||
this.inspectOpts.colors = this.useColors;
|
||||
return util.inspect(v, this.inspectOpts);
|
||||
};
|
||||
function formatArgs(args) {
|
||||
var name = this.namespace;
|
||||
var useColors2 = this.useColors;
|
||||
if (useColors2) {
|
||||
var c = this.color;
|
||||
var prefix = " \x1B[3" + c + ";1m" + name + " \x1B[0m";
|
||||
args[0] = prefix + args[0].split("\n").join("\n" + prefix);
|
||||
args.push("\x1B[3" + c + "m+" + exports2.humanize(this.diff) + "\x1B[0m");
|
||||
} else {
|
||||
args[0] = (/* @__PURE__ */ new Date()).toUTCString() + " " + name + " " + args[0];
|
||||
}
|
||||
}
|
||||
function log() {
|
||||
return stream.write(util.format.apply(util, arguments) + "\n");
|
||||
}
|
||||
function save(namespaces) {
|
||||
if (null == namespaces) {
|
||||
delete process.env.DEBUG;
|
||||
} else {
|
||||
process.env.DEBUG = namespaces;
|
||||
}
|
||||
}
|
||||
function load() {
|
||||
return process.env.DEBUG;
|
||||
}
|
||||
function createWritableStdioStream(fd2) {
|
||||
var stream2;
|
||||
var tty_wrap = process.binding("tty_wrap");
|
||||
switch (tty_wrap.guessHandleType(fd2)) {
|
||||
case "TTY":
|
||||
stream2 = new tty.WriteStream(fd2);
|
||||
stream2._type = "tty";
|
||||
if (stream2._handle && stream2._handle.unref) {
|
||||
stream2._handle.unref();
|
||||
}
|
||||
break;
|
||||
case "FILE":
|
||||
var fs = require$$3;
|
||||
stream2 = new fs.SyncWriteStream(fd2, { autoClose: false });
|
||||
stream2._type = "fs";
|
||||
break;
|
||||
case "PIPE":
|
||||
case "TCP":
|
||||
var net = require$$4;
|
||||
stream2 = new net.Socket({
|
||||
fd: fd2,
|
||||
readable: false,
|
||||
writable: true
|
||||
});
|
||||
stream2.readable = false;
|
||||
stream2.read = null;
|
||||
stream2._type = "pipe";
|
||||
if (stream2._handle && stream2._handle.unref) {
|
||||
stream2._handle.unref();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new Error("Implement me. Unknown stream file type!");
|
||||
}
|
||||
stream2.fd = fd2;
|
||||
stream2._isStdio = true;
|
||||
return stream2;
|
||||
}
|
||||
function init(debug2) {
|
||||
debug2.inspectOpts = {};
|
||||
var keys = Object.keys(exports2.inspectOpts);
|
||||
for (var i = 0; i < keys.length; i++) {
|
||||
debug2.inspectOpts[keys[i]] = exports2.inspectOpts[keys[i]];
|
||||
}
|
||||
}
|
||||
exports2.enable(load());
|
||||
})(node, node.exports);
|
||||
return node.exports;
|
||||
}
|
||||
if (typeof process !== "undefined" && process.type === "renderer") {
|
||||
src.exports = requireBrowser();
|
||||
} else {
|
||||
src.exports = requireNode();
|
||||
}
|
||||
var srcExports = src.exports;
|
||||
var path = require$$0$1;
|
||||
var spawn = require$$1$1.spawn;
|
||||
var debug = srcExports("electron-squirrel-startup");
|
||||
var app = require$$3$1.app;
|
||||
var run = function(args, done) {
|
||||
var updateExe = path.resolve(path.dirname(process.execPath), "..", "Update.exe");
|
||||
debug("Spawning `%s` with args `%s`", updateExe, args);
|
||||
spawn(updateExe, args, {
|
||||
detached: true
|
||||
}).on("close", done);
|
||||
};
|
||||
var check = function() {
|
||||
if (process.platform === "win32") {
|
||||
var cmd = process.argv[1];
|
||||
debug("processing squirrel command `%s`", cmd);
|
||||
var target = path.basename(process.execPath);
|
||||
if (cmd === "--squirrel-install" || cmd === "--squirrel-updated") {
|
||||
run(["--createShortcut=" + target], app.quit);
|
||||
return true;
|
||||
}
|
||||
if (cmd === "--squirrel-uninstall") {
|
||||
run(["--removeShortcut=" + target], app.quit);
|
||||
return true;
|
||||
}
|
||||
if (cmd === "--squirrel-obsolete") {
|
||||
app.quit();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
var electronSquirrelStartup = check();
|
||||
const started = /* @__PURE__ */ getDefaultExportFromCjs(electronSquirrelStartup);
|
||||
if (started) {
|
||||
require$$3$1.app.quit();
|
||||
}
|
||||
const createWindow = () => {
|
||||
const mainWindow = new require$$3$1.BrowserWindow({
|
||||
width: 800,
|
||||
height: 600,
|
||||
webPreferences: {
|
||||
preload: path$1.join(__dirname, "preload.js")
|
||||
}
|
||||
});
|
||||
{
|
||||
mainWindow.loadURL("http://localhost:3008");
|
||||
}
|
||||
mainWindow.webContents.openDevTools();
|
||||
};
|
||||
require$$3$1.app.on("ready", createWindow);
|
||||
require$$3$1.app.on("window-all-closed", () => {
|
||||
if (process.platform !== "darwin") {
|
||||
require$$3$1.app.quit();
|
||||
}
|
||||
});
|
||||
require$$3$1.app.on("activate", () => {
|
||||
if (require$$3$1.BrowserWindow.getAllWindows().length === 0) {
|
||||
createWindow();
|
||||
}
|
||||
});
|
||||
1
.vite/build/preload.js
Normal file
1
.vite/build/preload.js
Normal file
@ -0,0 +1 @@
|
||||
"use strict";
|
||||
49
electron/main.ts
Normal file
49
electron/main.ts
Normal file
@ -0,0 +1,49 @@
|
||||
import { app, BrowserWindow } from 'electron'
|
||||
import path from 'node:path'
|
||||
import started from 'electron-squirrel-startup'
|
||||
|
||||
if (started) {
|
||||
app.quit()
|
||||
}
|
||||
|
||||
const createWindow = () => {
|
||||
const mainWindow = new BrowserWindow({
|
||||
width: 800,
|
||||
height: 600,
|
||||
webPreferences: {
|
||||
preload: path.join(__dirname, 'preload.js'),
|
||||
},
|
||||
})
|
||||
if (MAIN_WINDOW_VITE_DEV_SERVER_URL) {
|
||||
mainWindow.loadURL(MAIN_WINDOW_VITE_DEV_SERVER_URL)
|
||||
} else {
|
||||
mainWindow.loadFile(path.join(__dirname, `../renderer/${MAIN_WINDOW_VITE_NAME}/index.html`))
|
||||
}
|
||||
|
||||
mainWindow.webContents.openDevTools()
|
||||
}
|
||||
|
||||
// This method will be called when Electron has finished
|
||||
// initialization and is ready to create browser windows.
|
||||
// Some APIs can only be used after this event occurs.
|
||||
app.on('ready', createWindow)
|
||||
|
||||
// Quit when all windows are closed, except on macOS. There, it's common
|
||||
// for applications and their menu bar to stay active until the user quits
|
||||
// explicitly with Cmd + Q.
|
||||
app.on('window-all-closed', () => {
|
||||
if (process.platform !== 'darwin') {
|
||||
app.quit()
|
||||
}
|
||||
})
|
||||
|
||||
app.on('activate', () => {
|
||||
// On OS X it's common to re-create a window in the app when the
|
||||
// dock icon is clicked and there are no other windows open.
|
||||
if (BrowserWindow.getAllWindows().length === 0) {
|
||||
createWindow()
|
||||
}
|
||||
})
|
||||
|
||||
// In this file you can include the rest of your app's specific main process
|
||||
// code. You can also put them in separate files and import them here.
|
||||
2
electron/preload.ts
Normal file
2
electron/preload.ts
Normal file
@ -0,0 +1,2 @@
|
||||
// See the Electron documentation for details on how to use preload scripts:
|
||||
// https://www.electronjs.org/docs/latest/tutorial/process-model#preload-scripts
|
||||
13
electron/vite.main.config.ts
Normal file
13
electron/vite.main.config.ts
Normal file
@ -0,0 +1,13 @@
|
||||
import { defineConfig } from 'vite'
|
||||
|
||||
export default defineConfig({
|
||||
build: {
|
||||
lib: {
|
||||
entry: 'electron/main.ts',
|
||||
formats: ['cjs'],
|
||||
},
|
||||
rollupOptions: {
|
||||
external: ['electron'],
|
||||
},
|
||||
},
|
||||
})
|
||||
3
electron/vite.preload.config.ts
Normal file
3
electron/vite.preload.config.ts
Normal file
@ -0,0 +1,3 @@
|
||||
import { defineConfig } from 'vite'
|
||||
|
||||
export default defineConfig({})
|
||||
3
env.d.ts
vendored
3
env.d.ts
vendored
@ -1 +1,4 @@
|
||||
/// <reference types="vite/client" />
|
||||
|
||||
declare const MAIN_WINDOW_VITE_DEV_SERVER_URL: string | undefined;
|
||||
declare const MAIN_WINDOW_VITE_NAME: string | undefined;
|
||||
|
||||
59
forge.config.ts
Normal file
59
forge.config.ts
Normal file
@ -0,0 +1,59 @@
|
||||
import type { ForgeConfig } from '@electron-forge/shared-types';
|
||||
import { MakerSquirrel } from '@electron-forge/maker-squirrel';
|
||||
import { MakerZIP } from '@electron-forge/maker-zip';
|
||||
import { MakerDeb } from '@electron-forge/maker-deb';
|
||||
import { MakerRpm } from '@electron-forge/maker-rpm';
|
||||
import { VitePlugin } from '@electron-forge/plugin-vite';
|
||||
import { FusesPlugin } from '@electron-forge/plugin-fuses';
|
||||
import { FuseV1Options, FuseVersion } from '@electron/fuses';
|
||||
|
||||
const config: ForgeConfig = {
|
||||
packagerConfig: {
|
||||
asar: true,
|
||||
},
|
||||
rebuildConfig: {},
|
||||
makers: [
|
||||
new MakerSquirrel({}),
|
||||
new MakerZIP({}, ['darwin']),
|
||||
new MakerRpm({}),
|
||||
new MakerDeb({}),
|
||||
],
|
||||
plugins: [
|
||||
new VitePlugin({
|
||||
// `build` can specify multiple entry builds, which can be Main process, Preload scripts, Worker process, etc.
|
||||
// If you are familiar with Vite configuration, it will look really familiar.
|
||||
build: [
|
||||
{
|
||||
// `entry` is just an alias for `build.lib.entry` in the corresponding file of `config`.
|
||||
entry: 'electron/main.ts',
|
||||
config: 'electron/vite.main.config.ts',
|
||||
target: 'main',
|
||||
},
|
||||
{
|
||||
entry: 'electron/preload.ts',
|
||||
config: 'electron/vite.preload.config.ts',
|
||||
target: 'preload',
|
||||
},
|
||||
],
|
||||
renderer: [
|
||||
{
|
||||
name: 'main_window',
|
||||
config: 'vite.config.ts',
|
||||
},
|
||||
],
|
||||
}),
|
||||
// Fuses are used to enable/disable various Electron functionality
|
||||
// at package time, before code signing the application
|
||||
new FusesPlugin({
|
||||
version: FuseVersion.V1,
|
||||
[FuseV1Options.RunAsNode]: false,
|
||||
[FuseV1Options.EnableCookieEncryption]: true,
|
||||
[FuseV1Options.EnableNodeOptionsEnvironmentVariable]: false,
|
||||
[FuseV1Options.EnableNodeCliInspectArguments]: false,
|
||||
[FuseV1Options.EnableEmbeddedAsarIntegrityValidation]: true,
|
||||
[FuseV1Options.OnlyLoadAppFromAsar]: true,
|
||||
}),
|
||||
],
|
||||
};
|
||||
|
||||
export default config;
|
||||
@ -2,8 +2,7 @@
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
<link rel="apple-touch-icon" href="/favicon.png" />
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Neptune Web Wallet</title>
|
||||
</head>
|
||||
|
||||
12268
package-lock.json
generated
Normal file
12268
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
17
package.json
17
package.json
@ -6,8 +6,13 @@
|
||||
"workspaces": [
|
||||
"packages/*"
|
||||
],
|
||||
"main": ".vite/build/neptune-web-wallet.cjs",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"start:electron": "electron-forge start",
|
||||
"make:electron": "electron-forge make",
|
||||
"package:electron": "electron-forge package",
|
||||
"publish:electron": "electron-forge publish",
|
||||
"build": "run-p type-check \"build-only {@}\" --",
|
||||
"preview": "vite preview",
|
||||
"build-only": "vite build",
|
||||
@ -20,20 +25,32 @@
|
||||
"ant-design-vue": "^4.2.6",
|
||||
"axios": "^1.6.8",
|
||||
"dayjs": "^1.11.10",
|
||||
"electron-squirrel-startup": "^1.0.1",
|
||||
"pinia": "^2.1.7",
|
||||
"vue": "^3.4.21",
|
||||
"vue-router": "^4.3.0",
|
||||
"vue3-i18n": "^1.1.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@electron-forge/cli": "^7.10.2",
|
||||
"@electron-forge/maker-deb": "^7.10.2",
|
||||
"@electron-forge/maker-rpm": "^7.10.2",
|
||||
"@electron-forge/maker-squirrel": "^7.10.2",
|
||||
"@electron-forge/maker-zip": "^7.10.2",
|
||||
"@electron-forge/plugin-auto-unpack-natives": "^7.10.2",
|
||||
"@electron-forge/plugin-fuses": "^7.10.2",
|
||||
"@electron-forge/plugin-vite": "^7.10.2",
|
||||
"@electron/fuses": "^1.8.0",
|
||||
"@rushstack/eslint-patch": "^1.8.0",
|
||||
"@tsconfig/node20": "^20.1.4",
|
||||
"@types/electron-squirrel-startup": "^1.0.2",
|
||||
"@types/node": "^20.12.5",
|
||||
"@vitejs/plugin-vue": "^5.0.4",
|
||||
"@vitejs/plugin-vue-jsx": "^3.1.0",
|
||||
"@vue/eslint-config-prettier": "^9.0.0",
|
||||
"@vue/eslint-config-typescript": "^13.0.0",
|
||||
"@vue/tsconfig": "^0.5.1",
|
||||
"electron": "^39.0.0",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-plugin-vue": "^9.23.0",
|
||||
"npm-run-all2": "^6.1.2",
|
||||
|
||||
@ -51,3 +51,11 @@ export const getTransaction = async (txHash: string): Promise<any> => {
|
||||
export const getMempoolInfo = async (): Promise<any> => {
|
||||
return await callJsonRpc('chain_mempool', [])
|
||||
}
|
||||
|
||||
export const importKeystore = async (keystore: string, password: string): Promise<any> => {
|
||||
const params = {
|
||||
keystore,
|
||||
password,
|
||||
}
|
||||
return await callJsonRpc('wallet_importKeystore', params)
|
||||
}
|
||||
|
||||
@ -57,6 +57,7 @@ export function useNeptuneWallet() {
|
||||
|
||||
const resultJson = generate_seed()
|
||||
const result: GenerateSeedResult = JSON.parse(resultJson)
|
||||
console.log('result :>> ', result);
|
||||
|
||||
store.setSeedPhrase(result.seed_phrase)
|
||||
store.setReceiverId(result.receiver_identifier)
|
||||
@ -165,6 +166,41 @@ export function useNeptuneWallet() {
|
||||
}
|
||||
}
|
||||
|
||||
const importFromKeystore = async (keystore: string, password: string): Promise<any> => {
|
||||
try {
|
||||
store.setLoading(true)
|
||||
store.setError(null)
|
||||
|
||||
const response = await API.importKeystore(keystore, password)
|
||||
const result = response.data?.result || response.data
|
||||
|
||||
// Set wallet data from keystore import result
|
||||
if (result.seed_phrase) {
|
||||
store.setSeedPhrase(result.seed_phrase)
|
||||
}
|
||||
if (result.view_key) {
|
||||
store.setViewKey(result.view_key)
|
||||
}
|
||||
if (result.address) {
|
||||
store.setAddress(result.address)
|
||||
}
|
||||
if (result.receiver_identifier) {
|
||||
store.setReceiverId(result.receiver_identifier)
|
||||
}
|
||||
if (result.network) {
|
||||
store.setNetwork(result.network)
|
||||
}
|
||||
|
||||
return result
|
||||
} catch (err) {
|
||||
const errorMsg = err instanceof Error ? err.message : 'Failed to import from keystore'
|
||||
store.setError(errorMsg)
|
||||
throw err
|
||||
} finally {
|
||||
store.setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
// ===== API METHODS =====
|
||||
|
||||
const getUtxos = async (
|
||||
@ -298,6 +334,7 @@ export function useNeptuneWallet() {
|
||||
generateWallet,
|
||||
importWallet,
|
||||
importFromViewKey,
|
||||
importFromKeystore,
|
||||
getViewKeyFromSeed,
|
||||
getAddressFromSeed,
|
||||
validateSeedPhrase,
|
||||
|
||||
@ -22,6 +22,11 @@ export const routes: any = [
|
||||
name: 'login',
|
||||
component: Page.Auth,
|
||||
},
|
||||
{
|
||||
path: '/password',
|
||||
name: 'password',
|
||||
component: Page.PasswordKeystore,
|
||||
},
|
||||
{
|
||||
path: '/:pathMatch(.*)*',
|
||||
component: Page.NotFound,
|
||||
|
||||
12
src/types/electron.d.ts
vendored
Normal file
12
src/types/electron.d.ts
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
// Global type definitions for Electron API
|
||||
declare global {
|
||||
interface Window {
|
||||
electron: {
|
||||
send: (channel: string, data: any) => void;
|
||||
receive: (channel: string, func: (...args: any[]) => void) => void;
|
||||
removeAllListeners: (channel: string) => void;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export {};
|
||||
39
src/types/neptune-wasm.d.ts
vendored
39
src/types/neptune-wasm.d.ts
vendored
@ -1,39 +0,0 @@
|
||||
/**
|
||||
* Type declarations for Neptune WASM module
|
||||
*/
|
||||
|
||||
declare module '/wasm/neptune_wasm.js' {
|
||||
export default function init(): Promise<void>
|
||||
|
||||
export function generate_seed(): string
|
||||
export function get_viewkey(seedPhraseJson: string, network: string): string
|
||||
export function address_from_seed(seedPhraseJson: string, network: string): string
|
||||
export function validate_seed_phrase(seedPhraseJson: string): boolean
|
||||
export function decode_viewkey(viewKeyHex: string): string
|
||||
export function get_balance(rpcUrl: string): Promise<string>
|
||||
export function get_block_height(rpcUrl: string): Promise<number>
|
||||
export function get_network_info(rpcUrl: string): Promise<string>
|
||||
export function get_wallet_address(rpcUrl: string): Promise<string>
|
||||
export function get_utxos_from_viewkey(
|
||||
rpcUrl: string,
|
||||
viewKeyHex: string,
|
||||
startBlock: number,
|
||||
endBlock: number | null
|
||||
): Promise<string>
|
||||
export function build_and_sign_tx(requestJson: string): string
|
||||
export function send_tx_jsonrpc(
|
||||
rpcUrl: string,
|
||||
toAddress: string,
|
||||
amount: string,
|
||||
fee: string
|
||||
): Promise<string>
|
||||
}
|
||||
|
||||
// Global type definitions
|
||||
declare global {
|
||||
interface Window {
|
||||
neptuneWasm?: any
|
||||
}
|
||||
}
|
||||
|
||||
export {}
|
||||
@ -1,142 +1 @@
|
||||
// BIP39 English wordlist (first 100 words for demo)
|
||||
const BIP39_WORDS = [
|
||||
'abandon',
|
||||
'ability',
|
||||
'able',
|
||||
'about',
|
||||
'above',
|
||||
'absent',
|
||||
'absorb',
|
||||
'abstract',
|
||||
'absurd',
|
||||
'abuse',
|
||||
'access',
|
||||
'accident',
|
||||
'account',
|
||||
'accuse',
|
||||
'achieve',
|
||||
'acid',
|
||||
'acoustic',
|
||||
'acquire',
|
||||
'across',
|
||||
'act',
|
||||
'action',
|
||||
'actor',
|
||||
'actress',
|
||||
'actual',
|
||||
'adapt',
|
||||
'add',
|
||||
'addict',
|
||||
'address',
|
||||
'adjust',
|
||||
'admit',
|
||||
'adult',
|
||||
'advance',
|
||||
'advice',
|
||||
'aerobic',
|
||||
'affair',
|
||||
'afford',
|
||||
'afraid',
|
||||
'again',
|
||||
'age',
|
||||
'agent',
|
||||
'agree',
|
||||
'ahead',
|
||||
'aim',
|
||||
'air',
|
||||
'airport',
|
||||
'aisle',
|
||||
'alarm',
|
||||
'album',
|
||||
'alcohol',
|
||||
'alert',
|
||||
'alien',
|
||||
'all',
|
||||
'alley',
|
||||
'allow',
|
||||
'almost',
|
||||
'alone',
|
||||
'alpha',
|
||||
'already',
|
||||
'also',
|
||||
'alter',
|
||||
'always',
|
||||
'amateur',
|
||||
'amazing',
|
||||
'among',
|
||||
'amount',
|
||||
'amused',
|
||||
'analyst',
|
||||
'anchor',
|
||||
'ancient',
|
||||
'anger',
|
||||
'angle',
|
||||
'angry',
|
||||
'animal',
|
||||
'ankle',
|
||||
'announce',
|
||||
'annual',
|
||||
'another',
|
||||
'answer',
|
||||
'antenna',
|
||||
'antique',
|
||||
'anxiety',
|
||||
'any',
|
||||
'apart',
|
||||
'apology',
|
||||
'appear',
|
||||
'apple',
|
||||
'approve',
|
||||
'april',
|
||||
'arch',
|
||||
'arctic',
|
||||
'area',
|
||||
'arena',
|
||||
'argue',
|
||||
'arm',
|
||||
'armed',
|
||||
'armor',
|
||||
'army',
|
||||
'around',
|
||||
'arrange',
|
||||
'arrest',
|
||||
]
|
||||
|
||||
/**
|
||||
* Generate a random seed phrase with 12 words
|
||||
* In a real application, you would use a proper BIP39 library
|
||||
*/
|
||||
export const generateSeedPhrase = (): string[] => {
|
||||
const words: string[] = []
|
||||
|
||||
for (let i = 0; i < 12; i++) {
|
||||
const randomIndex = Math.floor(Math.random() * BIP39_WORDS.length)
|
||||
words.push(BIP39_WORDS[randomIndex])
|
||||
}
|
||||
|
||||
return words
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate if a seed phrase is valid (basic validation)
|
||||
*/
|
||||
export const validateSeedPhrase = (words: string[]): boolean => {
|
||||
if (words.length !== 12) return false
|
||||
|
||||
// Check if all words are in the BIP39 wordlist
|
||||
return words.every((word) => BIP39_WORDS.includes(word.toLowerCase()))
|
||||
}
|
||||
|
||||
export const validateSeedPhrase18 = (words: string[]): boolean => {
|
||||
if (words.length !== 18) return false
|
||||
return words.every((word) => BIP39_WORDS.includes(word.toLowerCase()))
|
||||
}
|
||||
|
||||
export const generateSeedPhrase18 = (): string[] => {
|
||||
const words: string[] = []
|
||||
for (let i = 0; i < 18; i++) {
|
||||
const randomIndex = Math.floor(Math.random() * BIP39_WORDS.length)
|
||||
words.push(BIP39_WORDS[randomIndex])
|
||||
}
|
||||
return words
|
||||
}
|
||||
export const validateSeedPhrase18 = (words: string[]): boolean => words.length !== 18
|
||||
|
||||
@ -1,30 +1,19 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { ImportWalletComponent, OpenWalletComponent } from '.'
|
||||
import { ImportWalletComponent } from '.'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
const emit = defineEmits<{ goToCreate: [] }>()
|
||||
|
||||
const stage = ref<'import' | 'password'>('import')
|
||||
const importData = ref<any>(null)
|
||||
const router = useRouter()
|
||||
|
||||
const handleImported = (payload: { type: 'seed' | 'keystore'; value: string | string[] }) => {
|
||||
importData.value = payload
|
||||
stage.value = 'password'
|
||||
}
|
||||
|
||||
const handleNavigateToCreate = () => {
|
||||
emit('goToCreate')
|
||||
if (payload.type === 'keystore') {
|
||||
localStorage.setItem('temp_keystore', JSON.stringify(payload.value))
|
||||
return router.push({ name: 'password' })
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<div class="login-tab">
|
||||
<ImportWalletComponent v-if="stage === 'import'" @import-success="handleImported" />
|
||||
<OpenWalletComponent
|
||||
v-else
|
||||
:import-data="importData"
|
||||
@navigateToCreate="handleNavigateToCreate"
|
||||
@back="stage = 'import'"
|
||||
/>
|
||||
<ImportWalletComponent @import-success="handleImported" />
|
||||
</div>
|
||||
</template>
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@ -34,7 +34,7 @@ const handleGoToLogin = () => {
|
||||
Open existing wallet
|
||||
</ButtonCommon>
|
||||
</div>
|
||||
<div class="note">Thank you for being a part of the Kaspa community!</div>
|
||||
<div class="note">Thank you for being a part of the Neptune community!</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -1,424 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { message } from 'ant-design-vue'
|
||||
import { ButtonCommon, FormCommon } from '@/components'
|
||||
import { useNeptuneWallet } from '@/composables/useNeptuneWallet'
|
||||
|
||||
interface ImportData {
|
||||
type: 'seed' | 'keystore'
|
||||
value: string | string[]
|
||||
}
|
||||
|
||||
const props = defineProps<{
|
||||
importData?: ImportData | null
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
navigateToCreate: []
|
||||
back: []
|
||||
}>()
|
||||
|
||||
const router = useRouter()
|
||||
const { importWallet, importFromViewKey } = useNeptuneWallet()
|
||||
|
||||
const password = ref('')
|
||||
const passwordError = ref('')
|
||||
const isLoading = ref(false)
|
||||
|
||||
const handleOpenWallet = async () => {
|
||||
if (!password.value) {
|
||||
passwordError.value = 'Please enter your password'
|
||||
return
|
||||
}
|
||||
|
||||
if (!props.importData) {
|
||||
passwordError.value = 'No import data available'
|
||||
return
|
||||
}
|
||||
|
||||
isLoading.value = true
|
||||
passwordError.value = ''
|
||||
|
||||
try {
|
||||
// TODO: Use password to decrypt the wallet data
|
||||
// For now, we'll just import directly
|
||||
|
||||
if (props.importData.type === 'seed') {
|
||||
const seedPhrase = Array.isArray(props.importData.value)
|
||||
? props.importData.value
|
||||
: props.importData.value.split(' ')
|
||||
|
||||
await importWallet(seedPhrase, 'testnet')
|
||||
message.success('Wallet imported successfully from seed phrase!')
|
||||
} else if (props.importData.type === 'keystore') {
|
||||
// Assume keystore contains the view key hex string
|
||||
const viewKey = typeof props.importData.value === 'string'
|
||||
? props.importData.value
|
||||
: props.importData.value.join('')
|
||||
|
||||
await importFromViewKey(viewKey)
|
||||
message.success('Wallet imported successfully from keystore!')
|
||||
}
|
||||
|
||||
// Navigate to home after successful import
|
||||
router.push('/')
|
||||
} catch (error) {
|
||||
const errorMsg = error instanceof Error ? error.message : 'Failed to import wallet'
|
||||
passwordError.value = errorMsg
|
||||
message.error(errorMsg)
|
||||
} finally {
|
||||
isLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const navigateToNewWallet = () => {
|
||||
emit('navigateToCreate')
|
||||
}
|
||||
|
||||
const handleBack = () => {
|
||||
emit('back')
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="auth-container">
|
||||
<div class="auth-card">
|
||||
<div class="auth-card-content">
|
||||
<div class="wallet-icon">
|
||||
<div class="logo-container">
|
||||
<div class="logo-circle">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 100 100"
|
||||
class="neptune-logo"
|
||||
>
|
||||
<defs>
|
||||
<linearGradient
|
||||
id="neptuneGradient"
|
||||
x1="0%"
|
||||
y1="0%"
|
||||
x2="100%"
|
||||
y2="100%"
|
||||
>
|
||||
<stop
|
||||
offset="0%"
|
||||
style="stop-color: #007fcf; stop-opacity: 1"
|
||||
/>
|
||||
<stop
|
||||
offset="100%"
|
||||
style="stop-color: #0066a6; stop-opacity: 1"
|
||||
/>
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="ringGradient"
|
||||
x1="0%"
|
||||
y1="0%"
|
||||
x2="100%"
|
||||
y2="0%"
|
||||
>
|
||||
<stop
|
||||
offset="0%"
|
||||
style="stop-color: #007fcf; stop-opacity: 0.3"
|
||||
/>
|
||||
<stop
|
||||
offset="50%"
|
||||
style="stop-color: #007fcf; stop-opacity: 0.6"
|
||||
/>
|
||||
<stop
|
||||
offset="100%"
|
||||
style="stop-color: #007fcf; stop-opacity: 0.3"
|
||||
/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
|
||||
<circle cx="50" cy="50" r="28" fill="url(#neptuneGradient)" />
|
||||
|
||||
<ellipse
|
||||
cx="50"
|
||||
cy="45"
|
||||
rx="22"
|
||||
ry="6"
|
||||
fill="rgba(255, 255, 255, 0.1)"
|
||||
/>
|
||||
<ellipse cx="50" cy="55" rx="20" ry="5" fill="rgba(0, 0, 0, 0.1)" />
|
||||
|
||||
<ellipse
|
||||
cx="50"
|
||||
cy="50"
|
||||
rx="42"
|
||||
ry="12"
|
||||
fill="none"
|
||||
stroke="url(#ringGradient)"
|
||||
stroke-width="4"
|
||||
opacity="0.8"
|
||||
/>
|
||||
|
||||
<circle cx="42" cy="42" r="6" fill="rgba(255, 255, 255, 0.4)" />
|
||||
</svg>
|
||||
</div>
|
||||
<div class="logo-text">
|
||||
<span class="coin-name">Neptune</span>
|
||||
<span class="coin-symbol">NPTUN</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="import-info" v-if="importData">
|
||||
<div class="info-label">Importing from:</div>
|
||||
<div class="info-value">{{ importData.type === 'seed' ? 'Seed Phrase' : 'Keystore' }}</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<FormCommon
|
||||
v-model="password"
|
||||
type="password"
|
||||
label="Create a password to encrypt your wallet"
|
||||
placeholder="Enter password"
|
||||
show-password-toggle
|
||||
required
|
||||
:error="passwordError"
|
||||
:disabled="isLoading"
|
||||
@input="passwordError = ''"
|
||||
@keyup.enter="handleOpenWallet"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="security-notice">
|
||||
<div class="notice-icon">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none">
|
||||
<path
|
||||
d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
/>
|
||||
<path
|
||||
d="M9 12l2 2 4-4"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<span>Your password is encrypted and stored locally</span>
|
||||
</div>
|
||||
|
||||
<div class="auth-button-group">
|
||||
<ButtonCommon
|
||||
type="primary"
|
||||
size="large"
|
||||
block
|
||||
:disabled="!password || isLoading"
|
||||
:loading="isLoading"
|
||||
@click="handleOpenWallet"
|
||||
>
|
||||
{{ isLoading ? 'Importing...' : 'Import Wallet' }}
|
||||
</ButtonCommon>
|
||||
<div class="button-row">
|
||||
<ButtonCommon type="default" size="large" @click="handleBack" :disabled="isLoading">
|
||||
Back
|
||||
</ButtonCommon>
|
||||
<ButtonCommon type="default" size="large" @click="navigateToNewWallet" :disabled="isLoading">
|
||||
New Wallet
|
||||
</ButtonCommon>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.auth-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: var(--spacing-xl);
|
||||
background: var(--bg-light);
|
||||
}
|
||||
|
||||
.auth-card {
|
||||
@include card-base;
|
||||
max-width: 420px;
|
||||
width: 100%;
|
||||
|
||||
@include screen(mobile) {
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.auth-card-content {
|
||||
.import-info {
|
||||
text-align: center;
|
||||
padding: var(--spacing-md);
|
||||
background: var(--primary-light);
|
||||
border-radius: var(--radius-md);
|
||||
margin-bottom: var(--spacing-lg);
|
||||
|
||||
.info-label {
|
||||
font-size: var(--font-sm);
|
||||
color: var(--text-secondary);
|
||||
margin-bottom: var(--spacing-xs);
|
||||
}
|
||||
|
||||
.info-value {
|
||||
font-size: var(--font-md);
|
||||
font-weight: var(--font-semibold);
|
||||
color: var(--primary-color);
|
||||
}
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: var(--spacing-xl);
|
||||
}
|
||||
}
|
||||
|
||||
.logo-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: var(--spacing-md);
|
||||
margin-bottom: var(--spacing-lg);
|
||||
|
||||
.logo-circle {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border-radius: 50%;
|
||||
background: linear-gradient(135deg, var(--primary-light), var(--bg-white));
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: var(--spacing-sm);
|
||||
box-shadow: 0 2px 8px rgba(0, 127, 207, 0.15);
|
||||
|
||||
.neptune-logo {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.logo-text {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
|
||||
.coin-name {
|
||||
font-size: var(--font-lg);
|
||||
font-weight: var(--font-bold);
|
||||
color: var(--text-primary);
|
||||
line-height: 1;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.coin-symbol {
|
||||
font-size: var(--font-xs);
|
||||
font-weight: var(--font-medium);
|
||||
color: var(--primary-color);
|
||||
background: var(--primary-light);
|
||||
padding: 2px 8px;
|
||||
border-radius: var(--radius-sm);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.security-notice {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-sm);
|
||||
padding: var(--spacing-md);
|
||||
background: var(--bg-light);
|
||||
border-radius: var(--radius-md);
|
||||
border: 1px solid var(--border-light);
|
||||
margin-bottom: var(--spacing-xl);
|
||||
|
||||
.notice-icon {
|
||||
color: var(--success-color);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
span {
|
||||
font-size: var(--font-xs);
|
||||
color: var(--text-secondary);
|
||||
line-height: var(--leading-normal);
|
||||
}
|
||||
}
|
||||
|
||||
.auth-button-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-md);
|
||||
margin-bottom: var(--spacing-xl);
|
||||
|
||||
.button-row {
|
||||
display: flex;
|
||||
gap: var(--spacing-md);
|
||||
|
||||
> * {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Help Links
|
||||
.help-links {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: var(--spacing-sm);
|
||||
padding-top: var(--spacing-lg);
|
||||
border-top: 1px solid var(--border-color);
|
||||
|
||||
.link-button {
|
||||
background: none;
|
||||
border: none;
|
||||
color: var(--primary-color);
|
||||
font-size: var(--font-sm);
|
||||
cursor: pointer;
|
||||
transition: color 0.2s ease;
|
||||
padding: 0;
|
||||
|
||||
&:hover:not(:disabled) {
|
||||
color: var(--primary-hover);
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
color: var(--text-muted);
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
|
||||
.separator {
|
||||
color: var(--text-muted);
|
||||
font-size: var(--font-sm);
|
||||
}
|
||||
}
|
||||
|
||||
// Responsive Design
|
||||
@include screen(mobile) {
|
||||
.auth-container {
|
||||
padding: var(--spacing-md);
|
||||
}
|
||||
|
||||
.logo-container {
|
||||
.logo-circle {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
.logo-text {
|
||||
.coin-name {
|
||||
font-size: var(--font-md);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.wallet-icon {
|
||||
.icon-circle {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
328
src/views/Auth/components/PasswordKeystoreComponent.vue
Normal file
328
src/views/Auth/components/PasswordKeystoreComponent.vue
Normal file
@ -0,0 +1,328 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from 'vue'
|
||||
import { ButtonCommon, FormCommon } from '@/components'
|
||||
import { useNeptuneWallet } from '@/composables/useNeptuneWallet'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
const emit = defineEmits<{
|
||||
success: []
|
||||
error: [message: string]
|
||||
}>()
|
||||
|
||||
const router = useRouter()
|
||||
const { importFromKeystore } = useNeptuneWallet()
|
||||
|
||||
const password = ref('')
|
||||
const passwordError = ref('')
|
||||
const isLoading = ref(false)
|
||||
|
||||
const passwordValidation = computed(() => {
|
||||
if (!password.value) return { isValid: false, message: '' }
|
||||
|
||||
const checks = {
|
||||
length: password.value.length >= 8,
|
||||
uppercase: /[A-Z]/.test(password.value),
|
||||
lowercase: /[a-z]/.test(password.value),
|
||||
number: /[0-9]/.test(password.value),
|
||||
special: /[!@#$%^&*(),.?":{}|<>]/.test(password.value),
|
||||
}
|
||||
|
||||
if (!checks.length) return { isValid: false, message: 'Password must be at least 8 characters' }
|
||||
if (!checks.uppercase)
|
||||
return { isValid: false, message: 'Password must contain uppercase letter' }
|
||||
if (!checks.lowercase)
|
||||
return { isValid: false, message: 'Password must contain lowercase letter' }
|
||||
if (!checks.number) return { isValid: false, message: 'Password must contain number' }
|
||||
if (!checks.special)
|
||||
return { isValid: false, message: 'Password must contain special character' }
|
||||
|
||||
return { isValid: true, message: 'Password format is valid' }
|
||||
})
|
||||
|
||||
const canProceed = computed(() => {
|
||||
return password.value.length > 0 && passwordValidation.value.isValid
|
||||
})
|
||||
|
||||
const handleSubmit = async () => {
|
||||
if (!canProceed.value) {
|
||||
if (password.value.length > 0 && !passwordValidation.value.isValid) {
|
||||
passwordError.value = passwordValidation.value.message
|
||||
} else {
|
||||
passwordError.value = 'Please enter your password'
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
isLoading.value = true
|
||||
passwordError.value = ''
|
||||
|
||||
const keystore = localStorage.getItem('temp_keystore')
|
||||
if (!keystore) {
|
||||
throw new Error('No keystore found. Please try importing again.')
|
||||
}
|
||||
|
||||
await importFromKeystore(keystore, password.value)
|
||||
|
||||
localStorage.removeItem('temp_keystore')
|
||||
|
||||
router.push({ name: 'home' })
|
||||
emit('success')
|
||||
} catch (err) {
|
||||
const errorMsg = err instanceof Error ? err.message : 'Failed to access wallet'
|
||||
passwordError.value = errorMsg
|
||||
emit('error', errorMsg)
|
||||
} finally {
|
||||
isLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const handleBack = () => {
|
||||
router.back()
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="password-keystore-step">
|
||||
<div class="auth-card-header">
|
||||
<div class="logo-container">
|
||||
<div class="logo-circle">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 100 100"
|
||||
class="neptune-logo"
|
||||
>
|
||||
<defs>
|
||||
<linearGradient
|
||||
id="neptuneGradient"
|
||||
x1="0%"
|
||||
y1="0%"
|
||||
x2="100%"
|
||||
y2="100%"
|
||||
>
|
||||
<stop offset="0%" style="stop-color: #007fcf; stop-opacity: 1" />
|
||||
<stop offset="100%" style="stop-color: #0066a6; stop-opacity: 1" />
|
||||
</linearGradient>
|
||||
<linearGradient id="ringGradient" x1="0%" y1="0%" x2="100%" y2="0%">
|
||||
<stop offset="0%" style="stop-color: #007fcf; stop-opacity: 0.3" />
|
||||
<stop offset="50%" style="stop-color: #007fcf; stop-opacity: 0.6" />
|
||||
<stop
|
||||
offset="100%"
|
||||
style="stop-color: #007fcf; stop-opacity: 0.3"
|
||||
/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
|
||||
<circle cx="50" cy="50" r="28" fill="url(#neptuneGradient)" />
|
||||
|
||||
<ellipse cx="50" cy="45" rx="22" ry="6" fill="rgba(255, 255, 255, 0.1)" />
|
||||
<ellipse cx="50" cy="55" rx="20" ry="5" fill="rgba(0, 0, 0, 0.1)" />
|
||||
|
||||
<ellipse
|
||||
cx="50"
|
||||
cy="50"
|
||||
rx="42"
|
||||
ry="12"
|
||||
fill="none"
|
||||
stroke="url(#ringGradient)"
|
||||
stroke-width="4"
|
||||
opacity="0.8"
|
||||
/>
|
||||
|
||||
<circle cx="42" cy="42" r="6" fill="rgba(255, 255, 255, 0.4)" />
|
||||
</svg>
|
||||
</div>
|
||||
<div class="logo-text">
|
||||
<span class="coin-name">Neptune</span>
|
||||
<span class="coin-symbol">NPTUN</span>
|
||||
</div>
|
||||
</div>
|
||||
<h1 class="auth-title">Access Wallet</h1>
|
||||
<p class="auth-subtitle">Enter your keystore password to unlock your wallet</p>
|
||||
</div>
|
||||
|
||||
<div class="auth-card-content">
|
||||
<div class="form-group">
|
||||
<FormCommon
|
||||
v-model="password"
|
||||
type="password"
|
||||
label="Keystore Password"
|
||||
placeholder="Enter your keystore password"
|
||||
show-password-toggle
|
||||
required
|
||||
:error="passwordError"
|
||||
@input="passwordError = ''"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<p class="helper-text">
|
||||
Password must be at least 8 characters with uppercase, lowercase, numbers, and
|
||||
special characters.
|
||||
</p>
|
||||
|
||||
<div class="auth-button-group">
|
||||
<ButtonCommon
|
||||
type="primary"
|
||||
size="large"
|
||||
class="auth-button"
|
||||
block
|
||||
:disabled="!canProceed || isLoading"
|
||||
:loading="isLoading"
|
||||
@click="handleSubmit"
|
||||
>
|
||||
Access Wallet
|
||||
</ButtonCommon>
|
||||
<div class="secondary-actions">
|
||||
<button class="link-button" @click="handleBack">Back to Import</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.password-keystore-step {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.auth-card-header {
|
||||
text-align: center;
|
||||
margin-bottom: var(--spacing-2xl);
|
||||
padding-bottom: var(--spacing-xl);
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
|
||||
.logo-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: var(--spacing-md);
|
||||
margin-bottom: var(--spacing-lg);
|
||||
|
||||
.logo-circle {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border-radius: 50%;
|
||||
background: linear-gradient(135deg, var(--primary-light), var(--bg-white));
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: var(--spacing-sm);
|
||||
box-shadow: 0 2px 8px rgba(0, 127, 207, 0.15);
|
||||
|
||||
.neptune-logo {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.logo-text {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
|
||||
.coin-name {
|
||||
font-size: var(--font-lg);
|
||||
font-weight: var(--font-bold);
|
||||
color: var(--text-primary);
|
||||
line-height: 1;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.coin-symbol {
|
||||
font-size: var(--font-xs);
|
||||
font-weight: var(--font-medium);
|
||||
color: var(--primary-color);
|
||||
background: var(--primary-light);
|
||||
padding: 2px 8px;
|
||||
border-radius: var(--radius-sm);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.auth-title {
|
||||
font-size: var(--font-2xl);
|
||||
font-weight: var(--font-bold);
|
||||
color: var(--text-primary);
|
||||
margin-bottom: var(--spacing-xs);
|
||||
}
|
||||
|
||||
.auth-subtitle {
|
||||
font-size: var(--font-sm);
|
||||
color: var(--text-secondary);
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.auth-card-content {
|
||||
.form-group {
|
||||
margin-bottom: var(--spacing-xl);
|
||||
}
|
||||
}
|
||||
|
||||
.helper-text {
|
||||
font-size: var(--font-xs);
|
||||
color: var(--text-muted);
|
||||
margin: 0 0 var(--spacing-xl);
|
||||
line-height: var(--leading-normal);
|
||||
}
|
||||
|
||||
.auth-button {
|
||||
width: fit-content;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.auth-button-group {
|
||||
margin-top: var(--spacing-2xl);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.secondary-actions {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: var(--spacing-sm);
|
||||
margin-top: var(--spacing-md);
|
||||
}
|
||||
|
||||
.link-button {
|
||||
background: none;
|
||||
border: none;
|
||||
color: var(--primary-color);
|
||||
font-size: var(--font-sm);
|
||||
cursor: pointer;
|
||||
transition: color 0.2s ease;
|
||||
padding: 0;
|
||||
|
||||
&:hover {
|
||||
color: var(--primary-hover);
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
.separator {
|
||||
color: var(--text-muted);
|
||||
font-size: var(--font-sm);
|
||||
}
|
||||
}
|
||||
|
||||
@include screen(mobile) {
|
||||
.auth-card-header {
|
||||
.logo-container {
|
||||
.logo-circle {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
.logo-text {
|
||||
.coin-name {
|
||||
font-size: var(--font-md);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.auth-title {
|
||||
font-size: var(--font-xl);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -1,6 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { validateSeedPhrase18 } from '@/utils'
|
||||
import { ref } from 'vue'
|
||||
import { validateSeedPhrase18 } from '@/utils/helpers/seedPhrase'
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:valid', valid: boolean): void
|
||||
@ -12,7 +12,7 @@ const seedError = ref('')
|
||||
|
||||
const updateValidity = () => {
|
||||
const words = seedWords.value.filter((w) => w.trim())
|
||||
const isValid = words.length === 18 && !seedError.value
|
||||
const isValid = validateSeedPhrase18(words) && !seedError.value
|
||||
emit('update:valid', isValid)
|
||||
}
|
||||
|
||||
@ -42,23 +42,16 @@ const handlePaste = (event: ClipboardEvent) => {
|
||||
|
||||
const validateSeed = () => {
|
||||
const words = seedWords.value.filter((w) => w.trim())
|
||||
|
||||
if (words.length !== 18) {
|
||||
seedError.value = 'Please enter all 18 words.'
|
||||
return false
|
||||
}
|
||||
if (!validateSeedPhrase18(words)) {
|
||||
seedError.value = 'One or more words are invalid.'
|
||||
return false
|
||||
}
|
||||
seedError.value = ''
|
||||
return true
|
||||
return validateSeedPhrase18(words)
|
||||
}
|
||||
|
||||
const handleSubmit = () => {
|
||||
if (validateSeed()) {
|
||||
emit('submit', seedWords.value.filter((w) => w.trim()))
|
||||
}
|
||||
if (validateSeed()) seedError.value = ''
|
||||
emit(
|
||||
'submit',
|
||||
seedWords.value.filter((w) => w.trim())
|
||||
)
|
||||
|
||||
updateValidity()
|
||||
}
|
||||
|
||||
@ -91,7 +84,7 @@ defineExpose({
|
||||
:class="{ error: seedError && !word.trim() }"
|
||||
@focus="seedError = ''"
|
||||
@input="handleGridInput(i, ($event.target as HTMLInputElement).value)"
|
||||
@paste="handlePaste($event, i)"
|
||||
@paste="handlePaste($event)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@ -168,4 +161,3 @@ defineExpose({
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
@ -8,7 +8,6 @@ export { default as KeystoreTab } from './KeystoreTab.vue'
|
||||
|
||||
// Auth Components
|
||||
export { default as OnboardingComponent } from './OnboardingComponent.vue'
|
||||
export { default as OpenWalletComponent } from './OpenWalletComponent.vue'
|
||||
export { default as CreateWalletComponent } from './CreateWalletComponent.vue'
|
||||
export { default as RecoverySeedComponent } from './RecoverySeedComponent.vue'
|
||||
export { default as ConfirmSeedComponent } from './ConfirmSeedComponent.vue'
|
||||
|
||||
@ -5,7 +5,7 @@ import WalletInfo from '@/components/WalletInfo.vue'
|
||||
import { WalletTab, NetworkTab, UTXOTab } from './components'
|
||||
|
||||
const activeTab = ref('UTXOs')
|
||||
const network = ref('kaspa-mainnet')
|
||||
const network = ref('neptune-mainnet')
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
49
src/views/PasswordKeystore/PasswordKeystoreView.vue
Normal file
49
src/views/PasswordKeystore/PasswordKeystoreView.vue
Normal file
@ -0,0 +1,49 @@
|
||||
<script setup lang="ts">
|
||||
import PasswordKeystoreComponent from '@/views/Auth/components/PasswordKeystoreComponent.vue'
|
||||
|
||||
const handleSuccess = () => {
|
||||
console.log('Wallet accessed successfully')
|
||||
}
|
||||
|
||||
const handleError = (message: string) => {
|
||||
console.error('Error accessing wallet:', message)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="password-keystore-view">
|
||||
<div class="auth-container">
|
||||
<PasswordKeystoreComponent @success="handleSuccess" @error="handleError" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.password-keystore-view {
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: var(--bg-primary);
|
||||
padding: var(--spacing-lg);
|
||||
}
|
||||
|
||||
.auth-container {
|
||||
width: 100%;
|
||||
max-width: 480px;
|
||||
background: var(--bg-white);
|
||||
border-radius: var(--radius-xl);
|
||||
box-shadow: var(--shadow-primary);
|
||||
padding: var(--spacing-2xl);
|
||||
}
|
||||
|
||||
@include screen(mobile) {
|
||||
.password-keystore-view {
|
||||
padding: var(--spacing-md);
|
||||
}
|
||||
|
||||
.auth-container {
|
||||
padding: var(--spacing-xl);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -1,3 +1,4 @@
|
||||
export const Home = () => import('@/views/Home/HomeView.vue')
|
||||
export const NotFound = () => import('@/views/NotFound/NotFoundView.vue')
|
||||
export const Auth = () => import('@/views/Auth/AuthView.vue')
|
||||
export const PasswordKeystore = () => import('@/views/PasswordKeystore/PasswordKeystoreView.vue')
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"extends": "@vue/tsconfig/tsconfig.dom.json",
|
||||
"include": ["env.d.ts", "src/**/*", "src/**/*.vue"],
|
||||
"include": ["env.d.ts", "src/**/*", "src/**/*.vue", "electron/*"],
|
||||
"exclude": ["src/**/__tests__/*"],
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"extends": "@tsconfig/node20/tsconfig.json",
|
||||
"include": ["vite.config.*", "vitest.config.*"],
|
||||
"include": ["vite.config.*", "vitest.config.*", "vite.config.ts", "env.d.ts"],
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"noEmit": true,
|
||||
|
||||
@ -8,6 +8,7 @@ export default defineConfig({
|
||||
server: {
|
||||
port: 3008,
|
||||
},
|
||||
base: './',
|
||||
plugins: [vue(), vueJsx(), VueDevTools()],
|
||||
optimizeDeps: {
|
||||
exclude: ['@neptune/wasm'],
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user