Skip to content

Commit

Permalink
Merge pull request #36 from bchainhub/update/feature-counting-01
Browse files Browse the repository at this point in the history
Added counting and help
  • Loading branch information
rastislavcore authored Aug 29, 2024
2 parents b1bdd01 + 29a61b5 commit 41e6481
Show file tree
Hide file tree
Showing 6 changed files with 191 additions and 18 deletions.
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@ The library is designed to be compatible with both module systems, so you can ch

- `encode(hex: string): string` — Convert hex transaction into UTF-16BE.
- `decode(data: string): string` — Convert UTF-16BE into hex transaction.
- `count(data: string, type: 'sms' | 'mms'): number` — Count the number of characters/SMS/MMS needed for the transaction.
- `getEndpoint(network?: number | string, countriesList?: string | Array<string>): { [key: string]: Array<string> }` — Get an object of SMS endpoints (phone numbers) per country.
- `sms(number?: boolean | string | number | Array<string>, message?: string, network?: number | string, encodeMessage?: boolean, platform?: string): string` — Create an SMS URI based on the provided parameters.
- `mms(number?: boolean | string | number | Array<string>, message?: string, network?: number | string, encodeMessage?: boolean, platform?: string): string` — Create an MMS URI based on the provided parameters.
Expand Down Expand Up @@ -181,10 +182,12 @@ Types:
- `--version` (`-v`) - Get the version of the library.
- `--encode` (`-e`) - Encode the HEX transaction.
- `--decode` (`-d`) - Decode the UTF-16BE transaction.
- `--count` (`-ct`) - Count the number of characters needed for the transaction. You can choose type of count: `sms`, `mms`. (To perform a count, you need to provide `encode` command.)
- `--getendpoint` (`-g`) - Get the SMS/MMS endpoint for the network and country.
- `--sms` - Create an SMS URI based on the provided parameters.
- `--mms` - Create an MMS URI based on the provided parameters.
- `--download` (`-dl`) - Boolean value to download a file with the encoded content as `.txms.txt` file in your working directory.
- `--download` (`-dl`) - Boolean value to download a file with the encoded content as `.txms.txt` file in your working directory. (To download a file, you need to provide `encode` command.)
- `--help` (`-h`) - Show help. (Only for TTY mode.)

### Piping

Expand Down
86 changes: 75 additions & 11 deletions bin/txms
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ function parseArgs(argv) {
countryCodes: null, // For getEndpoint
phoneNumbers: null, // For SMS/MMS phone numbers
download: false, // Flag to indicate download
countType: null, // Count type: sms, mms, or null for characters
showHelp: false, // Flag to indicate if help is requested
};

argv.forEach((arg) => {
Expand All @@ -35,6 +37,9 @@ function parseArgs(argv) {
args.kind = 'decode';
args.value = value;
break;
case '--count':
args.countType = value || true;
break;
case '--getendpoint':
args.kind = 'getendpoint';
args.value = value; // Network type for getEndpoint
Expand All @@ -59,6 +64,9 @@ function parseArgs(argv) {
case '--countries':
args.countryCodes = value.split(','); // Comma-separated country codes
break;
case '--help':
args.showHelp = true;
break;
default:
break;
}
Expand All @@ -76,6 +84,9 @@ function parseArgs(argv) {
args.kind = 'decode';
args.value = value;
break;
case '-ct':
args.countType = value || true;
break;
case '-g':
args.kind = 'getendpoint';
args.value = value;
Expand All @@ -97,6 +108,9 @@ function parseArgs(argv) {
case '-c':
args.countryCodes = value.split(',');
break;
case '-h':
args.showHelp = true;
break;
default:
break;
}
Expand All @@ -109,12 +123,46 @@ function parseArgs(argv) {
return args;
}

// Function to display help
function displayHelp(newline = '\n') {
const helpText = `
\x1b[1mUsage:\x1b[0m txms \x1b[38;5;214m[options]\x1b[0m
\x1b[1mOptions:\x1b[0m
\x1b[38;5;214m-v, --version\x1b[0m Get the version of the library.
\x1b[38;5;214m-e, --encode\x1b[0m Encode the HEX transaction.
\x1b[38;5;214m-d, --decode\x1b[0m Decode the UTF-16BE transaction.
\x1b[38;5;214m-ct, --count\x1b[0m Count the number of characters needed for the transaction. You can choose the type of count: 'sms' or 'mms'.
\x1b[38;5;214m-g, --getendpoint\x1b[0m Get the SMS/MMS endpoint for the network and country.
\x1b[38;5;214m-s, --sms\x1b[0m Create an SMS URI based on the provided parameters.
\x1b[38;5;214m-m, --mms\x1b[0m Create an MMS URI based on the provided parameters.
\x1b[38;5;214m-dl, --download\x1b[0m Download a file with the encoded content as .txms.txt file in your working directory.
\x1b[38;5;214m-o, --output\x1b[0m Specify the output directory for downloads.
\x1b[38;5;214m-f, --filename\x1b[0m Specify the filename for downloads.
\x1b[38;5;214m-c, --countries\x1b[0m Specify a comma-separated list of country codes.
\x1b[38;5;214m-h, --help\x1b[0m Show this help message and exit.
\x1b[1mExamples:\x1b[0m
txms --encode=yourHexValue
txms -e=yourHexValue --download
txms -d=yourUTF16String
echo yourHexValue | txms --encode
`;
process.stdout.write(helpText + newline);
}

// Parse the arguments
const args = parseArgs(process.argv.slice(2));

// If the help flag is set, display the help message immediately
if (args.showHelp && process.stdin.isTTY) {
displayHelp();
process.exit(0);
}

if (process.stdin.isTTY) {
// If the script is run with a TTY, process the command-line arguments
run(args.kind, args.value, args.output, args.countryCodes, args.phoneNumbers, args.download, args.filename, '\n');
run(args.kind, args.value, args.output, args.countryCodes, args.phoneNumbers, args.download, args.filename, args.countType, '\n');
} else {
// If data is being piped into the script, capture it
let content = '';
Expand All @@ -124,16 +172,18 @@ if (process.stdin.isTTY) {
});
process.stdin.on('end', () => {
content = content.trim();

if (!content) {
run(args.kind, args.value, args.output, args.countryCodes, args.phoneNumbers, args.download, args.filename, '');
if (!content && !args.showHelp) {
run(args.kind, args.value, args.output, args.countryCodes, args.phoneNumbers, args.download, args.filename, args.countType, '');
} else if (args.showHelp) {
displayHelp();
process.exit(0);
} else {
run(args.kind, content, args.output, args.countryCodes, args.phoneNumbers, args.download, args.filename, '');
run(args.kind, content, args.output, args.countryCodes, args.phoneNumbers, args.download, args.filename, args.countType, '');
}
});
}

async function run(kind, value, output, countryCodes, phoneNumbers, download, filename, newline) {
async function run(kind, value, output, countryCodes, phoneNumbers, download, filename, countType, newline) {
if (!value && kind !== 'version') {
process.stderr.write('Value is required' + newline);
process.exit(1);
Expand All @@ -152,12 +202,26 @@ async function run(kind, value, output, countryCodes, phoneNumbers, download, fi
process.exit(0);
} else if (kind === 'encode' || kind === 'e') {
if (download) {
const filenm = await txms.downloadMessage(value, filename ? filename : undefined, output);
process.stdout.write(`TxMS file was downloaded as "${filenm}".${newline}`);
} else {
const encoded = txms.encode(value);
process.stdout.write(encoded + newline);
const filenamePrint = await txms.downloadMessage(value, filename ? filename : undefined, output);
process.stdout.write(`TxMS file was downloaded as "${filenamePrint}".${newline}`);
process.exit(0);
}

if (countType) {
let calculated;
if (countType === 'sms') {
calculated = txms.count(value, 'sms');
} else if (countType === 'mms') {
calculated = txms.count(value, 'mms');
} else {
calculated = txms.count(value);
}
process.stdout.write(calculated + newline);
process.exit(0);
}

let encodedMessage = txms.encode(value);
process.stdout.write(encodedMessage + newline);
process.exit(0);
} else if (kind === 'decode' || kind === 'd') {
const decoded = txms.decode(value);
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "txms.js",
"version": "1.2.9",
"version": "1.2.10",
"description": "Transaction messaging service protocol",
"main": "dist/index.js",
"types": "dist/index.d.ts",
Expand Down
42 changes: 42 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
export interface Transport {
encode(hex: string): string;
decode(data: string): string;
count(hex: string, type?: 'sms' | 'mms' | true): number;
getEndpoint(network?: number | string, countriesList?: string | Array<string>): { [key: string]: Array<string> };
sms(number?: boolean | string | number | Array<string>, message?: string, network?: number | string, encodeMessage?: boolean, platform?: string): string;
mms(number?: boolean | string | number | Array<string>, message?: string, network?: number | string, encodeMessage?: boolean, platform?: string): string;
Expand Down Expand Up @@ -85,6 +86,47 @@ const txms: Transport = {
return '0x' + hex.replace(/^0+/, '');
},

count(hex: string, type?: 'sms' | 'mms' | true): number {
// Encode the hex string to the message
const message = this.encode(hex);

// If type is not provided, return the length of the message in characters
if (!type) {
return message.length;
}

// SMS calculation logic
if (type === 'sms') {
// UTF-16 encoding, so the character limit is 70 characters for a single SMS
const singleSmsLimit = 70;
const multipartSmsLimit = 67; // Character limit per SMS in a concatenated message

if (message.length <= singleSmsLimit) {
// Fits within a single SMS
return 1;
} else {
// Calculate the number of segments required for a multipart SMS
return Math.ceil(message.length / multipartSmsLimit);
}
}

// MMS calculation logic
if (type === 'mms') {
// Assume a typical size limit of 300 KB for MMS (this may vary by carrier)
const mmsSizeLimit = 300 * 1024; // 300 KB in bytes
// Estimate size of the message in bytes (UTF-16, so each character is 2 bytes)
const messageSizeInBytes = message.length * 2;

if (messageSizeInBytes <= mmsSizeLimit) {
return 1;
} else {
return Math.ceil(messageSizeInBytes / mmsSizeLimit);
}
}

return message.length;
},

getEndpoint(network?: number | string, countriesList?: string | Array<string>): { [key: string]: Array<string> } {
let requestedList: Array<string> | undefined;
if (countriesList instanceof Array) {
Expand Down
64 changes: 61 additions & 3 deletions test/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,14 @@ if (!fs.existsSync(outputDir)) {
// Encode/Decode Tests
describe('Encode/Decode Tests', () => {
samples.valid.forEach((f) => {
test(`OK - Encode - to data. Description: ${f.description}`, () => {
test(`Should encode - to data. Description: ${f.description}`, () => {
const actual = txms.encode(f.hex);
assert.strictEqual(actual, f.data);
});
});

samples.valid.forEach((f) => {
test(`OK - Decode - to hex. Description: ${f.description}`, () => {
test(`Should decode - to hex. Description: ${f.description}`, () => {
const actual = txms.decode(f.data);
const normalizedActual = actual.startsWith('0x') ? actual.slice(2) : actual;
const normalizedExpected = f.hex.startsWith('0x') ? f.hex.slice(2) : f.hex;
Expand All @@ -37,12 +37,33 @@ describe('Encode/Decode Tests', () => {
});

samples.invalid.forEach((f) => {
test(`Encode${f.description}`, () => {
test(`Should encode${f.description}`, () => {
assert.throws(() => {
txms.encode(f.hex);
}, /Not a hex format/);
});
});

samples.valid.forEach((f) => {
test(`Should count - characters. Description: ${f.description}`, () => {
const length = txms.count(f.hex);
assert.strictEqual(length, f.length);
});
});

samples.valid.forEach((f) => {
test(`Should count - SMS. Description: ${f.description}`, () => {
const lengthSms = txms.count(f.hex, 'sms');
assert.strictEqual(lengthSms, f.sms);
});
});

samples.valid.forEach((f) => {
test(`Should count - MMS. Description: ${f.description}`, () => {
const lengthSms = txms.count(f.hex, 'mms');
assert.strictEqual(lengthSms, f.mms);
});
});
});

// Endpoint Tests
Expand Down Expand Up @@ -263,4 +284,41 @@ describe('CLI Tests', () => {
assert.strictEqual(result.status, 0);
assert.strictEqual(result.stdout.toString().trim(), samples.valid[0].hex);
});

test('Should count length', () => {
const hexValue = samples.valid[0].hex;
const result = spawnSync('node', [txmsPath, `--encode=${hexValue}`, '--count']);
assert.strictEqual(result.status, 0);
assert.strictEqual(parseInt(result.stdout.toString(), 10), samples.valid[0].length);
});

test('Should count amount of SMS', () => {
const hexValue = samples.valid[0].hex;
const result = spawnSync('node', [txmsPath, `--encode=${hexValue}`, '--count=sms']);
assert.strictEqual(result.status, 0);
assert.strictEqual(parseInt(result.stdout.toString(), 10), samples.valid[0].sms);
});

test('Should count amount of MMS', () => {
const hexValue = samples.valid[0].hex;
const result = spawnSync('node', [txmsPath, `--encode=${hexValue}`, '-ct=mms']);
assert.strictEqual(result.status, 0);
assert.strictEqual(parseInt(result.stdout.toString(), 10), samples.valid[0].mms);
});

test('Should count with piping', () => {
const hexValue = samples.valid[0].hex;
const echo = spawnSync('echo', [hexValue]);
const result = spawnSync('node', [txmsPath, '--encode', '--count'], {
input: echo.stdout
});
assert.strictEqual(result.status, 0);
assert.strictEqual(parseInt(result.stdout.toString(), 10), samples.valid[0].length);
});

test('Should print help text', () => {
const result = spawnSync('node', [txmsPath, '--help']);
assert.strictEqual(result.status, 0);
assert.match(result.stdout.toString(), /^\n\x1B\[1mUsage:\x1B\[0m txms \x1B\[38;5;214m\[options\]\x1B\[0m/);
});
});
10 changes: 8 additions & 2 deletions test/samples.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,18 @@
{
"hex": "0xf8d880843b9aca008252080396ab2896c9af969b11394e3f0be750da75d0076e1d1afd880de0b6b3a764000080b8ab16acb60152c254244a6e3edcd5e66c029caf909f4f2dc4972ff01716d96de84afc62d94c47810b55b38eb0c3572b728552611d3fbb0209e100fde2e8404d168dd431c6d3dd2eec1574d86ad34e2374506cb98ee4576bbf5a78f07003c653d9e8887582020fb369c16fe1ae5e46f0dd4e0b00771fb7ab745c15389be291530859010281f2746b652118a47b07b9763c789e90576f6a2585d530a89892aa3aba2577895e63a3492cbbf38d00",
"data": "~Ǹǘ肄㮚쨀艒ࠃ隫⢖즯際ᄹ丿௧僚痐ݮᴚﶈ~čǠ뚳Ꝥ~ĀĀ肸ꬖ겶Œ쉔⑊渾~ǜǕ~ǦŬʜ꾐齏ⷄ霯~ǰėᛙ淨䫼拙䱇脋喳躰썗⭲蕒愝㾻ȉ~ǡĀ~ǽǢ~Ǩŀ䴖跔㇆폝⻬ᕴ~ǘŪ퍎⍴偬릎~Ǥŗ殿婸~ǰŰφ叙~Ǩƈ疂ȏ덩셯~ǡƮ幆~ǰǝ下wᾷꭴ尕㢛~ǢƑ匈夁ʁ~DzŴ步℘ꑻ~ćƹ瘼碞遗潪▅픰ꢘ銪㪺╷襞掣䤬믳贀",
"description": "Correct transaction on Devin."
"description": "Correct transaction on Devin.",
"length": 145,
"sms": 3,
"mms": 1
},
{
"hex": "f8dc821ae4850ee6b280008252080196cb65d677385703c528527f2a0f0e401b4af1988d91c5896e3f4f2ab21845000080b8abcffa127f34f8dc8d8bc9a50da5def786a16ecab58d9d1cdc3e1347077f531ad0339797568345464f542f8da3bcd50fd683878f52e6d094610025d6e4a5fb3699acd20ebd1ec2fdde9d12f5e82fe5f4c8d9061466475b3293bb18c34504c6eb43bc0ba48d61a8edfda686c69773fa96b90d00760d8277330d90589ba26fb63874952b013a8af1a5edacbcabb37108b47518c79abd6e50be00da0a08fb9126fd265175cace1ac93d1f809b80",
"data": "~Ǹǜ舚~Ǥƅ~ĎǦ늀~ĀƂ刈Ɩ쭥홷㡗υ⡒缪༎䀛䫱颍釅襮㽏⪲ᡅ~ĀĀ肸ꯏ晴缴~Ǹǜ趋즥ඥ~ǞǷ蚡滊떍鴜~ǜľፇݿ匚퀳鞗嚃䕆佔⾍ꎼ픏횃螏勦킔愀◖~Ǥƥזּ馬툎봞싽~ǞƝድ~Ǩį~ǥǴ죙ؔ晇嬲鎻ᣃ䔄웫䎼த赡꣭ﶦ蛆靳殺뤍vං眳ඐ墛ꉯ똸璕⬁㪊~DZƥ~ǭƬ벫덱ࢴ甘잚뵮傾Úਈﮑ⛽♑痊츚줽ᾀ鮀",
"description": "Correct transaction without 0x prefix."
"description": "Correct transaction without 0x prefix.",
"length": 139,
"sms": 3,
"mms": 1
}
],
"invalid": [
Expand Down

0 comments on commit 41e6481

Please sign in to comment.