You're Invited:Meet the Socket Team at RSAC and BSidesSF 2026, March 23–26.RSVP
Socket
Book a DemoSign in
Socket

votifier-x

Package Overview
Dependencies
Maintainers
3
Versions
3
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

votifier-x - npm Package Compare versions

Comparing version
0.1.1
to
0.2.1
+18
-1
dist/index.d.mts

@@ -54,2 +54,19 @@ import { EventEmitter } from 'events';

declare class VotifierError extends Error {
remoteAddress?: string;
constructor(message: string);
}
declare class ProtocolError extends VotifierError {
constructor(message: string);
}
declare class InvalidChallengeError extends VotifierError {
constructor(message?: string);
}
declare class UnknownServiceError extends VotifierError {
constructor(serviceName: string);
}
declare class InvalidSignatureError extends VotifierError {
constructor(message?: string);
}
declare class TokenManager {

@@ -89,2 +106,2 @@ private readonly tokenPath;

export { TokenManager, type VoteOptions, VotifierClient, type VotifierClientOptions, type VotifierMessage, type VotifierPayload, type VotifierResponse, VotifierServer, type VotifierServerOptions, magicV2 };
export { InvalidChallengeError, InvalidSignatureError, ProtocolError, TokenManager, UnknownServiceError, type VoteOptions, VotifierClient, type VotifierClientOptions, VotifierError, type VotifierMessage, type VotifierPayload, type VotifierResponse, VotifierServer, type VotifierServerOptions, magicV2 };

@@ -54,2 +54,19 @@ import { EventEmitter } from 'events';

declare class VotifierError extends Error {
remoteAddress?: string;
constructor(message: string);
}
declare class ProtocolError extends VotifierError {
constructor(message: string);
}
declare class InvalidChallengeError extends VotifierError {
constructor(message?: string);
}
declare class UnknownServiceError extends VotifierError {
constructor(serviceName: string);
}
declare class InvalidSignatureError extends VotifierError {
constructor(message?: string);
}
declare class TokenManager {

@@ -89,2 +106,2 @@ private readonly tokenPath;

export { TokenManager, type VoteOptions, VotifierClient, type VotifierClientOptions, type VotifierMessage, type VotifierPayload, type VotifierResponse, VotifierServer, type VotifierServerOptions, magicV2 };
export { InvalidChallengeError, InvalidSignatureError, ProtocolError, TokenManager, UnknownServiceError, type VoteOptions, VotifierClient, type VotifierClientOptions, VotifierError, type VotifierMessage, type VotifierPayload, type VotifierResponse, VotifierServer, type VotifierServerOptions, magicV2 };

@@ -33,4 +33,9 @@ "use strict";

__export(index_exports, {
InvalidChallengeError: () => InvalidChallengeError,
InvalidSignatureError: () => InvalidSignatureError,
ProtocolError: () => ProtocolError,
TokenManager: () => TokenManager,
UnknownServiceError: () => UnknownServiceError,
VotifierClient: () => VotifierClient,
VotifierError: () => VotifierError,
VotifierServer: () => VotifierServer,

@@ -137,2 +142,31 @@ magicV2: () => magicV2

// src/server/error.ts
var VotifierError = class extends Error {
remoteAddress;
constructor(message) {
super(message);
this.name = this.constructor.name;
}
};
var ProtocolError = class extends VotifierError {
constructor(message) {
super(message);
}
};
var InvalidChallengeError = class extends VotifierError {
constructor(message = "Challenge is not valid") {
super(message);
}
};
var UnknownServiceError = class extends VotifierError {
constructor(serviceName) {
super(`Unknown service '${serviceName}'`);
}
};
var InvalidSignatureError = class extends VotifierError {
constructor(message = "Signature is not valid (invalid token?)") {
super(message);
}
};
// src/server/server.ts

@@ -226,2 +260,5 @@ var import_events = require("events");

} catch (error) {
if (error instanceof VotifierError) {
error.remoteAddress = socket.remoteAddress;
}
return handleError(error);

@@ -241,3 +278,3 @@ }

if (magic !== magicV2) {
throw new Error("This server only accepts well-formed Votifier v2 packets.");
throw new ProtocolError("This server only accepts well-formed Votifier v2 packets.");
}

@@ -249,3 +286,3 @@ const messageLength = data.readUInt32BE(2);

if (payload.challenge !== challenge) {
throw new Error("Challenge is not valid");
throw new InvalidChallengeError();
}

@@ -256,3 +293,3 @@ let token = this.tokenManager.getToken(payload.serviceName);

if (!token) {
throw new Error(`Unknown service '${payload.serviceName}'`);
throw new UnknownServiceError(payload.serviceName);
}

@@ -262,3 +299,3 @@ }

if (message.signature !== serverSignature) {
throw new Error("Signature is not valid (invalid token?)");
throw new InvalidSignatureError();
}

@@ -278,4 +315,9 @@ return payload;

0 && (module.exports = {
InvalidChallengeError,
InvalidSignatureError,
ProtocolError,
TokenManager,
UnknownServiceError,
VotifierClient,
VotifierError,
VotifierServer,

@@ -282,0 +324,0 @@ magicV2

+1
-1

@@ -1,1 +0,1 @@

{"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"]}
{"version":3,"sources":["../src/index.ts","../src/client/client.ts","../src/constants.ts","../src/server/error.ts","../src/server/server.ts","../src/server/tokens.ts"],"sourcesContent":["export * from './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;","export class VotifierError extends Error {\n public remoteAddress?: string;\n\n constructor(message: string) {\n super(message);\n this.name = this.constructor.name;\n }\n}\n\nexport class ProtocolError extends VotifierError {\n constructor(message: string) {\n super(message);\n }\n}\n\nexport class InvalidChallengeError extends VotifierError {\n constructor(message: string = 'Challenge is not valid') {\n super(message);\n }\n}\n\nexport class UnknownServiceError extends VotifierError {\n constructor(serviceName: string) {\n super(`Unknown service '${serviceName}'`);\n }\n}\n\nexport class InvalidSignatureError extends VotifierError {\n constructor(message: string = 'Signature is not valid (invalid token?)') {\n super(message);\n }\n}\n","import { EventEmitter } from 'events';\nimport { createServer, Server, Socket } from 'net';\nimport { TokenManager } from './tokens';\nimport { magicV2 } from '../constants';\nimport { InvalidChallengeError, InvalidSignatureError, ProtocolError, UnknownServiceError, VotifierError } from './error';\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 if (error instanceof VotifierError) {\n error.remoteAddress = socket.remoteAddress;\n }\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 ProtocolError('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 InvalidChallengeError();\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 UnknownServiceError(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 InvalidSignatureError();\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"]}
// src/client/client.ts
import { readFileSync } from "node:fs";
import { createConnection } from "node:net";
import { createHmac } from "node:crypto";
import { readFileSync } from "fs";
import { createConnection } from "net";
import { createHmac } from "crypto";

@@ -97,2 +97,31 @@ // src/constants.ts

// src/server/error.ts
var VotifierError = class extends Error {
remoteAddress;
constructor(message) {
super(message);
this.name = this.constructor.name;
}
};
var ProtocolError = class extends VotifierError {
constructor(message) {
super(message);
}
};
var InvalidChallengeError = class extends VotifierError {
constructor(message = "Challenge is not valid") {
super(message);
}
};
var UnknownServiceError = class extends VotifierError {
constructor(serviceName) {
super(`Unknown service '${serviceName}'`);
}
};
var InvalidSignatureError = class extends VotifierError {
constructor(message = "Signature is not valid (invalid token?)") {
super(message);
}
};
// src/server/server.ts

@@ -104,3 +133,4 @@ import { EventEmitter } from "events";

import * as crypto from "crypto";
import { existsSync, readFileSync as readFileSync2, writeFileSync } from "node:fs";
import { existsSync, readFileSync as readFileSync2, writeFileSync } from "fs";
import "path";
var TokenManager = class {

@@ -186,2 +216,5 @@ tokenPath;

} catch (error) {
if (error instanceof VotifierError) {
error.remoteAddress = socket.remoteAddress;
}
return handleError(error);

@@ -201,3 +234,3 @@ }

if (magic !== magicV2) {
throw new Error("This server only accepts well-formed Votifier v2 packets.");
throw new ProtocolError("This server only accepts well-formed Votifier v2 packets.");
}

@@ -209,3 +242,3 @@ const messageLength = data.readUInt32BE(2);

if (payload.challenge !== challenge) {
throw new Error("Challenge is not valid");
throw new InvalidChallengeError();
}

@@ -216,3 +249,3 @@ let token = this.tokenManager.getToken(payload.serviceName);

if (!token) {
throw new Error(`Unknown service '${payload.serviceName}'`);
throw new UnknownServiceError(payload.serviceName);
}

@@ -222,3 +255,3 @@ }

if (message.signature !== serverSignature) {
throw new Error("Signature is not valid (invalid token?)");
throw new InvalidSignatureError();
}

@@ -237,4 +270,9 @@ return payload;

export {
InvalidChallengeError,
InvalidSignatureError,
ProtocolError,
TokenManager,
UnknownServiceError,
VotifierClient,
VotifierError,
VotifierServer,

@@ -241,0 +279,0 @@ magicV2

@@ -1,1 +0,1 @@

{"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"]}
{"version":3,"sources":["../src/client/client.ts","../src/constants.ts","../src/server/error.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;","export class VotifierError extends Error {\n public remoteAddress?: string;\n\n constructor(message: string) {\n super(message);\n this.name = this.constructor.name;\n }\n}\n\nexport class ProtocolError extends VotifierError {\n constructor(message: string) {\n super(message);\n }\n}\n\nexport class InvalidChallengeError extends VotifierError {\n constructor(message: string = 'Challenge is not valid') {\n super(message);\n }\n}\n\nexport class UnknownServiceError extends VotifierError {\n constructor(serviceName: string) {\n super(`Unknown service '${serviceName}'`);\n }\n}\n\nexport class InvalidSignatureError extends VotifierError {\n constructor(message: string = 'Signature is not valid (invalid token?)') {\n super(message);\n }\n}\n","import { EventEmitter } from 'events';\nimport { createServer, Server, Socket } from 'net';\nimport { TokenManager } from './tokens';\nimport { magicV2 } from '../constants';\nimport { InvalidChallengeError, InvalidSignatureError, ProtocolError, UnknownServiceError, VotifierError } from './error';\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 if (error instanceof VotifierError) {\n error.remoteAddress = socket.remoteAddress;\n }\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 ProtocolError('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 InvalidChallengeError();\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 UnknownServiceError(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 InvalidSignatureError();\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","randomBytes","createHmac"]}
{
"name": "votifier-x",
"version": "0.1.1",
"version": "0.2.1",
"description": "Votifier implementation in TypeScript. Including standalone server and client.",

@@ -42,3 +42,7 @@ "exports": {

"node": ">=16.0.0"
},
"publishConfig": {
"access": "public",
"provenance": true
}
}