Skip to content

Instantly share code, notes, and snippets.

@hurelhuyag
Created May 21, 2026 01:20
Show Gist options
  • Select an option

  • Save hurelhuyag/50e17e357d19c7f48965ed7549c32c99 to your computer and use it in GitHub Desktop.

Select an option

Save hurelhuyag/50e17e357d19c7f48965ed7549c32c99 to your computer and use it in GitHub Desktop.
export type SMSEncoding = 'LATIN' | 'UNICODE';
export interface ValidationResult {
isValid: boolean;
characterCount: number;
segmentCount: number;
encodingUsed: SMSEncoding;
error?: string;
}
/**
* Validates and calculates segment counts for an internal SMS gateway.
* @param text The actual SMS body string.
* @param preferredEncoding 'LATIN' (GSM-7) or 'UNICODE' (UCS-2).
* @param maxSegments Optional cap to prevent runaway billing (Default: 5).
*/
export function validateSMS(
text: string,
preferredEncoding: SMSEncoding,
maxSegments: number = 5
): ValidationResult {
if (!text || text.length === 0) {
return { isValid: false, characterCount: 0, segmentCount: 0, encodingUsed: preferredEncoding, error: 'SMS body cannot be empty.' };
}
let finalEncoding: SMSEncoding = preferredEncoding;
let characterCount = 0;
// 1. GSM-7 Character Sets
const gsmBasic = "@£$¥èéùìòÇ\nØø\rÅåΔ_ΦΓΛΩΠΨΣΘΞÆæßÉ !\"#¤%&'()*+,-./0123456789:;<=>?¡ABCDEFGHIJKLMNOPQRSTUVWXYZÄÖÑܧ¿abcdefghijklmnopqrstuvwxyzäöñüà";
const gsmExtended = "^{}\\[~]|€"; // These take 2 slots in LATIN mode
// 2. Calculate Count and Auto-Detect Encoding Upgrades
if (preferredEncoding === 'LATIN') {
for (let i = 0; i < text.length; i++) {
const char = text[i];
if (gsmBasic.includes(char)) {
characterCount += 1;
} else if (gsmExtended.includes(char)) {
characterCount += 2; // Extended chars take double space in GSM-7
} else {
// Non-GSM character found! Force upgrade to UNICODE.
finalEncoding = 'UNICODE';
break;
}
}
}
// 3. Handle Unicode Mode (or Auto-Upgraded Unicode)
if (finalEncoding === 'UNICODE') {
// JS .length natively counts surrogate pairs (emojis) as 2.
// This perfectly matches UCS-2 byte limits (4 bytes = 2 UCS-2 characters).
characterCount = text.length;
}
// 4. Calculate Segments based on final Encoding
let segmentCount = 0;
if (finalEncoding === 'LATIN') {
if (characterCount <= 160) {
segmentCount = 1;
} else {
segmentCount = Math.ceil(characterCount / 153);
}
} else { // UNICODE
if (characterCount <= 70) {
segmentCount = 1;
} else {
segmentCount = Math.ceil(characterCount / 67);
}
}
// 5. Enforce Corporate Gateway Budget Limits
if (segmentCount > maxSegments) {
return {
isValid: false,
characterCount,
segmentCount,
encodingUsed: finalEncoding,
error: `Message exceeds maximum allowed segments (${maxSegments}). Calculated segments: ${segmentCount}.`
};
}
return {
isValid: true,
characterCount,
segmentCount,
encodingUsed: finalEncoding
};
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment