SMS limiter

This commit is contained in:
Simon Pocrnjič
2025-10-27 19:00:00 +01:00
parent 369af34ad4
commit 20d4907fc5
2 changed files with 213 additions and 0 deletions
+113
View File
@@ -268,6 +268,84 @@ const renderTokens = (text, vars) => {
});
};
// SMS length, encoding and credits
const GSM7_EXTENDED = new Set(["^", "{", "}", "\\", "[", "~", "]", "|"]);
const isGsm7 = (text) => {
for (const ch of text || "") {
if (ch === "€") continue; // Allowed via GSM 03.38 extension
const code = ch.charCodeAt(0);
if (code >= 0x80) return false; // Non-ASCII -> UCS-2
}
return true;
};
const gsm7Length = (text) => {
let len = 0;
for (const ch of text || "") {
if (ch === "€" || GSM7_EXTENDED.has(ch)) {
len += 2; // extension table uses ESC + char
} else {
len += 1;
}
}
return len;
};
const ucs2Length = (text) => (text ? text.length : 0); // UTF-16 units ~ UCS-2 cost
const smsEncoding = computed(() => (isGsm7(smsMessage.value) ? "GSM-7" : "UCS-2"));
const charCount = computed(() =>
smsEncoding.value === "GSM-7"
? gsm7Length(smsMessage.value)
: ucs2Length(smsMessage.value)
);
const perSegment = computed(() => {
const count = charCount.value;
if (smsEncoding.value === "GSM-7") {
return count <= 160 ? 160 : 153; // single vs concatenated
}
return count <= 70 ? 70 : 67;
});
const segments = computed(() => {
const count = charCount.value;
const size = perSegment.value || 1;
return count > 0 ? Math.ceil(count / size) : 0;
});
const creditsNeeded = computed(() => segments.value);
// Provider hard limit: max 4 SMS parts
// - GSM-7: 640 units total
// - Unicode (UCS-2): 320 units total
const maxAllowed = computed(() => (smsEncoding.value === "GSM-7" ? 640 : 320));
const remaining = computed(() => Math.max(0, maxAllowed.value - charCount.value));
// Truncate helper that respects GSM-7 extended char cost (2 units) and UCS-2 units (1 per code unit)
const truncateToLimit = (text, limit, encoding) => {
if (!text) return "";
if (limit <= 0) return "";
if (encoding === "UCS-2") {
// UCS-2: count per UTF-16 code unit
return text.slice(0, limit);
}
// GSM-7: iterate and sum per-char costs (extended table chars cost 2)
let acc = 0;
let out = "";
for (const ch of text) {
const cost = ch === "€" || GSM7_EXTENDED.has(ch) ? 2 : 1;
if (acc + cost > limit) break;
out += ch;
acc += cost;
}
return out;
};
// Enforce hard limits by truncating user input as they type/paste
watch(smsMessage, (val) => {
const limit = maxAllowed.value;
// If currently over limit, truncate
if (charCount.value > limit) {
smsMessage.value = truncateToLimit(val, limit, smsEncoding.value);
}
});
const buildVarsFromSelectedContract = () => {
const uuid = selectedContractUuid.value;
if (!uuid) return {};
@@ -912,6 +990,41 @@ const submitSms = () => {
class="w-full rounded border-gray-300 focus:border-indigo-500 focus:ring-indigo-500"
placeholder="Vpišite SMS vsebino..."
></textarea>
<!-- Live counters -->
<div class="mt-1 text-xs text-gray-600 flex flex-col gap-1">
<div>
<span class="font-medium">Znakov:</span>
<span class="font-mono">{{ charCount }}</span>
<span class="mx-2">|</span>
<span class="font-medium">Kodiranje:</span>
<span>{{ smsEncoding }}</span>
<span class="mx-2">|</span>
<span class="font-medium">Deli SMS:</span>
<span class="font-mono">{{ segments }}</span>
<span class="mx-2">|</span>
<span class="font-medium">Krediti:</span>
<span class="font-mono">{{ creditsNeeded }}</span>
</div>
<div>
<span class="font-medium">Omejitev:</span>
<span class="font-mono">{{ maxAllowed }}</span>
<span class="mx-2">|</span>
<span class="font-medium">Preostanek:</span>
<span class="font-mono" :class="{ 'text-red-600': remaining === 0 }">{{
remaining
}}</span>
</div>
<p class="text-[11px] text-gray-500 leading-snug">
Dolžina 160 znakov velja samo pri pošiljanju sporočil, ki vsebujejo znake, ki
ne zahtevajo enkodiranja. Če npr. želite pošiljati šumnike, ki niso del
7-bitne abecede GSM, morate uporabiti Unicode enkodiranje (UCS2). V tem
primeru je največja dolžina enega SMS sporočila 70 znakov (pri daljših
sporočilih 67 znakov na del), medtem ko je pri GSM7 160 znakov (pri daljših
sporočilih 153 znakov na del). Razširjeni znaki (^{{ "{" }}}}\\[]~| in )
štejejo dvojno. Največja dovoljena dolžina po ponudniku: 640 (GSM7) oziroma
320 (UCS2) znakov.
</p>
</div>
<label class="inline-flex items-center gap-2 text-sm text-gray-700 mt-1">
<input