Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 | 24x 24x 62368x 62368x 62368x 66524x 66524x 66524x 7796x 62368x 7796x 24x 46x 43x 43x 83x 77x 77x | /**
* Referral codes.
*
* The code is the most-handled string in the programme. It goes into a WhatsApp
* message the partner forwards, into `go.interioring.com/e/<code>`, and back to
* us as `ref <code>` when the homeowner replies — so it is read on a phone
* screen, sometimes retyped, and occasionally read aloud over a call.
*
* ── The alphabet ─────────────────────────────────────────────────────────
* 30 characters, uppercase, with `0 1 I L O U` removed:
* - 0/O and 1/I/L are the pairs people actually confuse when retyping.
* - U is dropped as well, which costs one character of entropy and removes
* the great majority of accidental English profanity from a generated
* code. A partner forwarding a rude five-letter string to a homeowner they
* are trying to impress is a real cost, and it is cheaper to avoid than to
* apologise for.
*
* ── The length ───────────────────────────────────────────────────────────
* 8 characters: 30^8 ≈ 6.6e11. At the programme's plausible ceiling — say a
* million referrals — the birthday probability of any collision at all is
* under one in a thousand, and `referrals_code_uniq` turns the case we do hit
* into a retry rather than into two homeowners sharing a link.
*
* Not sequential, and not derived from the partner id: the code appears in a
* public URL, so anything guessable would let someone enumerate other
* partners' referrals and read a homeowner's name off the page.
*/
/** No 0/1/I/L/O/U. */
const ALPHABET = "23456789ABCDEFGHJKMNPQRSTVWXYZ";
const CODE_LENGTH = 8;
/**
* Rejection sampling rather than `% ALPHABET.length`.
*
* 256 is not a multiple of 30, so a plain modulo makes the first 16 characters
* of the alphabet fractionally likelier than the rest. It would not matter for
* a display id; it matters here only because this is also the thing standing
* between a stranger and someone else's referral, and biased randomness is a
* bad habit to establish in a file whose whole job is unguessability.
*/
function randomIndex(): number {
const limit = 256 - (256 % ALPHABET.length); // 240
const byte = new Uint8Array(1);
for (;;) {
crypto.getRandomValues(byte);
const value = byte[0] as number;
if (value < limit) return value % ALPHABET.length;
}
}
export function generateReferralCode(): string {
let out = "";
for (let i = 0; i < CODE_LENGTH; i++) out += ALPHABET[randomIndex()];
return out;
}
const CODE_RE = new RegExp(`^[${ALPHABET}]{${CODE_LENGTH}}$`);
/**
* Canonicalises what a human typed, or null if it is not a code.
*
* There is deliberately NO character mapping here. The obvious feature — treat
* a typed `O` as `0`, an `I` as `1` — has nothing to operate on, because the
* alphabet excludes BOTH halves of every confusable pair. That is the whole
* point of choosing it: ambiguity is designed out at generation rather than
* guessed at on the way back in.
*
* A code containing `O` is not a code with a typo we can repair; it is either
* misread (and `Q`, `D` and `0` are all equally likely intents) or invented.
* Guessing would turn one person's mistyping into another person's referral,
* so this refuses and the UI asks them to check.
*/
export function normaliseReferralCode(
input: string | null | undefined,
): string | null {
if (!input) return null;
const cleaned = input
.trim()
.replace(/^ref\s+/i, "")
.replace(/[\s-]/g, "")
.toUpperCase();
return CODE_RE.test(cleaned) ? cleaned : null;
}
/**
* Pulls `ref <code>` out of an inbound WhatsApp message (B4, API-3).
*
* The homeowner's message is free text they may have edited, so the code can
* sit anywhere in it and carry punctuation. Anchored on the `ref` keyword
* rather than "any 8-char token" so an ordinary sentence cannot be mistaken
* for a referral.
*/
export function extractReferralCode(
text: string | null | undefined,
): string | null {
if (!text) return null;
const match = text.match(/\bref[:\s]+([A-Za-z0-9-]{6,14})\b/i);
return match ? normaliseReferralCode(match[1]) : null;
}
export {
ALPHABET as REFERRAL_CODE_ALPHABET,
CODE_LENGTH as REFERRAL_CODE_LENGTH,
};
|