| import { EventEmitter } from 'events'; | ||
| import { Server } from 'net'; | ||
| interface VoteOptions { | ||
| username: string; | ||
| address: string; | ||
| serviceName?: string; | ||
| timestamp?: number; | ||
| additionalData?: string | Buffer; | ||
| } | ||
| interface VotifierPayload { | ||
| serviceName: string; | ||
| username: string; | ||
| address: string; | ||
| timestamp: number; | ||
| challenge: string; | ||
| additionalData?: string; | ||
| } | ||
| interface VotifierMessage { | ||
| payload: string; | ||
| signature: string; | ||
| } | ||
| type VotifierResponse = { | ||
| status: 'error'; | ||
| cause: string; | ||
| error: string; | ||
| } | { | ||
| status: 'ok'; | ||
| }; | ||
| interface VotifierClientOptions { | ||
| token: string; | ||
| serviceName: string; | ||
| host: string; | ||
| /** Defaults to 8192 */ | ||
| port?: number; | ||
| protocol?: 2; | ||
| debug?: boolean; | ||
| } | ||
| declare class VotifierClient { | ||
| readonly host: string; | ||
| readonly port: number; | ||
| readonly token: string; | ||
| readonly protocolVersion: number; | ||
| serviceName: string; | ||
| readonly debug: boolean; | ||
| constructor(options: VotifierClientOptions); | ||
| private getToken; | ||
| sendVote(vote: VoteOptions): Promise<void>; | ||
| } | ||
| declare const magicV2 = 29498; | ||
| declare class TokenManager { | ||
| private readonly tokenPath; | ||
| private tokens; | ||
| constructor(tokenPath: string); | ||
| private load; | ||
| private writeTokens; | ||
| getToken(serviceName: string): string | undefined; | ||
| setToken(serviceName: string, token: string): void; | ||
| generateToken(): string; | ||
| } | ||
| interface VotifierServerOptions { | ||
| /** Path of the token file. Defaults to 'tokens.json' */ | ||
| tokenPath?: string; | ||
| /** Port to listen vote requests. Defaults to 8192 */ | ||
| port?: number; | ||
| } | ||
| declare class VotifierServer extends EventEmitter { | ||
| readonly server: Server; | ||
| readonly options: VotifierServerOptions; | ||
| readonly protocolVersion: number; | ||
| readonly tokenManager: TokenManager; | ||
| constructor(options?: VotifierServerOptions); | ||
| private handleConnection; | ||
| private decode; | ||
| start(): void; | ||
| stop(): void; | ||
| } | ||
| interface VotifierServer { | ||
| on(event: 'listen', listener: () => void): this; | ||
| on(event: 'vote', listener: (vote: VotifierPayload) => void): this; | ||
| on(event: 'error', listener: (error: any) => void): this; | ||
| } | ||
| export { TokenManager, type VoteOptions, VotifierClient, type VotifierClientOptions, type VotifierMessage, type VotifierPayload, type VotifierResponse, VotifierServer, type VotifierServerOptions, magicV2 }; |
| import { EventEmitter } from 'events'; | ||
| import { Server } from 'net'; | ||
| interface VoteOptions { | ||
| username: string; | ||
| address: string; | ||
| serviceName?: string; | ||
| timestamp?: number; | ||
| additionalData?: string | Buffer; | ||
| } | ||
| interface VotifierPayload { | ||
| serviceName: string; | ||
| username: string; | ||
| address: string; | ||
| timestamp: number; | ||
| challenge: string; | ||
| additionalData?: string; | ||
| } | ||
| interface VotifierMessage { | ||
| payload: string; | ||
| signature: string; | ||
| } | ||
| type VotifierResponse = { | ||
| status: 'error'; | ||
| cause: string; | ||
| error: string; | ||
| } | { | ||
| status: 'ok'; | ||
| }; | ||
| interface VotifierClientOptions { | ||
| token: string; | ||
| serviceName: string; | ||
| host: string; | ||
| /** Defaults to 8192 */ | ||
| port?: number; | ||
| protocol?: 2; | ||
| debug?: boolean; | ||
| } | ||
| declare class VotifierClient { | ||
| readonly host: string; | ||
| readonly port: number; | ||
| readonly token: string; | ||
| readonly protocolVersion: number; | ||
| serviceName: string; | ||
| readonly debug: boolean; | ||
| constructor(options: VotifierClientOptions); | ||
| private getToken; | ||
| sendVote(vote: VoteOptions): Promise<void>; | ||
| } | ||
| declare const magicV2 = 29498; | ||
| declare class TokenManager { | ||
| private readonly tokenPath; | ||
| private tokens; | ||
| constructor(tokenPath: string); | ||
| private load; | ||
| private writeTokens; | ||
| getToken(serviceName: string): string | undefined; | ||
| setToken(serviceName: string, token: string): void; | ||
| generateToken(): string; | ||
| } | ||
| interface VotifierServerOptions { | ||
| /** Path of the token file. Defaults to 'tokens.json' */ | ||
| tokenPath?: string; | ||
| /** Port to listen vote requests. Defaults to 8192 */ | ||
| port?: number; | ||
| } | ||
| declare class VotifierServer extends EventEmitter { | ||
| readonly server: Server; | ||
| readonly options: VotifierServerOptions; | ||
| readonly protocolVersion: number; | ||
| readonly tokenManager: TokenManager; | ||
| constructor(options?: VotifierServerOptions); | ||
| private handleConnection; | ||
| private decode; | ||
| start(): void; | ||
| stop(): void; | ||
| } | ||
| interface VotifierServer { | ||
| on(event: 'listen', listener: () => void): this; | ||
| on(event: 'vote', listener: (vote: VotifierPayload) => void): this; | ||
| on(event: 'error', listener: (error: any) => void): this; | ||
| } | ||
| export { TokenManager, type VoteOptions, VotifierClient, type VotifierClientOptions, type VotifierMessage, type VotifierPayload, type VotifierResponse, VotifierServer, type VotifierServerOptions, magicV2 }; |
+276
| "use strict"; | ||
| var __create = Object.create; | ||
| var __defProp = Object.defineProperty; | ||
| var __getOwnPropDesc = Object.getOwnPropertyDescriptor; | ||
| var __getOwnPropNames = Object.getOwnPropertyNames; | ||
| var __getProtoOf = Object.getPrototypeOf; | ||
| var __hasOwnProp = Object.prototype.hasOwnProperty; | ||
| var __export = (target, all) => { | ||
| for (var name in all) | ||
| __defProp(target, name, { get: all[name], enumerable: true }); | ||
| }; | ||
| var __copyProps = (to, from, except, desc) => { | ||
| if (from && typeof from === "object" || typeof from === "function") { | ||
| for (let key of __getOwnPropNames(from)) | ||
| if (!__hasOwnProp.call(to, key) && key !== except) | ||
| __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); | ||
| } | ||
| return to; | ||
| }; | ||
| var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( | ||
| // If the importer is in node compatibility mode or this is not an ESM | ||
| // file that has been converted to a CommonJS file using a Babel- | ||
| // compatible transform (i.e. "__esModule" has not been set), then set | ||
| // "default" to the CommonJS "module.exports" for node compatibility. | ||
| isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, | ||
| mod | ||
| )); | ||
| var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); | ||
| // src/index.ts | ||
| var index_exports = {}; | ||
| __export(index_exports, { | ||
| TokenManager: () => TokenManager, | ||
| VotifierClient: () => VotifierClient, | ||
| VotifierServer: () => VotifierServer, | ||
| magicV2: () => magicV2 | ||
| }); | ||
| module.exports = __toCommonJS(index_exports); | ||
| // src/client/client.ts | ||
| var import_node_fs = require("fs"); | ||
| var import_node_net = require("net"); | ||
| var import_node_crypto = require("crypto"); | ||
| // src/constants.ts | ||
| var magicV2 = 29498; | ||
| // src/client/client.ts | ||
| var VotifierClient = class { | ||
| host; | ||
| port = 8192; | ||
| token; | ||
| protocolVersion = 2; | ||
| serviceName; | ||
| debug = false; | ||
| constructor(options) { | ||
| this.token = options.token; | ||
| this.serviceName = options.serviceName; | ||
| this.host = options.host; | ||
| if (options.port) this.port = options.port; | ||
| if (options.protocol) this.protocolVersion = options.protocol; | ||
| if (options.debug) this.debug = options.debug; | ||
| } | ||
| getToken(path) { | ||
| if (!path) throw new Error("Token or token path is required"); | ||
| return (0, import_node_fs.readFileSync)(path, "utf-8"); | ||
| } | ||
| sendVote(vote) { | ||
| return new Promise((resolve, reject) => { | ||
| const socket = (0, import_node_net.createConnection)(this.port, this.host); | ||
| socket.setTimeout(5e3, () => { | ||
| socket.destroy(); | ||
| handleError(new Error("Connection timeout")); | ||
| }); | ||
| const handleError = (error) => { | ||
| socket.destroy(); | ||
| socket.removeAllListeners(); | ||
| reject(error); | ||
| }; | ||
| const handleGreeting = (data) => { | ||
| const greeting = data.toString(); | ||
| if (this.debug) console.log("[S->C] Greeting:", greeting); | ||
| const headers = greeting.split(" "); | ||
| if (headers.length !== 3) { | ||
| return handleError(new Error("Not a v2 protocol server")); | ||
| } | ||
| const payload = { | ||
| username: vote.username, | ||
| address: vote.address, | ||
| timestamp: vote.timestamp ?? Date.now(), | ||
| serviceName: vote.serviceName ?? this.serviceName, | ||
| challenge: headers[2].trim(), | ||
| additionalData: (typeof vote.additionalData === "string" ? Buffer.from(vote.additionalData) : vote.additionalData)?.toString("base64") | ||
| }; | ||
| if (this.debug) console.log("[C->S] Payload:", payload, payload.challenge.length); | ||
| const serializedPayload = JSON.stringify(payload); | ||
| const signature = (0, import_node_crypto.createHmac)("sha256", this.token).update(serializedPayload).digest("base64"); | ||
| const message = { | ||
| payload: serializedPayload, | ||
| signature | ||
| }; | ||
| const serializedMessage = JSON.stringify(message); | ||
| const buffer = Buffer.alloc(4 + serializedMessage.length); | ||
| buffer.writeUInt16BE(magicV2, 0); | ||
| buffer.writeUInt16BE(serializedMessage.length, 2); | ||
| buffer.write(serializedMessage, 4); | ||
| socket.write(new Uint8Array(buffer)); | ||
| socket.once("data", handleResponse); | ||
| }; | ||
| const handleResponse = (data) => { | ||
| let response; | ||
| try { | ||
| response = JSON.parse(data.toString()); | ||
| } catch (error) { | ||
| throw new Error("Failed to parse response"); | ||
| } | ||
| if (this.debug) console.log("[S->C] Response:", response); | ||
| socket.end(); | ||
| socket.removeAllListeners(); | ||
| if (response.status === "ok") { | ||
| resolve(); | ||
| } else if (response.status === "error") { | ||
| const err = new Error(response.error); | ||
| err.name = response.cause; | ||
| handleError(err); | ||
| } else { | ||
| handleError(new Error("Unknown response")); | ||
| } | ||
| }; | ||
| socket.once("data", handleGreeting); | ||
| socket.once("error", handleError); | ||
| }); | ||
| } | ||
| }; | ||
| // src/server/server.ts | ||
| var import_events = require("events"); | ||
| var import_net = require("net"); | ||
| // src/server/tokens.ts | ||
| var crypto = __toESM(require("crypto")); | ||
| var import_node_fs2 = require("fs"); | ||
| var import_node_path = require("path"); | ||
| var TokenManager = class { | ||
| tokenPath; | ||
| tokens = /* @__PURE__ */ new Map(); | ||
| constructor(tokenPath) { | ||
| this.tokenPath = tokenPath; | ||
| this.load(); | ||
| } | ||
| load() { | ||
| if ((0, import_node_fs2.existsSync)(this.tokenPath)) { | ||
| const file = (0, import_node_fs2.readFileSync)(this.tokenPath, "utf-8"); | ||
| this.tokens = new Map(Object.entries(JSON.parse(file))); | ||
| } else { | ||
| const defaultToken = this.generateToken(); | ||
| this.tokens.set("default", defaultToken); | ||
| this.writeTokens(); | ||
| console.log("-".repeat(75)); | ||
| console.log("[VotifierX]"); | ||
| console.log("No tokens were found in your tokenPath, so we've generated one for you."); | ||
| console.log("Your default Votifier token is " + defaultToken); | ||
| console.log("You will need to provide this token when you submit your server to a voting"); | ||
| console.log("list."); | ||
| console.log("-".repeat(75)); | ||
| } | ||
| } | ||
| writeTokens() { | ||
| (0, import_node_fs2.writeFileSync)(this.tokenPath, JSON.stringify(Object.fromEntries(this.tokens), null, 2)); | ||
| } | ||
| getToken(serviceName) { | ||
| return this.tokens.get(serviceName); | ||
| } | ||
| setToken(serviceName, token) { | ||
| this.tokens.set(serviceName, token); | ||
| this.writeTokens(); | ||
| } | ||
| generateToken() { | ||
| const randomBytes2 = crypto.randomBytes(16); | ||
| const bigInt = BigInt("0x" + randomBytes2.toString("hex")) + BigInt(Math.floor(Math.random() * 2 ** 14)); | ||
| return bigInt.toString(32); | ||
| } | ||
| }; | ||
| // src/server/server.ts | ||
| var import_crypto = require("crypto"); | ||
| var defaultOptions = { | ||
| tokenPath: "tokens.json", | ||
| port: 8192 | ||
| }; | ||
| var VotifierServer = class extends import_events.EventEmitter { | ||
| server = (0, import_net.createServer)(); | ||
| options; | ||
| protocolVersion = 2; | ||
| tokenManager; | ||
| constructor(options) { | ||
| super(); | ||
| this.options = { ...defaultOptions, ...options }; | ||
| this.tokenManager = new TokenManager(this.options.tokenPath); | ||
| this.server.on("connection", this.handleConnection.bind(this)); | ||
| this.server.on("error", this.emit.bind(this, "error")); | ||
| } | ||
| handleConnection(socket) { | ||
| const challenge = this.tokenManager.generateToken(); | ||
| const greeting = `VOTIFIER ${this.protocolVersion} ${challenge} | ||
| `; | ||
| socket.write(greeting); | ||
| const handleError = (error) => { | ||
| const errorResponse = { | ||
| status: "error", | ||
| error: error.message, | ||
| cause: error.name | ||
| }; | ||
| socket.write(JSON.stringify(errorResponse)); | ||
| socket.destroy(); | ||
| socket.removeAllListeners(); | ||
| this.emit("error", error); | ||
| }; | ||
| const handleData = (data) => { | ||
| let payload; | ||
| try { | ||
| payload = this.decode(data, challenge); | ||
| } catch (error) { | ||
| return handleError(error); | ||
| } | ||
| const response = { status: "ok" }; | ||
| socket.write(JSON.stringify(response)); | ||
| socket.end(); | ||
| socket.destroy(); | ||
| this.emit("vote", payload); | ||
| }; | ||
| socket.once("data", handleData); | ||
| socket.on("error", handleError); | ||
| } | ||
| decode(data, challenge) { | ||
| const magic = data.readUInt16BE(0); | ||
| if (magic !== magicV2) { | ||
| throw new Error("This server only accepts well-formed Votifier v2 packets."); | ||
| } | ||
| const messageLength = data.readUInt32BE(2); | ||
| const serializedMessage = data.subarray(4, 4 + messageLength).toString(); | ||
| const message = JSON.parse(serializedMessage); | ||
| const payload = JSON.parse(message.payload); | ||
| if (payload.challenge !== challenge) { | ||
| throw new Error("Challenge is not valid"); | ||
| } | ||
| let token = this.tokenManager.getToken(payload.serviceName); | ||
| if (!token) { | ||
| token = this.tokenManager.getToken("default"); | ||
| if (!token) { | ||
| throw new Error(`Unknown service '${payload.serviceName}'`); | ||
| } | ||
| } | ||
| const serverSignature = (0, import_crypto.createHmac)("sha256", token).update(message.payload).digest("base64"); | ||
| if (message.signature !== serverSignature) { | ||
| throw new Error("Signature is not valid (invalid token?)"); | ||
| } | ||
| return payload; | ||
| } | ||
| start() { | ||
| this.server.listen(this.options.port, () => { | ||
| this.emit("listen"); | ||
| }); | ||
| } | ||
| stop() { | ||
| this.server.close(); | ||
| } | ||
| }; | ||
| // Annotate the CommonJS export names for ESM import in node: | ||
| 0 && (module.exports = { | ||
| TokenManager, | ||
| VotifierClient, | ||
| VotifierServer, | ||
| magicV2 | ||
| }); | ||
| //# sourceMappingURL=index.js.map |
| {"version":3,"sources":["../src/index.ts","../src/client/client.ts","../src/constants.ts","../src/server/server.ts","../src/server/tokens.ts"],"sourcesContent":["export * from './client/client';\nexport * from './constants';\nexport * from './server';\nexport * from './types';\n","import { readFileSync } from 'node:fs';\nimport { createConnection } from 'node:net';\nimport { createHmac } from 'node:crypto';\nimport type { VoteOptions, VotifierMessage, VotifierPayload, VotifierResponse } from '../types';\nimport { magicV2 } from '../constants';\n\nexport interface VotifierClientOptions {\n token: string;\n serviceName: string;\n host: string;\n /** Defaults to 8192 */\n port?: number;\n protocol?: 2;\n debug?: boolean;\n}\n\nexport class VotifierClient {\n public readonly host: string;\n\n public readonly port: number = 8192;\n\n public readonly token: string;\n \n public readonly protocolVersion: number = 2;\n \n public serviceName: string;\n\n public readonly debug: boolean = false;\n\n constructor(options: VotifierClientOptions) {\n this.token = options.token;\n this.serviceName = options.serviceName;\n this.host = options.host;\n if (options.port) this.port = options.port;\n if (options.protocol) this.protocolVersion = options.protocol;\n if (options.debug) this.debug = options.debug;\n }\n\n private getToken(path?: string): string {\n if (!path) throw new Error('Token or token path is required');\n return readFileSync(path, 'utf-8');\n } \n \n public sendVote(vote: VoteOptions) {\n return new Promise<void>((resolve, reject) => {\n const socket = createConnection(this.port, this.host);\n socket.setTimeout(5000, () => {\n socket.destroy();\n handleError(new Error('Connection timeout'));\n });\n\n const handleError = (error: Error) => {\n socket.destroy();\n socket.removeAllListeners();\n reject(error);\n }\n\n const handleGreeting = (data: Buffer) => {\n const greeting = data.toString();\n if (this.debug) console.log('[S->C] Greeting:', greeting);\n\n const headers = greeting.split(' ');\n if (headers.length !== 3) {\n return handleError(new Error('Not a v2 protocol server'));\n }\n\n const payload: VotifierPayload = {\n username: vote.username,\n address: vote.address,\n timestamp: vote.timestamp ?? Date.now(),\n serviceName: vote.serviceName ?? this.serviceName,\n challenge: headers[2]!.trim(),\n additionalData: (typeof vote.additionalData === 'string' ? Buffer.from(vote.additionalData) : vote.additionalData)?.toString('base64'),\n };\n if (this.debug) console.log('[C->S] Payload:', payload, payload.challenge.length);\n const serializedPayload = JSON.stringify(payload);\n\n const signature = createHmac('sha256', this.token)\n .update(serializedPayload)\n .digest('base64');\n\n const message: VotifierMessage = {\n payload: serializedPayload,\n signature\n };\n const serializedMessage = JSON.stringify(message);\n\n const buffer = Buffer.alloc(4 + serializedMessage.length);\n buffer.writeUInt16BE(magicV2, 0);\n buffer.writeUInt16BE(serializedMessage.length, 2);\n buffer.write(serializedMessage, 4);\n\n socket.write(new Uint8Array(buffer));\n socket.once('data', handleResponse);\n }\n\n const handleResponse = (data: Buffer) => {\n let response: VotifierResponse;\n try {\n response = JSON.parse(data.toString());\n } catch (error) {\n throw new Error('Failed to parse response');\n }\n\n if (this.debug) console.log('[S->C] Response:', response);\n\n socket.end();\n socket.removeAllListeners();\n\n if (response.status === 'ok') {\n resolve();\n\n } else if (response.status === 'error') {\n const err = new Error(response.error);\n err.name = response.cause;\n handleError(err);\n\n } else {\n handleError(new Error('Unknown response'));\n }\n }\n\n socket.once('data', handleGreeting);\n socket.once('error', handleError);\n });\n }\n}\n\n// export function sendVote() {}.\n/*\nfunc (client *V2Client) SendVote(vote Vote) error {\n\tconn, err := dial(client.address)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer conn.Close()\n\n\tgreeting := make([]byte, 64)\n\tread, err := conn.Read(greeting)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error reading greeting: %w\", err)\n\t}\n\n\tparts := bytes.Split(greeting[:read-1], []byte(\" \"))\n\tif len(parts) != 3 {\n\t\treturn errors.New(\"not a v2 server\")\n\t}\n\tchallenge := string(parts[2])\n\n\tserialized, err := vote.EncodeV2(client.token, challenge)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error encoding vote: %w\", err)\n\t}\n\t_, err = conn.Write(serialized)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to send vote: %w\", err)\n\t}\n\n\t// read response\n\tresBuf := make([]byte, 256)\n\tread, err = conn.Read(resBuf)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error reading response: %w\", err)\n\t}\n\n\tvar res v2Response\n\trd := bytes.NewReader(resBuf[:read])\n\terr = json.NewDecoder(rd).Decode(&res)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error decoding response: %w\", err)\n\t}\n\n\tif !strings.EqualFold(res.Status, \"ok\") {\n\t\treturn fmt.Errorf(\"remote server error: %w\", &remoteError{\n\t\t\tcause: res.Cause,\n\t\t\terr: errors.New(res.Error),\n\t\t})\n\t}\n\n\treturn nil\n}\n\ntype remoteError struct {\n\tcause string\n\terr error\n}\n\nfunc (e *remoteError) Error() string {\n\treturn fmt.Sprintf(\"%s: %s\", e.cause, e.err)\n}\n\nfunc (e *remoteError) Unwrap() error {\n\treturn e.err\n}\n*/","export const magicV2 = 0x733A;","import { EventEmitter } from 'events';\nimport { createServer, Server, Socket } from 'net';\nimport { TokenManager } from './tokens';\nimport { magicV2 } from '../constants';\nimport type { VotifierMessage, VotifierPayload, VotifierResponse } from '../types';\nimport { createHmac } from 'crypto';\n\nexport interface VotifierServerOptions {\n /** Path of the token file. Defaults to 'tokens.json' */\n tokenPath?: string;\n\n /** Port to listen vote requests. Defaults to 8192 */\n port?: number;\n}\n\nconst defaultOptions: VotifierServerOptions = {\n tokenPath: 'tokens.json',\n port: 8192,\n}\n\nexport class VotifierServer extends EventEmitter {\n public readonly server: Server = createServer();\n\n public readonly options: VotifierServerOptions;\n\n public readonly protocolVersion: number = 2;\n\n public readonly tokenManager: TokenManager;\n \n constructor(options?: VotifierServerOptions) {\n super();\n this.options = { ...defaultOptions, ...options };\n this.tokenManager = new TokenManager(this.options.tokenPath!);\n\n this.server.on('connection', this.handleConnection.bind(this));\n this.server.on('error', this.emit.bind(this, 'error'));\n }\n\n private handleConnection(socket: Socket): void {\n const challenge = this.tokenManager.generateToken();\n const greeting = `VOTIFIER ${this.protocolVersion} ${challenge}\\n`;\n socket.write(greeting);\n\n const handleError = (error: Error) => {\n const errorResponse: VotifierResponse = {\n status: 'error',\n error: error.message,\n cause: error.name,\n }\n socket.write(JSON.stringify(errorResponse));\n socket.destroy();\n socket.removeAllListeners();\n this.emit('error', error);\n }\n\n const handleData = (data: Buffer) => {\n let payload: VotifierPayload;\n try {\n payload = this.decode(data, challenge);\n } catch (error: any) {\n return handleError(error);\n }\n\n const response: VotifierResponse = { status: 'ok' };\n socket.write(JSON.stringify(response));\n socket.end();\n socket.destroy();\n\n this.emit('vote', payload);\n }\n \n socket.once('data', handleData);\n socket.on('error', handleError);\n }\n\n private decode(data: Buffer, challenge: string): VotifierPayload {\n const magic = data.readUInt16BE(0);\n if (magic !== magicV2) {\n throw new Error('This server only accepts well-formed Votifier v2 packets.');\n }\n\n const messageLength = data.readUInt32BE(2);\n const serializedMessage = data.subarray(4, 4 + messageLength).toString();\n \n const message: VotifierMessage = JSON.parse(serializedMessage);\n const payload: VotifierPayload = JSON.parse(message.payload);\n\n // verify challenge\n if (payload.challenge !== challenge) {\n throw new Error('Challenge is not valid');\n }\n\n let token = this.tokenManager.getToken(payload.serviceName);\n if (!token) {\n token = this.tokenManager.getToken('default');\n if (!token) {\n throw new Error(`Unknown service '${payload.serviceName}'`);\n }\n }\n\n // verify signature, decode\n const serverSignature = createHmac('sha256', token)\n .update(message.payload)\n .digest('base64');\n \n if (message.signature !== serverSignature) {\n throw new Error('Signature is not valid (invalid token?)');\n }\n\n return payload;\n }\n\n public start(): void {\n this.server.listen(this.options.port, () => {\n this.emit('listen');\n });\n }\n\n public stop(): void {\n this.server.close();\n }\n}\n\nexport interface VotifierServer {\n on(event: 'listen', listener: () => void): this;\n on(event: 'vote', listener: (vote: VotifierPayload) => void): this;\n on(event: 'error', listener: (error: any) => void): this;\n}\n","import * as crypto from 'crypto';\nimport { existsSync, readFileSync, writeFileSync } from 'node:fs';\nimport { join } from 'node:path';\n\nexport class TokenManager { \n private readonly tokenPath: string;\n\n private tokens: Map<string, string> = new Map();\n \n constructor(tokenPath: string) {\n this.tokenPath = tokenPath;\n this.load();\n }\n\n private load(): void {\n if (existsSync(this.tokenPath)) {\n const file = readFileSync(this.tokenPath, 'utf-8');\n this.tokens = new Map(Object.entries(JSON.parse(file)));\n } else {\n const defaultToken = this.generateToken();\n this.tokens.set('default', defaultToken);\n this.writeTokens();\n\n console.log('-'.repeat(75));\n console.log('[VotifierX]');\n console.log('No tokens were found in your tokenPath, so we\\'ve generated one for you.');\n console.log('Your default Votifier token is ' + defaultToken);\n console.log('You will need to provide this token when you submit your server to a voting');\n console.log('list.');\n console.log('-'.repeat(75));\n }\n }\n\n private writeTokens() {\n writeFileSync(this.tokenPath, JSON.stringify(Object.fromEntries(this.tokens), null, 2));\n }\n\n public getToken(serviceName: string): string | undefined {\n return this.tokens.get(serviceName);\n }\n\n public setToken(serviceName: string, token: string): void {\n this.tokens.set(serviceName, token);\n this.writeTokens();\n }\n \n public generateToken(): string {\n const randomBytes = crypto.randomBytes(16); // 128 bit\n const bigInt = BigInt('0x' + randomBytes.toString('hex')) + BigInt(Math.floor(Math.random() * 2 ** 14)); // 130ビットにするために2^14を足す\n // 32進数に変換\n return bigInt.toString(32);\n }\n}"],"mappings":"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","names":["import_node_fs","randomBytes"]} |
+235
| // src/client/client.ts | ||
| import { readFileSync } from "node:fs"; | ||
| import { createConnection } from "node:net"; | ||
| import { createHmac } from "node:crypto"; | ||
| // src/constants.ts | ||
| var magicV2 = 29498; | ||
| // src/client/client.ts | ||
| var VotifierClient = class { | ||
| host; | ||
| port = 8192; | ||
| token; | ||
| protocolVersion = 2; | ||
| serviceName; | ||
| debug = false; | ||
| constructor(options) { | ||
| this.token = options.token; | ||
| this.serviceName = options.serviceName; | ||
| this.host = options.host; | ||
| if (options.port) this.port = options.port; | ||
| if (options.protocol) this.protocolVersion = options.protocol; | ||
| if (options.debug) this.debug = options.debug; | ||
| } | ||
| getToken(path) { | ||
| if (!path) throw new Error("Token or token path is required"); | ||
| return readFileSync(path, "utf-8"); | ||
| } | ||
| sendVote(vote) { | ||
| return new Promise((resolve, reject) => { | ||
| const socket = createConnection(this.port, this.host); | ||
| socket.setTimeout(5e3, () => { | ||
| socket.destroy(); | ||
| handleError(new Error("Connection timeout")); | ||
| }); | ||
| const handleError = (error) => { | ||
| socket.destroy(); | ||
| socket.removeAllListeners(); | ||
| reject(error); | ||
| }; | ||
| const handleGreeting = (data) => { | ||
| const greeting = data.toString(); | ||
| if (this.debug) console.log("[S->C] Greeting:", greeting); | ||
| const headers = greeting.split(" "); | ||
| if (headers.length !== 3) { | ||
| return handleError(new Error("Not a v2 protocol server")); | ||
| } | ||
| const payload = { | ||
| username: vote.username, | ||
| address: vote.address, | ||
| timestamp: vote.timestamp ?? Date.now(), | ||
| serviceName: vote.serviceName ?? this.serviceName, | ||
| challenge: headers[2].trim(), | ||
| additionalData: (typeof vote.additionalData === "string" ? Buffer.from(vote.additionalData) : vote.additionalData)?.toString("base64") | ||
| }; | ||
| if (this.debug) console.log("[C->S] Payload:", payload, payload.challenge.length); | ||
| const serializedPayload = JSON.stringify(payload); | ||
| const signature = createHmac("sha256", this.token).update(serializedPayload).digest("base64"); | ||
| const message = { | ||
| payload: serializedPayload, | ||
| signature | ||
| }; | ||
| const serializedMessage = JSON.stringify(message); | ||
| const buffer = Buffer.alloc(4 + serializedMessage.length); | ||
| buffer.writeUInt16BE(magicV2, 0); | ||
| buffer.writeUInt16BE(serializedMessage.length, 2); | ||
| buffer.write(serializedMessage, 4); | ||
| socket.write(new Uint8Array(buffer)); | ||
| socket.once("data", handleResponse); | ||
| }; | ||
| const handleResponse = (data) => { | ||
| let response; | ||
| try { | ||
| response = JSON.parse(data.toString()); | ||
| } catch (error) { | ||
| throw new Error("Failed to parse response"); | ||
| } | ||
| if (this.debug) console.log("[S->C] Response:", response); | ||
| socket.end(); | ||
| socket.removeAllListeners(); | ||
| if (response.status === "ok") { | ||
| resolve(); | ||
| } else if (response.status === "error") { | ||
| const err = new Error(response.error); | ||
| err.name = response.cause; | ||
| handleError(err); | ||
| } else { | ||
| handleError(new Error("Unknown response")); | ||
| } | ||
| }; | ||
| socket.once("data", handleGreeting); | ||
| socket.once("error", handleError); | ||
| }); | ||
| } | ||
| }; | ||
| // src/server/server.ts | ||
| import { EventEmitter } from "events"; | ||
| import { createServer } from "net"; | ||
| // src/server/tokens.ts | ||
| import * as crypto from "crypto"; | ||
| import { existsSync, readFileSync as readFileSync2, writeFileSync } from "node:fs"; | ||
| var TokenManager = class { | ||
| tokenPath; | ||
| tokens = /* @__PURE__ */ new Map(); | ||
| constructor(tokenPath) { | ||
| this.tokenPath = tokenPath; | ||
| this.load(); | ||
| } | ||
| load() { | ||
| if (existsSync(this.tokenPath)) { | ||
| const file = readFileSync2(this.tokenPath, "utf-8"); | ||
| this.tokens = new Map(Object.entries(JSON.parse(file))); | ||
| } else { | ||
| const defaultToken = this.generateToken(); | ||
| this.tokens.set("default", defaultToken); | ||
| this.writeTokens(); | ||
| console.log("-".repeat(75)); | ||
| console.log("[VotifierX]"); | ||
| console.log("No tokens were found in your tokenPath, so we've generated one for you."); | ||
| console.log("Your default Votifier token is " + defaultToken); | ||
| console.log("You will need to provide this token when you submit your server to a voting"); | ||
| console.log("list."); | ||
| console.log("-".repeat(75)); | ||
| } | ||
| } | ||
| writeTokens() { | ||
| writeFileSync(this.tokenPath, JSON.stringify(Object.fromEntries(this.tokens), null, 2)); | ||
| } | ||
| getToken(serviceName) { | ||
| return this.tokens.get(serviceName); | ||
| } | ||
| setToken(serviceName, token) { | ||
| this.tokens.set(serviceName, token); | ||
| this.writeTokens(); | ||
| } | ||
| generateToken() { | ||
| const randomBytes2 = crypto.randomBytes(16); | ||
| const bigInt = BigInt("0x" + randomBytes2.toString("hex")) + BigInt(Math.floor(Math.random() * 2 ** 14)); | ||
| return bigInt.toString(32); | ||
| } | ||
| }; | ||
| // src/server/server.ts | ||
| import { createHmac as createHmac2 } from "crypto"; | ||
| var defaultOptions = { | ||
| tokenPath: "tokens.json", | ||
| port: 8192 | ||
| }; | ||
| var VotifierServer = class extends EventEmitter { | ||
| server = createServer(); | ||
| options; | ||
| protocolVersion = 2; | ||
| tokenManager; | ||
| constructor(options) { | ||
| super(); | ||
| this.options = { ...defaultOptions, ...options }; | ||
| this.tokenManager = new TokenManager(this.options.tokenPath); | ||
| this.server.on("connection", this.handleConnection.bind(this)); | ||
| this.server.on("error", this.emit.bind(this, "error")); | ||
| } | ||
| handleConnection(socket) { | ||
| const challenge = this.tokenManager.generateToken(); | ||
| const greeting = `VOTIFIER ${this.protocolVersion} ${challenge} | ||
| `; | ||
| socket.write(greeting); | ||
| const handleError = (error) => { | ||
| const errorResponse = { | ||
| status: "error", | ||
| error: error.message, | ||
| cause: error.name | ||
| }; | ||
| socket.write(JSON.stringify(errorResponse)); | ||
| socket.destroy(); | ||
| socket.removeAllListeners(); | ||
| this.emit("error", error); | ||
| }; | ||
| const handleData = (data) => { | ||
| let payload; | ||
| try { | ||
| payload = this.decode(data, challenge); | ||
| } catch (error) { | ||
| return handleError(error); | ||
| } | ||
| const response = { status: "ok" }; | ||
| socket.write(JSON.stringify(response)); | ||
| socket.end(); | ||
| socket.destroy(); | ||
| this.emit("vote", payload); | ||
| }; | ||
| socket.once("data", handleData); | ||
| socket.on("error", handleError); | ||
| } | ||
| decode(data, challenge) { | ||
| const magic = data.readUInt16BE(0); | ||
| if (magic !== magicV2) { | ||
| throw new Error("This server only accepts well-formed Votifier v2 packets."); | ||
| } | ||
| const messageLength = data.readUInt32BE(2); | ||
| const serializedMessage = data.subarray(4, 4 + messageLength).toString(); | ||
| const message = JSON.parse(serializedMessage); | ||
| const payload = JSON.parse(message.payload); | ||
| if (payload.challenge !== challenge) { | ||
| throw new Error("Challenge is not valid"); | ||
| } | ||
| let token = this.tokenManager.getToken(payload.serviceName); | ||
| if (!token) { | ||
| token = this.tokenManager.getToken("default"); | ||
| if (!token) { | ||
| throw new Error(`Unknown service '${payload.serviceName}'`); | ||
| } | ||
| } | ||
| const serverSignature = createHmac2("sha256", token).update(message.payload).digest("base64"); | ||
| if (message.signature !== serverSignature) { | ||
| throw new Error("Signature is not valid (invalid token?)"); | ||
| } | ||
| return payload; | ||
| } | ||
| start() { | ||
| this.server.listen(this.options.port, () => { | ||
| this.emit("listen"); | ||
| }); | ||
| } | ||
| stop() { | ||
| this.server.close(); | ||
| } | ||
| }; | ||
| export { | ||
| TokenManager, | ||
| VotifierClient, | ||
| VotifierServer, | ||
| magicV2 | ||
| }; | ||
| //# sourceMappingURL=index.mjs.map |
| {"version":3,"sources":["../src/client/client.ts","../src/constants.ts","../src/server/server.ts","../src/server/tokens.ts"],"sourcesContent":["import { readFileSync } from 'node:fs';\nimport { createConnection } from 'node:net';\nimport { createHmac } from 'node:crypto';\nimport type { VoteOptions, VotifierMessage, VotifierPayload, VotifierResponse } from '../types';\nimport { magicV2 } from '../constants';\n\nexport interface VotifierClientOptions {\n token: string;\n serviceName: string;\n host: string;\n /** Defaults to 8192 */\n port?: number;\n protocol?: 2;\n debug?: boolean;\n}\n\nexport class VotifierClient {\n public readonly host: string;\n\n public readonly port: number = 8192;\n\n public readonly token: string;\n \n public readonly protocolVersion: number = 2;\n \n public serviceName: string;\n\n public readonly debug: boolean = false;\n\n constructor(options: VotifierClientOptions) {\n this.token = options.token;\n this.serviceName = options.serviceName;\n this.host = options.host;\n if (options.port) this.port = options.port;\n if (options.protocol) this.protocolVersion = options.protocol;\n if (options.debug) this.debug = options.debug;\n }\n\n private getToken(path?: string): string {\n if (!path) throw new Error('Token or token path is required');\n return readFileSync(path, 'utf-8');\n } \n \n public sendVote(vote: VoteOptions) {\n return new Promise<void>((resolve, reject) => {\n const socket = createConnection(this.port, this.host);\n socket.setTimeout(5000, () => {\n socket.destroy();\n handleError(new Error('Connection timeout'));\n });\n\n const handleError = (error: Error) => {\n socket.destroy();\n socket.removeAllListeners();\n reject(error);\n }\n\n const handleGreeting = (data: Buffer) => {\n const greeting = data.toString();\n if (this.debug) console.log('[S->C] Greeting:', greeting);\n\n const headers = greeting.split(' ');\n if (headers.length !== 3) {\n return handleError(new Error('Not a v2 protocol server'));\n }\n\n const payload: VotifierPayload = {\n username: vote.username,\n address: vote.address,\n timestamp: vote.timestamp ?? Date.now(),\n serviceName: vote.serviceName ?? this.serviceName,\n challenge: headers[2]!.trim(),\n additionalData: (typeof vote.additionalData === 'string' ? Buffer.from(vote.additionalData) : vote.additionalData)?.toString('base64'),\n };\n if (this.debug) console.log('[C->S] Payload:', payload, payload.challenge.length);\n const serializedPayload = JSON.stringify(payload);\n\n const signature = createHmac('sha256', this.token)\n .update(serializedPayload)\n .digest('base64');\n\n const message: VotifierMessage = {\n payload: serializedPayload,\n signature\n };\n const serializedMessage = JSON.stringify(message);\n\n const buffer = Buffer.alloc(4 + serializedMessage.length);\n buffer.writeUInt16BE(magicV2, 0);\n buffer.writeUInt16BE(serializedMessage.length, 2);\n buffer.write(serializedMessage, 4);\n\n socket.write(new Uint8Array(buffer));\n socket.once('data', handleResponse);\n }\n\n const handleResponse = (data: Buffer) => {\n let response: VotifierResponse;\n try {\n response = JSON.parse(data.toString());\n } catch (error) {\n throw new Error('Failed to parse response');\n }\n\n if (this.debug) console.log('[S->C] Response:', response);\n\n socket.end();\n socket.removeAllListeners();\n\n if (response.status === 'ok') {\n resolve();\n\n } else if (response.status === 'error') {\n const err = new Error(response.error);\n err.name = response.cause;\n handleError(err);\n\n } else {\n handleError(new Error('Unknown response'));\n }\n }\n\n socket.once('data', handleGreeting);\n socket.once('error', handleError);\n });\n }\n}\n\n// export function sendVote() {}.\n/*\nfunc (client *V2Client) SendVote(vote Vote) error {\n\tconn, err := dial(client.address)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer conn.Close()\n\n\tgreeting := make([]byte, 64)\n\tread, err := conn.Read(greeting)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error reading greeting: %w\", err)\n\t}\n\n\tparts := bytes.Split(greeting[:read-1], []byte(\" \"))\n\tif len(parts) != 3 {\n\t\treturn errors.New(\"not a v2 server\")\n\t}\n\tchallenge := string(parts[2])\n\n\tserialized, err := vote.EncodeV2(client.token, challenge)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error encoding vote: %w\", err)\n\t}\n\t_, err = conn.Write(serialized)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to send vote: %w\", err)\n\t}\n\n\t// read response\n\tresBuf := make([]byte, 256)\n\tread, err = conn.Read(resBuf)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error reading response: %w\", err)\n\t}\n\n\tvar res v2Response\n\trd := bytes.NewReader(resBuf[:read])\n\terr = json.NewDecoder(rd).Decode(&res)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error decoding response: %w\", err)\n\t}\n\n\tif !strings.EqualFold(res.Status, \"ok\") {\n\t\treturn fmt.Errorf(\"remote server error: %w\", &remoteError{\n\t\t\tcause: res.Cause,\n\t\t\terr: errors.New(res.Error),\n\t\t})\n\t}\n\n\treturn nil\n}\n\ntype remoteError struct {\n\tcause string\n\terr error\n}\n\nfunc (e *remoteError) Error() string {\n\treturn fmt.Sprintf(\"%s: %s\", e.cause, e.err)\n}\n\nfunc (e *remoteError) Unwrap() error {\n\treturn e.err\n}\n*/","export const magicV2 = 0x733A;","import { EventEmitter } from 'events';\nimport { createServer, Server, Socket } from 'net';\nimport { TokenManager } from './tokens';\nimport { magicV2 } from '../constants';\nimport type { VotifierMessage, VotifierPayload, VotifierResponse } from '../types';\nimport { createHmac } from 'crypto';\n\nexport interface VotifierServerOptions {\n /** Path of the token file. Defaults to 'tokens.json' */\n tokenPath?: string;\n\n /** Port to listen vote requests. Defaults to 8192 */\n port?: number;\n}\n\nconst defaultOptions: VotifierServerOptions = {\n tokenPath: 'tokens.json',\n port: 8192,\n}\n\nexport class VotifierServer extends EventEmitter {\n public readonly server: Server = createServer();\n\n public readonly options: VotifierServerOptions;\n\n public readonly protocolVersion: number = 2;\n\n public readonly tokenManager: TokenManager;\n \n constructor(options?: VotifierServerOptions) {\n super();\n this.options = { ...defaultOptions, ...options };\n this.tokenManager = new TokenManager(this.options.tokenPath!);\n\n this.server.on('connection', this.handleConnection.bind(this));\n this.server.on('error', this.emit.bind(this, 'error'));\n }\n\n private handleConnection(socket: Socket): void {\n const challenge = this.tokenManager.generateToken();\n const greeting = `VOTIFIER ${this.protocolVersion} ${challenge}\\n`;\n socket.write(greeting);\n\n const handleError = (error: Error) => {\n const errorResponse: VotifierResponse = {\n status: 'error',\n error: error.message,\n cause: error.name,\n }\n socket.write(JSON.stringify(errorResponse));\n socket.destroy();\n socket.removeAllListeners();\n this.emit('error', error);\n }\n\n const handleData = (data: Buffer) => {\n let payload: VotifierPayload;\n try {\n payload = this.decode(data, challenge);\n } catch (error: any) {\n return handleError(error);\n }\n\n const response: VotifierResponse = { status: 'ok' };\n socket.write(JSON.stringify(response));\n socket.end();\n socket.destroy();\n\n this.emit('vote', payload);\n }\n \n socket.once('data', handleData);\n socket.on('error', handleError);\n }\n\n private decode(data: Buffer, challenge: string): VotifierPayload {\n const magic = data.readUInt16BE(0);\n if (magic !== magicV2) {\n throw new Error('This server only accepts well-formed Votifier v2 packets.');\n }\n\n const messageLength = data.readUInt32BE(2);\n const serializedMessage = data.subarray(4, 4 + messageLength).toString();\n \n const message: VotifierMessage = JSON.parse(serializedMessage);\n const payload: VotifierPayload = JSON.parse(message.payload);\n\n // verify challenge\n if (payload.challenge !== challenge) {\n throw new Error('Challenge is not valid');\n }\n\n let token = this.tokenManager.getToken(payload.serviceName);\n if (!token) {\n token = this.tokenManager.getToken('default');\n if (!token) {\n throw new Error(`Unknown service '${payload.serviceName}'`);\n }\n }\n\n // verify signature, decode\n const serverSignature = createHmac('sha256', token)\n .update(message.payload)\n .digest('base64');\n \n if (message.signature !== serverSignature) {\n throw new Error('Signature is not valid (invalid token?)');\n }\n\n return payload;\n }\n\n public start(): void {\n this.server.listen(this.options.port, () => {\n this.emit('listen');\n });\n }\n\n public stop(): void {\n this.server.close();\n }\n}\n\nexport interface VotifierServer {\n on(event: 'listen', listener: () => void): this;\n on(event: 'vote', listener: (vote: VotifierPayload) => void): this;\n on(event: 'error', listener: (error: any) => void): this;\n}\n","import * as crypto from 'crypto';\nimport { existsSync, readFileSync, writeFileSync } from 'node:fs';\nimport { join } from 'node:path';\n\nexport class TokenManager { \n private readonly tokenPath: string;\n\n private tokens: Map<string, string> = new Map();\n \n constructor(tokenPath: string) {\n this.tokenPath = tokenPath;\n this.load();\n }\n\n private load(): void {\n if (existsSync(this.tokenPath)) {\n const file = readFileSync(this.tokenPath, 'utf-8');\n this.tokens = new Map(Object.entries(JSON.parse(file)));\n } else {\n const defaultToken = this.generateToken();\n this.tokens.set('default', defaultToken);\n this.writeTokens();\n\n console.log('-'.repeat(75));\n console.log('[VotifierX]');\n console.log('No tokens were found in your tokenPath, so we\\'ve generated one for you.');\n console.log('Your default Votifier token is ' + defaultToken);\n console.log('You will need to provide this token when you submit your server to a voting');\n console.log('list.');\n console.log('-'.repeat(75));\n }\n }\n\n private writeTokens() {\n writeFileSync(this.tokenPath, JSON.stringify(Object.fromEntries(this.tokens), null, 2));\n }\n\n public getToken(serviceName: string): string | undefined {\n return this.tokens.get(serviceName);\n }\n\n public setToken(serviceName: string, token: string): void {\n this.tokens.set(serviceName, token);\n this.writeTokens();\n }\n \n public generateToken(): string {\n const randomBytes = crypto.randomBytes(16); // 128 bit\n const bigInt = BigInt('0x' + randomBytes.toString('hex')) + BigInt(Math.floor(Math.random() * 2 ** 14)); // 130ビットにするために2^14を足す\n // 32進数に変換\n return bigInt.toString(32);\n }\n}"],"mappings":"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","names":["readFileSync","readFileSync","randomBytes","createHmac"]} |
+21
| MIT License | ||
| Copyright (c) 2025 SKYNETWORK-MCBE | ||
| Permission is hereby granted, free of charge, to any person obtaining a copy | ||
| of this software and associated documentation files (the "Software"), to deal | ||
| in the Software without restriction, including without limitation the rights | ||
| to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
| copies of the Software, and to permit persons to whom the Software is | ||
| furnished to do so, subject to the following conditions: | ||
| The above copyright notice and this permission notice shall be included in all | ||
| copies or substantial portions of the Software. | ||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
| IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
| FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
| AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
| LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
| OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
| SOFTWARE. |
+14
-4
| { | ||
| "name": "votifier-x", | ||
| "version": "0.1.0", | ||
| "version": "0.1.1", | ||
| "description": "Votifier implementation in TypeScript. Including standalone server and client.", | ||
@@ -19,4 +19,4 @@ "exports": { | ||
| "scripts": { | ||
| "dev-server": "bun test/server.js", | ||
| "dev-client": "bun test/client.js", | ||
| "dev-server": "bun test/server.mjs", | ||
| "dev-client": "bun test/client.mjs", | ||
| "build": "tsup", | ||
@@ -33,3 +33,13 @@ "lint": "tsc --noEmit" | ||
| "dist" | ||
| ] | ||
| ], | ||
| "repository": { | ||
| "type": "git", | ||
| "url": "https://github.com/SKYNETWORK-MCBE/VotifierX.git" | ||
| }, | ||
| "bugs": { | ||
| "url": "https://github.com/SKYNETWORK-MCBE/VotifierX/issues" | ||
| }, | ||
| "engines": { | ||
| "node": ">=16.0.0" | ||
| } | ||
| } |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
Empty package
Supply chain riskPackage does not contain any code. It may be removed, is name squatting, or the result of a faulty package publish.
Found 1 instance in 1 package
No bug tracker
MaintenancePackage does not have a linked bug tracker in package.json.
Found 1 instance in 1 package
No repository
Supply chain riskPackage does not have a linked source code repository. Without this field, a package will have no reference to the location of the source code use to generate the package.
Found 1 instance in 1 package
58813
2919.15%9
350%579
Infinity%0
-100%4
300%6
500%