diff --git a/injected/integration-test/broker-protection-tests/broker-protection.spec.js b/injected/integration-test/broker-protection-tests/broker-protection.spec.js index 920260cf5ec..06fd7db5633 100644 --- a/injected/integration-test/broker-protection-tests/broker-protection.spec.js +++ b/injected/integration-test/broker-protection-tests/broker-protection.spec.js @@ -379,6 +379,46 @@ test.describe('Broker Protection communications', () => { await dbp.doesInputValueEqual('#full-state', 'District Of Columbia'); }); + test('fillForm with state-aware generated ZIP and default city', async ({ page }, workerInfo) => { + const dbp = BrokerProtectionPage.create(page, workerInfo.project.use); + await dbp.enabled(); + await dbp.navigatesTo('form.html'); + await dbp.receivesInlineAction({ + state: { + action: { + actionType: 'fillForm', + id: '3', + selector: '.ahm', + elements: [{ type: '$generated_zip_code$', selector: '#user_zip_code', useState: true }], + }, + data: { extractedProfile: { state: 'AK' } }, + }, + }); + const response = await dbp.collector.waitForMessage('actionCompleted'); + dbp.isSuccessMessage(response); + await dbp.doesInputValueEqual('#user_zip_code', '99501'); + }); + + test('fillForm with state-aware generated ZIP and matching city', async ({ page }, workerInfo) => { + const dbp = BrokerProtectionPage.create(page, workerInfo.project.use); + await dbp.enabled(); + await dbp.navigatesTo('form.html'); + await dbp.receivesInlineAction({ + state: { + action: { + actionType: 'fillForm', + id: '3', + selector: '.ahm', + elements: [{ type: '$generated_zip_code$', selector: '#user_zip_code', useState: true }], + }, + data: { extractedProfile: { city: 'Juneau', state: 'AK' } }, + }, + }); + const response = await dbp.collector.waitForMessage('actionCompleted'); + dbp.isSuccessMessage(response); + await dbp.doesInputValueEqual('#user_zip_code', '99801'); + }); + test('fillForm with select containing numbers', async ({ page }, workerInfo) => { const dbp = BrokerProtectionPage.create(page, workerInfo.project.use); await dbp.enabled(); diff --git a/injected/integration-test/page-objects/broker-protection.js b/injected/integration-test/page-objects/broker-protection.js index c6bab1b3c24..703594496a8 100644 --- a/injected/integration-test/page-objects/broker-protection.js +++ b/injected/integration-test/page-objects/broker-protection.js @@ -175,7 +175,7 @@ export class BrokerProtectionPage { } /** - * @param {{state: {action: Record}}} action + * @param {{state: {action: Record; data?: Record}}} action * @return {Promise} */ async receivesInlineAction(action) { diff --git a/injected/src/features/broker-protection/actions/fill-form.js b/injected/src/features/broker-protection/actions/fill-form.js index 744ca98738a..e41e03dca47 100644 --- a/injected/src/features/broker-protection/actions/fill-form.js +++ b/injected/src/features/broker-protection/actions/fill-form.js @@ -1,11 +1,11 @@ -import { getElement, generateRandomInt } from '../utils/utils.js'; +import { getElement, generateRandomInt, hasOwn } from '../utils/utils.js'; import { ErrorResponse, SuccessResponse } from '../types.js'; import { generatePhoneNumber, generateZipCode, generateStreetAddress } from './generators.js'; import { states } from '../comparisons/constants.js'; /** - * @param {Record} action - * @param {Record} userData + * @param {import('../types.js').FillFormAction} action + * @param {import('../types.js').BrokerProtectionProfile} userData * @param {Document | HTMLElement} root * @return {import('../types.js').ActionResponse} */ @@ -36,8 +36,8 @@ export function fillForm(action, userData, root = document) { /** * Try to fill form elements. Collecting results + warnings for reporting. * @param {HTMLElement} root - * @param {{selector: string; type: string; min?: string; max?: string;}[]} elements - * @param {Record} data + * @param {import('../types.js').FillFormElement[]} elements + * @param {import('../types.js').BrokerProtectionProfile} data * @return {({result: true} | {result: false; error: string})[]} */ export function fillMany(root, elements, data) { @@ -55,7 +55,7 @@ export function fillMany(root, elements, data) { } else if (element.type === '$generated_phone_number$') { results.push(setValueForInput(inputElem, generatePhoneNumber())); } else if (element.type === '$generated_zip_code$') { - results.push(setValueForInput(inputElem, generateZipCode())); + results.push(setValueForInput(inputElem, generateZipCode(element.useState ? data : null))); } else if (element.type === '$generated_random_number$') { if (!element.min || !element.max) { results.push({ @@ -81,7 +81,7 @@ export function fillMany(root, elements, data) { // This is a composite of existing (but separate) city and state fields } else if (element.type === 'cityState') { - if (!Object.prototype.hasOwnProperty.call(data, 'city') || !Object.prototype.hasOwnProperty.call(data, 'state')) { + if (!hasOwn(data, 'city') || !hasOwn(data, 'state')) { results.push({ result: false, error: `element found with selector '${element.selector}', but data didn't contain the keys 'city' and 'state'`, @@ -90,7 +90,7 @@ export function fillMany(root, elements, data) { } results.push(setValueForInput(inputElem, data.city + ', ' + data.state)); } else if (element.type === 'fullState') { - if (!Object.prototype.hasOwnProperty.call(data, 'state')) { + if (!hasOwn(data, 'state')) { results.push({ result: false, error: `element found with selector '${element.selector}', but data didn't contain the key 'state'`, @@ -100,7 +100,7 @@ export function fillMany(root, elements, data) { const state = data.state; - if (!Object.prototype.hasOwnProperty.call(states, state)) { + if (!hasOwn(states, state)) { results.push({ result: false, error: `element found with selector '${element.selector}', but data contained an invalid 'state' abbreviation`, @@ -115,7 +115,7 @@ export function fillMany(root, elements, data) { if (isElementTypeOptional(element.type)) { continue; } - if (!Object.prototype.hasOwnProperty.call(data, element.type)) { + if (!hasOwn(data, element.type)) { results.push({ result: false, error: `element found with selector '${element.selector}', but data didn't contain the key '${element.type}'`, @@ -129,7 +129,7 @@ export function fillMany(root, elements, data) { }); continue; } - results.push(setValueForInput(inputElem, data[element.type])); + results.push(setValueForInput(inputElem, /** @type {string} */ (data[element.type]))); } } diff --git a/injected/src/features/broker-protection/actions/generators.js b/injected/src/features/broker-protection/actions/generators.js index 66dae2451f4..14199e44f88 100644 --- a/injected/src/features/broker-protection/actions/generators.js +++ b/injected/src/features/broker-protection/actions/generators.js @@ -1,4 +1,5 @@ -import { generateRandomInt } from '../utils/utils.js'; +import { generateRandomInt, getOwn, hasOwn } from '../utils/utils.js'; +import STATE_CITY_ZIPS from './state-city-zips.json' with { type: 'json' }; export function generatePhoneNumber() { /** @@ -14,9 +15,31 @@ export function generatePhoneNumber() { return `${areaCode}${exchangeCode}${lineNumber}`; } -export function generateZipCode() { - const zipCode = generateRandomInt(10000, 99999).toString(); - return zipCode; +/** + * @return {string} Random 5-digit ZIP code. + */ +const generateRandomZipCode = () => generateRandomInt(10000, 99999).toString(); + +/** + * Generate a 5-digit ZIP code. When state and city are provided, looks up a + * real ZIP from a curated city list. If the city is not in the list, falls + * back to the default (largest) city in the state. If the state is unknown, + * returns a random 5-digit number. + * + * @param {{state?: unknown; city?: unknown} | null} [params] + * @return {string} + */ +export function generateZipCode(params) { + const { state, city } = params ?? {}; + const stateUpperCase = typeof state === 'string' ? state.toUpperCase() : null; + if (!stateUpperCase || !hasOwn(STATE_CITY_ZIPS, stateUpperCase)) { + return generateRandomZipCode(); + } + + const stateCities = STATE_CITY_ZIPS[stateUpperCase]; + const cityUpperCase = typeof city === 'string' ? city.toUpperCase() : null; + const zips = getOwn(stateCities, cityUpperCase) || getOwn(stateCities, stateCities._default); + return zips?.[generateRandomInt(0, zips.length - 1)] ?? generateRandomZipCode(); } export function generateStreetAddress() { diff --git a/injected/src/features/broker-protection/actions/state-city-zips.json b/injected/src/features/broker-protection/actions/state-city-zips.json new file mode 100644 index 00000000000..36934019259 --- /dev/null +++ b/injected/src/features/broker-protection/actions/state-city-zips.json @@ -0,0 +1,1113 @@ +{ + "AL": { + "_default": "BIRMINGHAM", + "BIRMINGHAM": ["35203"], + "MONTGOMERY": ["36104"], + "HUNTSVILLE": ["35801"], + "MOBILE": ["36602"], + "TUSCALOOSA": ["35401"], + "HOOVER": ["35244"], + "DOTHAN": ["36301"], + "AUBURN": ["36830"], + "DECATUR": ["35601"], + "MADISON": ["35758"], + "FLORENCE": ["35630"], + "GADSDEN": ["35901"], + "VESTAVIA HILLS": ["35243"], + "PRATTVILLE": ["36067"], + "PHENIX CITY": ["36867"], + "OPELIKA": ["36801"] + }, + "AK": { + "_default": "ANCHORAGE", + "ANCHORAGE": ["99501"], + "JUNEAU": ["99801"], + "FAIRBANKS": ["99701"], + "WASILLA": ["99654"], + "SITKA": ["99835"], + "KETCHIKAN": ["99901"] + }, + "AZ": { + "_default": "PHOENIX", + "PHOENIX": ["85003", "85004", "85006", "85007", "85008", "85012", "85014"], + "TUCSON": ["85701", "85705", "85711", "85712", "85716", "85719"], + "MESA": ["85201", "85202", "85203", "85204", "85210"], + "CHANDLER": ["85225"], + "SCOTTSDALE": ["85251"], + "GLENDALE": ["85301"], + "GILBERT": ["85234"], + "TEMPE": ["85281"], + "PEORIA": ["85345"], + "SURPRISE": ["85374"], + "YUMA": ["85364"], + "FLAGSTAFF": ["86001"], + "GOODYEAR": ["85338"], + "AVONDALE": ["85323"], + "PRESCOTT": ["86301"], + "BUCKEYE": ["85326"], + "LAKE HAVASU CITY": ["86403"], + "CASA GRANDE": ["85122"], + "MARICOPA": ["85138"], + "QUEEN CREEK": ["85142"] + }, + "AR": { + "_default": "LITTLE ROCK", + "LITTLE ROCK": ["72201"], + "FORT SMITH": ["72901"], + "FAYETTEVILLE": ["72701"], + "SPRINGDALE": ["72764"], + "JONESBORO": ["72401"], + "NORTH LITTLE ROCK": ["72114"], + "CONWAY": ["72032"], + "PINE BLUFF": ["71601"], + "ROGERS": ["72756"], + "BENTONVILLE": ["72712"], + "HOT SPRINGS": ["71901"], + "TEXARKANA": ["71854"], + "BENTON": ["72015"] + }, + "CA": { + "_default": "LOS ANGELES", + "LOS ANGELES": ["90012", "90013", "90014", "90015", "90017", "90021", "90071"], + "SAN DIEGO": ["92101", "92102", "92103", "92104", "92108", "92110"], + "SAN JOSE": ["95110", "95112", "95113", "95116", "95125", "95126"], + "SAN FRANCISCO": ["94102", "94103", "94104", "94105", "94107", "94108", "94109"], + "FRESNO": ["93701", "93702", "93706", "93721", "93728"], + "SACRAMENTO": ["95811", "95814", "95816", "95818", "95819"], + "LONG BEACH": ["90802", "90803", "90806", "90813", "90814"], + "OAKLAND": ["94601", "94606", "94607", "94610", "94612"], + "BAKERSFIELD": ["93301"], + "ANAHEIM": ["92805"], + "SANTA ANA": ["92701"], + "RIVERSIDE": ["92501"], + "STOCKTON": ["95202"], + "IRVINE": ["92612"], + "CHULA VISTA": ["91910"], + "FREMONT": ["94538"], + "SAN BERNARDINO": ["92401"], + "MODESTO": ["95354"], + "MORENO VALLEY": ["92553"], + "FONTANA": ["92335"], + "GLENDALE": ["91205"], + "HUNTINGTON BEACH": ["92648"], + "SANTA CLARITA": ["91355"], + "GARDEN GROVE": ["92840"], + "OCEANSIDE": ["92054"], + "RANCHO CUCAMONGA": ["91730"], + "ONTARIO": ["91764"], + "SANTA ROSA": ["95401"], + "ELK GROVE": ["95624"], + "CORONA": ["92882"], + "PALMDALE": ["93550"], + "LANCASTER": ["93534"], + "SALINAS": ["93901"], + "POMONA": ["91766"], + "HAYWARD": ["94541"], + "ESCONDIDO": ["92025"], + "SUNNYVALE": ["94086"], + "TORRANCE": ["90503"], + "PASADENA": ["91101"], + "ROSEVILLE": ["95678"], + "CONCORD": ["94520"], + "VISALIA": ["93291"], + "SANTA CLARA": ["95050"], + "VICTORVILLE": ["92392"], + "VALLEJO": ["94590"], + "BERKELEY": ["94704"], + "EL MONTE": ["91731"], + "DOWNEY": ["90241"], + "COSTA MESA": ["92627"], + "INGLEWOOD": ["90301"], + "CARLSBAD": ["92008"], + "FAIRFIELD": ["94533"], + "WEST COVINA": ["91791"], + "MURRIETA": ["92562"], + "RICHMOND": ["94804"], + "NORWALK": ["90650"], + "ANTIOCH": ["94509"], + "TEMECULA": ["92591"], + "BURBANK": ["91502"], + "DALY CITY": ["94015"], + "EL CAJON": ["92020"], + "SAN MATEO": ["94401"], + "RIALTO": ["92376"], + "CLOVIS": ["93612"], + "COMPTON": ["90220"], + "SANTA MARIA": ["93454"], + "MISSION VIEJO": ["92691"], + "SOUTH GATE": ["90280"], + "CARSON": ["90745"], + "REDDING": ["96001"], + "SANTA BARBARA": ["93101"], + "CHICO": ["95928"], + "NAPA": ["94559"], + "SAN RAFAEL": ["94901"], + "VACAVILLE": ["95688"], + "SAN LEANDRO": ["94577"] + }, + "CO": { + "_default": "DENVER", + "DENVER": ["80202", "80203", "80204", "80205", "80206", "80211", "80218"], + "COLORADO SPRINGS": ["80903", "80904", "80905", "80907", "80909", "80910"], + "AURORA": ["80012"], + "FORT COLLINS": ["80524"], + "LAKEWOOD": ["80226"], + "THORNTON": ["80241"], + "ARVADA": ["80002"], + "WESTMINSTER": ["80030"], + "PUEBLO": ["81003"], + "CENTENNIAL": ["80112"], + "BOULDER": ["80302"], + "GREELEY": ["80631"], + "LONGMONT": ["80501"], + "LOVELAND": ["80537"], + "BROOMFIELD": ["80020"], + "CASTLE ROCK": ["80104"], + "GRAND JUNCTION": ["81501"], + "COMMERCE CITY": ["80022"], + "PARKER": ["80134"] + }, + "CT": { + "_default": "BRIDGEPORT", + "BRIDGEPORT": ["06604"], + "NEW HAVEN": ["06510"], + "STAMFORD": ["06901"], + "HARTFORD": ["06103"], + "WATERBURY": ["06702"], + "NORWALK": ["06851"], + "DANBURY": ["06810"], + "NEW BRITAIN": ["06051"], + "WEST HAVEN": ["06516"], + "MERIDEN": ["06450"], + "BRISTOL": ["06010"], + "MIDDLETOWN": ["06457"], + "MANCHESTER": ["06040"], + "NEW LONDON": ["06320"] + }, + "DE": { + "_default": "WILMINGTON", + "WILMINGTON": ["19801"], + "DOVER": ["19901"], + "NEWARK": ["19711"], + "MIDDLETOWN": ["19709"], + "BEAR": ["19701"], + "MILFORD": ["19963"] + }, + "DC": { + "_default": "WASHINGTON", + "WASHINGTON": ["20001", "20002", "20003", "20004", "20005", "20006", "20036"] + }, + "FL": { + "_default": "JACKSONVILLE", + "JACKSONVILLE": ["32202", "32204", "32205", "32206", "32207", "32210", "32211"], + "MIAMI": ["33125", "33128", "33129", "33130", "33131", "33132", "33136"], + "TAMPA": ["33602", "33603", "33604", "33605", "33606", "33607", "33609"], + "ORLANDO": ["32801"], + "ST. PETERSBURG": ["33701"], + "HIALEAH": ["33012"], + "TALLAHASSEE": ["32301"], + "FORT LAUDERDALE": ["33301"], + "PORT ST. LUCIE": ["34952"], + "CAPE CORAL": ["33904"], + "PEMBROKE PINES": ["33024"], + "HOLLYWOOD": ["33020"], + "GAINESVILLE": ["32601"], + "MIRAMAR": ["33025"], + "CORAL SPRINGS": ["33065"], + "CLEARWATER": ["33755"], + "WEST PALM BEACH": ["33401"], + "LAKELAND": ["33801"], + "POMPANO BEACH": ["33060"], + "MIAMI GARDENS": ["33056"], + "DAVIE": ["33314"], + "BOCA RATON": ["33432"], + "SUNRISE": ["33322"], + "PLANTATION": ["33324"], + "PALM BAY": ["32907"], + "FORT MYERS": ["33901"], + "DELTONA": ["32725"], + "LARGO": ["33770"], + "MELBOURNE": ["32901"], + "DEERFIELD BEACH": ["33441"], + "KISSIMMEE": ["34741"], + "DAYTONA BEACH": ["32114"], + "NORTH PORT": ["34287"], + "SARASOTA": ["34236"], + "PENSACOLA": ["32502"], + "OCALA": ["34471"], + "HOMESTEAD": ["33030"], + "PALM COAST": ["32137"], + "BONITA SPRINGS": ["34134"], + "SANFORD": ["32771"], + "BRADENTON": ["34205"], + "APOPKA": ["32703"], + "WINTER HAVEN": ["33880"] + }, + "GA": { + "_default": "ATLANTA", + "ATLANTA": ["30303", "30308", "30309", "30312", "30313", "30314", "30316"], + "AUGUSTA": ["30901"], + "COLUMBUS": ["31901"], + "SAVANNAH": ["31401"], + "ATHENS": ["30601"], + "SANDY SPRINGS": ["30328"], + "MACON": ["31201"], + "ROSWELL": ["30075"], + "JOHNS CREEK": ["30022"], + "ALBANY": ["31701"], + "WARNER ROBINS": ["31088"], + "ALPHARETTA": ["30009"], + "MARIETTA": ["30060"], + "VALDOSTA": ["31601"], + "SMYRNA": ["30080"], + "BROOKHAVEN": ["30319"], + "DUNWOODY": ["30338"], + "PEACHTREE CITY": ["30269"], + "KENNESAW": ["30144"], + "DALTON": ["30720"], + "STATESBORO": ["30458"], + "GAINESVILLE": ["30501"], + "NEWNAN": ["30263"], + "POOLER": ["31322"] + }, + "HI": { + "_default": "HONOLULU", + "HONOLULU": ["96813"], + "HILO": ["96720"], + "KAILUA": ["96734"], + "PEARL CITY": ["96782"], + "KANEOHE": ["96744"], + "WAIPAHU": ["96797"], + "KAPOLEI": ["96707"] + }, + "ID": { + "_default": "BOISE", + "BOISE": ["83702"], + "MERIDIAN": ["83642"], + "NAMPA": ["83651"], + "IDAHO FALLS": ["83401"], + "POCATELLO": ["83201"], + "CALDWELL": ["83605"], + "COEUR D'ALENE": ["83814"], + "TWIN FALLS": ["83301"], + "LEWISTON": ["83501"], + "POST FALLS": ["83854"], + "MOSCOW": ["83843"] + }, + "IL": { + "_default": "CHICAGO", + "CHICAGO": ["60601", "60602", "60603", "60604", "60605", "60606", "60607"], + "AURORA": ["60505"], + "ROCKFORD": ["61101"], + "JOLIET": ["60432"], + "NAPERVILLE": ["60540"], + "SPRINGFIELD": ["62701"], + "PEORIA": ["61602"], + "ELGIN": ["60120"], + "WAUKEGAN": ["60085"], + "CHAMPAIGN": ["61820"], + "BLOOMINGTON": ["61701"], + "DECATUR": ["62521"], + "EVANSTON": ["60201"], + "ARLINGTON HEIGHTS": ["60004"], + "SCHAUMBURG": ["60193"], + "BOLINGBROOK": ["60440"], + "PALATINE": ["60067"], + "SKOKIE": ["60076"], + "DES PLAINES": ["60016"], + "ORLAND PARK": ["60462"], + "TINLEY PARK": ["60477"], + "OAK LAWN": ["60453"], + "NORMAL": ["61761"], + "DEKALB": ["60115"] + }, + "IN": { + "_default": "INDIANAPOLIS", + "INDIANAPOLIS": ["46201", "46202", "46204", "46205", "46208", "46218", "46225"], + "FORT WAYNE": ["46802"], + "EVANSVILLE": ["47708"], + "SOUTH BEND": ["46601"], + "CARMEL": ["46032"], + "FISHERS": ["46038"], + "BLOOMINGTON": ["47401"], + "HAMMOND": ["46320"], + "GARY": ["46402"], + "LAFAYETTE": ["47901"], + "MUNCIE": ["47305"], + "TERRE HAUTE": ["47807"], + "NOBLESVILLE": ["46060"], + "GREENWOOD": ["46142"], + "ANDERSON": ["46016"], + "KOKOMO": ["46901"], + "ELKHART": ["46514"], + "MISHAWAKA": ["46544"] + }, + "IA": { + "_default": "DES MOINES", + "DES MOINES": ["50309"], + "CEDAR RAPIDS": ["52401"], + "DAVENPORT": ["52801"], + "SIOUX CITY": ["51101"], + "IOWA CITY": ["52240"], + "WATERLOO": ["50701"], + "COUNCIL BLUFFS": ["51501"], + "AMES": ["50010"], + "DUBUQUE": ["52001"], + "WEST DES MOINES": ["50265"], + "ANKENY": ["50023"], + "URBANDALE": ["50322"], + "MASON CITY": ["50401"], + "CEDAR FALLS": ["50613"], + "BETTENDORF": ["52722"] + }, + "KS": { + "_default": "WICHITA", + "WICHITA": ["67202"], + "OVERLAND PARK": ["66204"], + "KANSAS CITY": ["66101"], + "OLATHE": ["66061"], + "TOPEKA": ["66603"], + "LAWRENCE": ["66044"], + "SHAWNEE": ["66203"], + "MANHATTAN": ["66502"], + "LENEXA": ["66215"], + "SALINA": ["67401"], + "HUTCHINSON": ["67501"], + "LEAVENWORTH": ["66048"], + "LEAWOOD": ["66209"], + "GARDEN CITY": ["67846"], + "DODGE CITY": ["67801"] + }, + "KY": { + "_default": "LOUISVILLE", + "LOUISVILLE": ["40202", "40203", "40204", "40206", "40208", "40210", "40211"], + "LEXINGTON": ["40507"], + "BOWLING GREEN": ["42101"], + "OWENSBORO": ["42301"], + "COVINGTON": ["41011"], + "FRANKFORT": ["40601"], + "RICHMOND": ["40475"], + "FLORENCE": ["41042"], + "GEORGETOWN": ["40324"], + "HENDERSON": ["42420"], + "NICHOLASVILLE": ["40356"], + "ELIZABETHTOWN": ["42701"], + "HOPKINSVILLE": ["42240"], + "PADUCAH": ["42001"], + "RADCLIFF": ["40160"] + }, + "LA": { + "_default": "NEW ORLEANS", + "NEW ORLEANS": ["70112", "70113", "70116", "70118", "70119", "70125", "70130"], + "BATON ROUGE": ["70801"], + "SHREVEPORT": ["71101"], + "LAFAYETTE": ["70501"], + "LAKE CHARLES": ["70601"], + "KENNER": ["70062"], + "BOSSIER CITY": ["71111"], + "MONROE": ["71201"], + "ALEXANDRIA": ["71301"], + "HOUMA": ["70360"], + "NEW IBERIA": ["70560"], + "SLIDELL": ["70458"], + "NATCHITOCHES": ["71457"], + "RUSTON": ["71270"] + }, + "ME": { + "_default": "PORTLAND", + "PORTLAND": ["04101"], + "LEWISTON": ["04240"], + "BANGOR": ["04401"], + "AUGUSTA": ["04330"], + "SOUTH PORTLAND": ["04106"], + "BIDDEFORD": ["04005"], + "AUBURN": ["04210"] + }, + "MD": { + "_default": "BALTIMORE", + "BALTIMORE": ["21201", "21202", "21211", "21213", "21217", "21218", "21230"], + "FREDERICK": ["21701"], + "ROCKVILLE": ["20850"], + "GAITHERSBURG": ["20877"], + "BOWIE": ["20715"], + "HAGERSTOWN": ["21740"], + "ANNAPOLIS": ["21401"], + "COLLEGE PARK": ["20740"], + "SALISBURY": ["21801"], + "LAUREL": ["20707"], + "CUMBERLAND": ["21502"], + "ELKTON": ["21921"] + }, + "MA": { + "_default": "BOSTON", + "BOSTON": ["02101", "02108", "02109", "02110", "02111", "02113", "02114", "02116"], + "WORCESTER": ["01608"], + "SPRINGFIELD": ["01103"], + "CAMBRIDGE": ["02139"], + "LOWELL": ["01852"], + "BROCKTON": ["02301"], + "NEW BEDFORD": ["02740"], + "QUINCY": ["02169"], + "LYNN": ["01901"], + "FALL RIVER": ["02720"], + "NEWTON": ["02458"], + "SOMERVILLE": ["02143"], + "LAWRENCE": ["01840"], + "HAVERHILL": ["01830"], + "WALTHAM": ["02451"], + "MALDEN": ["02148"], + "MEDFORD": ["02155"], + "TAUNTON": ["02780"], + "REVERE": ["02151"], + "PITTSFIELD": ["01201"], + "ATTLEBORO": ["02703"] + }, + "MI": { + "_default": "DETROIT", + "DETROIT": ["48226"], + "GRAND RAPIDS": ["49503"], + "WARREN": ["48091"], + "STERLING HEIGHTS": ["48312"], + "ANN ARBOR": ["48104"], + "LANSING": ["48933"], + "FLINT": ["48502"], + "DEARBORN": ["48126"], + "LIVONIA": ["48150"], + "TROY": ["48084"], + "WESTLAND": ["48185"], + "FARMINGTON HILLS": ["48334"], + "KALAMAZOO": ["49007"], + "CANTON": ["48187"], + "WYOMING": ["49509"], + "SOUTHFIELD": ["48075"], + "ROCHESTER HILLS": ["48309"], + "TAYLOR": ["48180"], + "PONTIAC": ["48342"], + "ST. CLAIR SHORES": ["48080"], + "ROYAL OAK": ["48067"], + "NOVI": ["48375"], + "MUSKEGON": ["49440"], + "SAGINAW": ["48607"], + "BATTLE CREEK": ["49017"], + "MIDLAND": ["48640"], + "PORT HURON": ["48060"] + }, + "MN": { + "_default": "MINNEAPOLIS", + "MINNEAPOLIS": ["55401", "55402", "55403", "55404", "55405", "55408", "55414"], + "ST. PAUL": ["55101"], + "ROCHESTER": ["55901"], + "BLOOMINGTON": ["55420"], + "DULUTH": ["55802"], + "BROOKLYN PARK": ["55443"], + "PLYMOUTH": ["55446"], + "MAPLE GROVE": ["55369"], + "WOODBURY": ["55125"], + "ST. CLOUD": ["56301"], + "EAGAN": ["55122"], + "EDEN PRAIRIE": ["55344"], + "COON RAPIDS": ["55433"], + "BURNSVILLE": ["55337"], + "BLAINE": ["55449"], + "LAKEVILLE": ["55044"], + "MANKATO": ["56001"], + "MINNETONKA": ["55345"], + "APPLE VALLEY": ["55124"], + "MOORHEAD": ["56560"] + }, + "MS": { + "_default": "JACKSON", + "JACKSON": ["39201"], + "GULFPORT": ["39501"], + "SOUTHAVEN": ["38671"], + "HATTIESBURG": ["39401"], + "BILOXI": ["39530"], + "MERIDIAN": ["39301"], + "OLIVE BRANCH": ["38654"], + "TUPELO": ["38801"], + "GREENVILLE": ["38701"], + "PEARL": ["39208"], + "HORN LAKE": ["38637"], + "STARKVILLE": ["39759"], + "OXFORD": ["38655"], + "COLUMBUS": ["39701"], + "CLINTON": ["39056"], + "MADISON": ["39110"] + }, + "MO": { + "_default": "KANSAS CITY", + "KANSAS CITY": ["64105", "64106", "64108", "64109", "64110", "64111", "64112"], + "ST. LOUIS": ["63101"], + "SPRINGFIELD": ["65806"], + "COLUMBIA": ["65201"], + "INDEPENDENCE": ["64050"], + "LEE'S SUMMIT": ["64063"], + "O'FALLON": ["63366"], + "ST. JOSEPH": ["64501"], + "ST. CHARLES": ["63301"], + "ST. PETERS": ["63376"], + "JEFFERSON CITY": ["65101"], + "BLUE SPRINGS": ["64015"], + "JOPLIN": ["64801"], + "FLORISSANT": ["63031"], + "CAPE GIRARDEAU": ["63701"], + "LIBERTY": ["64068"] + }, + "MT": { + "_default": "BILLINGS", + "BILLINGS": ["59101"], + "MISSOULA": ["59801"], + "GREAT FALLS": ["59401"], + "HELENA": ["59601"], + "BOZEMAN": ["59715"], + "BUTTE": ["59701"], + "KALISPELL": ["59901"], + "HAVRE": ["59501"] + }, + "NE": { + "_default": "OMAHA", + "OMAHA": ["68102", "68104", "68105", "68106", "68107", "68108", "68110"], + "LINCOLN": ["68508"], + "BELLEVUE": ["68005"], + "GRAND ISLAND": ["68801"], + "KEARNEY": ["68847"], + "FREMONT": ["68025"], + "HASTINGS": ["68901"], + "NORTH PLATTE": ["69101"], + "NORFOLK": ["68701"], + "PAPILLION": ["68046"], + "COLUMBUS": ["68601"], + "SCOTTSBLUFF": ["69361"] + }, + "NV": { + "_default": "LAS VEGAS", + "LAS VEGAS": ["89101", "89102", "89104", "89106", "89109", "89119", "89120"], + "HENDERSON": ["89015"], + "RENO": ["89501"], + "NORTH LAS VEGAS": ["89030"], + "SPARKS": ["89431"], + "CARSON CITY": ["89701"], + "ELKO": ["89801"], + "FERNLEY": ["89408"], + "MESQUITE": ["89027"] + }, + "NH": { + "_default": "MANCHESTER", + "MANCHESTER": ["03101"], + "NASHUA": ["03060"], + "CONCORD": ["03301"], + "DOVER": ["03820"], + "ROCHESTER": ["03867"], + "KEENE": ["03431"], + "PORTSMOUTH": ["03801"], + "LACONIA": ["03246"] + }, + "NJ": { + "_default": "NEWARK", + "NEWARK": ["07102"], + "JERSEY CITY": ["07302"], + "PATERSON": ["07501"], + "ELIZABETH": ["07201"], + "TRENTON": ["08608"], + "CLIFTON": ["07011"], + "CAMDEN": ["08101"], + "PASSAIC": ["07055"], + "UNION CITY": ["07087"], + "BAYONNE": ["07002"], + "EAST ORANGE": ["07017"], + "VINELAND": ["08360"], + "NEW BRUNSWICK": ["08901"], + "HOBOKEN": ["07030"], + "PERTH AMBOY": ["08861"], + "PLAINFIELD": ["07060"], + "HACKENSACK": ["07601"], + "ATLANTIC CITY": ["08401"], + "LINDEN": ["07036"], + "WEST NEW YORK": ["07093"], + "IRVINGTON": ["07111"], + "CHERRY HILL": ["08002"], + "TOMS RIVER": ["08753"], + "EDISON": ["08817"] + }, + "NM": { + "_default": "ALBUQUERQUE", + "ALBUQUERQUE": ["87101", "87102", "87104", "87106", "87108", "87110"], + "LAS CRUCES": ["88001"], + "RIO RANCHO": ["87124"], + "SANTA FE": ["87501"], + "ROSWELL": ["88201"], + "FARMINGTON": ["87401"], + "CLOVIS": ["88101"], + "HOBBS": ["88240"], + "ALAMOGORDO": ["88310"], + "CARLSBAD": ["88220"] + }, + "NY": { + "_default": "NEW YORK", + "NEW YORK": ["10001", "10002", "10003", "10004", "10005", "10007", "10013", "10016", "10018"], + "BUFFALO": ["14202"], + "ROCHESTER": ["14604"], + "YONKERS": ["10701"], + "SYRACUSE": ["13202"], + "ALBANY": ["12207"], + "NEW ROCHELLE": ["10801"], + "MOUNT VERNON": ["10550"], + "SCHENECTADY": ["12305"], + "UTICA": ["13501"], + "WHITE PLAINS": ["10601"], + "TROY": ["12180"], + "NIAGARA FALLS": ["14301"], + "BINGHAMTON": ["13901"], + "ITHACA": ["14850"], + "POUGHKEEPSIE": ["12601"], + "ROME": ["13440"], + "NEWBURGH": ["12550"], + "JAMESTOWN": ["14701"], + "SARATOGA SPRINGS": ["12866"], + "WATERTOWN": ["13601"], + "MIDDLETOWN": ["10940"] + }, + "NC": { + "_default": "CHARLOTTE", + "CHARLOTTE": ["28202", "28203", "28204", "28205", "28206", "28208", "28209"], + "RALEIGH": ["27601", "27603", "27604", "27605", "27607", "27608", "27609"], + "GREENSBORO": ["27401"], + "DURHAM": ["27701"], + "WINSTON-SALEM": ["27101"], + "FAYETTEVILLE": ["28301"], + "CARY": ["27511"], + "WILMINGTON": ["28401"], + "HIGH POINT": ["27260"], + "CONCORD": ["28025"], + "ASHEVILLE": ["28801"], + "GASTONIA": ["28052"], + "JACKSONVILLE": ["28540"], + "CHAPEL HILL": ["27514"], + "HUNTERSVILLE": ["28078"], + "APEX": ["27502"], + "GREENVILLE": ["27834"], + "ROCKY MOUNT": ["27801"], + "BURLINGTON": ["27215"], + "KANNAPOLIS": ["28081"], + "MOORESVILLE": ["28115"], + "HICKORY": ["28601"], + "SANFORD": ["27330"], + "INDIAN TRAIL": ["28079"], + "GOLDSBORO": ["27530"], + "LUMBERTON": ["28358"] + }, + "ND": { + "_default": "FARGO", + "FARGO": ["58102"], + "BISMARCK": ["58501"], + "GRAND FORKS": ["58201"], + "MINOT": ["58701"], + "WEST FARGO": ["58078"], + "WILLISTON": ["58801"], + "MANDAN": ["58554"], + "DICKINSON": ["58601"] + }, + "OH": { + "_default": "COLUMBUS", + "COLUMBUS": ["43201", "43202", "43203", "43205", "43206", "43210", "43215"], + "CLEVELAND": ["44114"], + "CINCINNATI": ["45202"], + "TOLEDO": ["43604"], + "AKRON": ["44308"], + "DAYTON": ["45402"], + "PARMA": ["44129"], + "CANTON": ["44702"], + "YOUNGSTOWN": ["44503"], + "LORAIN": ["44052"], + "HAMILTON": ["45011"], + "SPRINGFIELD": ["45502"], + "LAKEWOOD": ["44107"], + "KETTERING": ["45429"], + "ELYRIA": ["44035"], + "MANSFIELD": ["44902"], + "NEWARK": ["43055"], + "CUYAHOGA FALLS": ["44221"], + "EUCLID": ["44117"], + "MENTOR": ["44060"], + "DUBLIN": ["43017"], + "GROVE CITY": ["43123"], + "STRONGSVILLE": ["44136"], + "FAIRFIELD": ["45014"], + "FINDLAY": ["45840"], + "HUBER HEIGHTS": ["45424"], + "LANCASTER": ["43130"], + "WARREN": ["44481"], + "LIMA": ["45801"], + "ZANESVILLE": ["43701"], + "CHILLICOTHE": ["45601"] + }, + "OK": { + "_default": "OKLAHOMA CITY", + "OKLAHOMA CITY": ["73102", "73104", "73105", "73106", "73107", "73108", "73109"], + "TULSA": ["74103", "74104", "74105", "74106", "74119", "74120"], + "NORMAN": ["73069"], + "BROKEN ARROW": ["74012"], + "EDMOND": ["73013"], + "LAWTON": ["73501"], + "MOORE": ["73160"], + "MIDWEST CITY": ["73110"], + "ENID": ["73701"], + "STILLWATER": ["74074"], + "MUSKOGEE": ["74401"], + "BARTLESVILLE": ["74003"], + "SHAWNEE": ["74801"], + "OWASSO": ["74055"], + "YUKON": ["73099"], + "PONCA CITY": ["74601"], + "ARDMORE": ["73401"], + "DUNCAN": ["73533"] + }, + "OR": { + "_default": "PORTLAND", + "PORTLAND": ["97201", "97202", "97204", "97205", "97209", "97210", "97214"], + "SALEM": ["97301"], + "EUGENE": ["97401"], + "GRESHAM": ["97030"], + "HILLSBORO": ["97123"], + "BEAVERTON": ["97005"], + "BEND": ["97701"], + "MEDFORD": ["97501"], + "SPRINGFIELD": ["97477"], + "CORVALLIS": ["97330"], + "ALBANY": ["97321"], + "TIGARD": ["97223"], + "LAKE OSWEGO": ["97034"], + "OREGON CITY": ["97045"], + "GRANTS PASS": ["97526"], + "REDMOND": ["97756"], + "TUALATIN": ["97062"], + "WEST LINN": ["97068"], + "WOODBURN": ["97071"], + "ASHLAND": ["97520"], + "ROSEBURG": ["97470"], + "MCMINNVILLE": ["97128"], + "KLAMATH FALLS": ["97601"], + "PENDLETON": ["97801"] + }, + "PA": { + "_default": "PHILADELPHIA", + "PHILADELPHIA": ["19102", "19103", "19106", "19107", "19123", "19130", "19146"], + "PITTSBURGH": ["15222"], + "ALLENTOWN": ["18101"], + "ERIE": ["16501"], + "READING": ["19601"], + "SCRANTON": ["18503"], + "BETHLEHEM": ["18015"], + "LANCASTER": ["17602"], + "HARRISBURG": ["17101"], + "YORK": ["17401"], + "WILKES-BARRE": ["18701"], + "CHESTER": ["19013"], + "STATE COLLEGE": ["16801"], + "WILLIAMSPORT": ["17701"], + "EASTON": ["18042"], + "NORRISTOWN": ["19401"], + "LEBANON": ["17042"], + "HAZLETON": ["18201"], + "POTTSVILLE": ["17901"], + "CHAMBERSBURG": ["17201"], + "JOHNSTOWN": ["15901"], + "NEW CASTLE": ["16101"] + }, + "RI": { + "_default": "PROVIDENCE", + "PROVIDENCE": ["02903"], + "WARWICK": ["02886"], + "CRANSTON": ["02920"], + "PAWTUCKET": ["02860"], + "EAST PROVIDENCE": ["02914"], + "WOONSOCKET": ["02895"], + "COVENTRY": ["02816"], + "NEWPORT": ["02840"] + }, + "SC": { + "_default": "COLUMBIA", + "COLUMBIA": ["29201"], + "CHARLESTON": ["29401"], + "NORTH CHARLESTON": ["29405"], + "MOUNT PLEASANT": ["29464"], + "ROCK HILL": ["29730"], + "GREENVILLE": ["29601"], + "SUMMERVILLE": ["29483"], + "GOOSE CREEK": ["29445"], + "HILTON HEAD ISLAND": ["29928"], + "FLORENCE": ["29501"], + "SPARTANBURG": ["29301"], + "MYRTLE BEACH": ["29577"], + "GREER": ["29650"], + "SUMTER": ["29150"], + "ANDERSON": ["29621"], + "AIKEN": ["29801"], + "BEAUFORT": ["29902"], + "BLUFFTON": ["29910"] + }, + "SD": { + "_default": "SIOUX FALLS", + "SIOUX FALLS": ["57104"], + "RAPID CITY": ["57701"], + "PIERRE": ["57501"], + "ABERDEEN": ["57401"], + "BROOKINGS": ["57006"], + "WATERTOWN": ["57201"], + "MITCHELL": ["57301"], + "HURON": ["57350"], + "YANKTON": ["57078"] + }, + "TN": { + "_default": "MEMPHIS", + "MEMPHIS": ["38103", "38104", "38105", "38106", "38107", "38111", "38112"], + "NASHVILLE": ["37201", "37203", "37204", "37206", "37208", "37210", "37212"], + "KNOXVILLE": ["37902"], + "CHATTANOOGA": ["37402"], + "CLARKSVILLE": ["37040"], + "MURFREESBORO": ["37130"], + "FRANKLIN": ["37064"], + "JACKSON": ["38301"], + "JOHNSON CITY": ["37601"], + "BARTLETT": ["38133"], + "HENDERSONVILLE": ["37075"], + "KINGSPORT": ["37660"], + "COLLIERVILLE": ["38017"], + "SMYRNA": ["37167"], + "CLEVELAND": ["37311"], + "BRENTWOOD": ["37027"], + "GERMANTOWN": ["38138"], + "GALLATIN": ["37066"], + "SPRING HILL": ["37174"], + "LEBANON": ["37087"], + "COOKEVILLE": ["38501"], + "OAK RIDGE": ["37830"], + "MORRISTOWN": ["37813"] + }, + "TX": { + "_default": "HOUSTON", + "HOUSTON": ["77002", "77003", "77004", "77006", "77007", "77008", "77019", "77098"], + "SAN ANTONIO": ["78202", "78204", "78205", "78207", "78210", "78212", "78215"], + "DALLAS": ["75201", "75202", "75204", "75206", "75207", "75210", "75226"], + "AUSTIN": ["78701", "78702", "78703", "78704", "78705", "78751"], + "FORT WORTH": ["76102", "76103", "76104", "76107", "76109", "76110"], + "EL PASO": ["79901", "79902", "79903", "79905", "79906", "79915"], + "ARLINGTON": ["76010", "76011", "76012", "76013", "76014"], + "CORPUS CHRISTI": ["78401"], + "PLANO": ["75074"], + "LAREDO": ["78040"], + "LUBBOCK": ["79401"], + "GARLAND": ["75040"], + "IRVING": ["75060"], + "AMARILLO": ["79101"], + "GRAND PRAIRIE": ["75050"], + "BROWNSVILLE": ["78520"], + "MCKINNEY": ["75069"], + "FRISCO": ["75034"], + "PASADENA": ["77502"], + "KILLEEN": ["76541"], + "MESQUITE": ["75149"], + "MCALLEN": ["78501"], + "MIDLAND": ["79701"], + "BEAUMONT": ["77701"], + "DENTON": ["76201"], + "CARROLLTON": ["75006"], + "WACO": ["76701"], + "ABILENE": ["79601"], + "ROUND ROCK": ["78664"], + "ODESSA": ["79761"], + "RICHARDSON": ["75080"], + "PEARLAND": ["77581"], + "LEAGUE CITY": ["77573"], + "SUGAR LAND": ["77478"], + "TYLER": ["75701"], + "COLLEGE STATION": ["77840"], + "ALLEN": ["75002"], + "SAN ANGELO": ["76901"], + "WICHITA FALLS": ["76301"], + "EDINBURG": ["78539"], + "LEWISVILLE": ["75067"], + "BRYAN": ["77801"], + "MISSION": ["78572"], + "LONGVIEW": ["75601"], + "PHARR": ["78577"], + "BAYTOWN": ["77520"], + "FLOWER MOUND": ["75028"], + "TEMPLE": ["76501"], + "NORTH RICHLAND HILLS": ["76180"], + "MISSOURI CITY": ["77459"], + "CONROE": ["77301"], + "NEW BRAUNFELS": ["78130"], + "MANSFIELD": ["76063"], + "CEDAR PARK": ["78613"], + "ROWLETT": ["75088"], + "PFLUGERVILLE": ["78660"], + "PORT ARTHUR": ["77640"], + "VICTORIA": ["77901"], + "GEORGETOWN": ["78626"], + "SAN MARCOS": ["78666"], + "HARLINGEN": ["78550"], + "CEDAR HILL": ["75104"], + "WYLIE": ["75098"], + "BURLESON": ["76028"], + "THE WOODLANDS": ["77380"], + "LUFKIN": ["75901"], + "DEL RIO": ["78840"], + "TEXARKANA": ["75501"], + "SHERMAN": ["75090"], + "EAGLE PASS": ["78852"], + "HALTOM CITY": ["76117"], + "DUNCANVILLE": ["75116"], + "WEATHERFORD": ["76086"] + }, + "UT": { + "_default": "SALT LAKE CITY", + "SALT LAKE CITY": ["84101"], + "WEST VALLEY CITY": ["84119"], + "PROVO": ["84601"], + "WEST JORDAN": ["84084"], + "OREM": ["84057"], + "SANDY": ["84070"], + "OGDEN": ["84401"], + "ST. GEORGE": ["84770"], + "LAYTON": ["84041"], + "TAYLORSVILLE": ["84129"], + "SOUTH JORDAN": ["84095"], + "LEHI": ["84043"], + "LOGAN": ["84321"], + "MURRAY": ["84107"], + "DRAPER": ["84020"], + "BOUNTIFUL": ["84010"], + "RIVERTON": ["84065"], + "SPANISH FORK": ["84660"], + "ROY": ["84067"], + "PLEASANT GROVE": ["84062"], + "TOOELE": ["84074"], + "CLEARFIELD": ["84015"] + }, + "VT": { + "_default": "BURLINGTON", + "BURLINGTON": ["05401"], + "SOUTH BURLINGTON": ["05403"], + "RUTLAND": ["05701"], + "MONTPELIER": ["05602"], + "BARRE": ["05641"], + "ST. ALBANS": ["05478"], + "BENNINGTON": ["05201"], + "BRATTLEBORO": ["05301"] + }, + "VA": { + "_default": "VIRGINIA BEACH", + "VIRGINIA BEACH": ["23451", "23452", "23454", "23456", "23462", "23464"], + "NORFOLK": ["23510"], + "CHESAPEAKE": ["23320"], + "RICHMOND": ["23219"], + "NEWPORT NEWS": ["23607"], + "ALEXANDRIA": ["22314"], + "HAMPTON": ["23669"], + "ROANOKE": ["24011"], + "PORTSMOUTH": ["23704"], + "SUFFOLK": ["23434"], + "LYNCHBURG": ["24501"], + "HARRISONBURG": ["22801"], + "CHARLOTTESVILLE": ["22902"], + "DANVILLE": ["24541"], + "MANASSAS": ["20110"], + "FREDERICKSBURG": ["22401"], + "BLACKSBURG": ["24060"], + "LEESBURG": ["20176"], + "STAUNTON": ["24401"], + "WINCHESTER": ["22601"], + "PETERSBURG": ["23803"], + "RADFORD": ["24141"], + "WILLIAMSBURG": ["23185"] + }, + "WA": { + "_default": "SEATTLE", + "SEATTLE": ["98101", "98102", "98104", "98109", "98112", "98122", "98144"], + "SPOKANE": ["99201"], + "TACOMA": ["98402"], + "VANCOUVER": ["98660"], + "BELLEVUE": ["98004"], + "KENT": ["98032"], + "EVERETT": ["98201"], + "RENTON": ["98057"], + "SPOKANE VALLEY": ["99206"], + "FEDERAL WAY": ["98003"], + "OLYMPIA": ["98501"], + "YAKIMA": ["98901"], + "BELLINGHAM": ["98225"], + "KIRKLAND": ["98033"], + "KENNEWICK": ["99336"], + "AUBURN": ["98002"], + "REDMOND": ["98052"], + "LAKEWOOD": ["98499"], + "MARYSVILLE": ["98270"], + "PASCO": ["99301"], + "RICHLAND": ["99352"], + "SAMMAMISH": ["98074"], + "BURIEN": ["98166"], + "SHORELINE": ["98133"], + "WALLA WALLA": ["99362"], + "LONGVIEW": ["98632"], + "PULLMAN": ["99163"] + }, + "WV": { + "_default": "CHARLESTON", + "CHARLESTON": ["25301"], + "HUNTINGTON": ["25701"], + "MORGANTOWN": ["26505"], + "PARKERSBURG": ["26101"], + "WHEELING": ["26003"], + "MARTINSBURG": ["25401"], + "BECKLEY": ["25801"], + "CLARKSBURG": ["26301"], + "FAIRMONT": ["26554"], + "WEIRTON": ["26062"], + "BLUEFIELD": ["24701"] + }, + "WI": { + "_default": "MILWAUKEE", + "MILWAUKEE": ["53202", "53203", "53204", "53205", "53207", "53208", "53211", "53212"], + "MADISON": ["53703"], + "GREEN BAY": ["54301"], + "KENOSHA": ["53140"], + "RACINE": ["53403"], + "APPLETON": ["54911"], + "WAUKESHA": ["53186"], + "OSHKOSH": ["54901"], + "EAU CLAIRE": ["54701"], + "JANESVILLE": ["53545"], + "WEST ALLIS": ["53214"], + "LA CROSSE": ["54601"], + "SHEBOYGAN": ["53081"], + "WAUWATOSA": ["53213"], + "FOND DU LAC": ["54935"], + "BROOKFIELD": ["53045"], + "NEW BERLIN": ["53151"], + "BELOIT": ["53511"], + "MANITOWOC": ["54220"], + "WEST BEND": ["53095"], + "GREENFIELD": ["53220"], + "STEVENS POINT": ["54481"], + "FITCHBURG": ["53711"], + "SUN PRAIRIE": ["53590"], + "SUPERIOR": ["54880"], + "MARSHFIELD": ["54449"], + "WAUSAU": ["54401"] + }, + "WY": { + "_default": "CHEYENNE", + "CHEYENNE": ["82001"], + "CASPER": ["82601"], + "LARAMIE": ["82070"], + "GILLETTE": ["82716"], + "ROCK SPRINGS": ["82901"], + "SHERIDAN": ["82801"], + "GREEN RIVER": ["82935"], + "EVANSTON": ["82930"], + "RIVERTON": ["82501"], + "JACKSON": ["83001"], + "CODY": ["82414"] + } +} diff --git a/injected/src/features/broker-protection/comparisons/is-same-name.js b/injected/src/features/broker-protection/comparisons/is-same-name.js index a4968ba35c3..3a9436a65be 100644 --- a/injected/src/features/broker-protection/comparisons/is-same-name.js +++ b/injected/src/features/broker-protection/comparisons/is-same-name.js @@ -1,4 +1,5 @@ import { names } from './constants.js'; +import { hasOwn } from '../utils/utils.js'; /** * @param {string} fullNameExtracted @@ -182,7 +183,7 @@ export function getNicknames(name, nicknames) { name = name.toLowerCase(); - if (Object.prototype.hasOwnProperty.call(nicknames, name)) { + if (hasOwn(nicknames, name)) { return new Set(nicknames[name]); } diff --git a/injected/src/features/broker-protection/execute.js b/injected/src/features/broker-protection/execute.js index 784cac1adef..58260c0205f 100644 --- a/injected/src/features/broker-protection/execute.js +++ b/injected/src/features/broker-protection/execute.js @@ -1,6 +1,7 @@ // eslint-disable-next-line no-redeclare import { navigate, extract, click, scroll, expectation, fillForm, getCaptchaInfo, solveCaptcha, condition } from './actions/actions'; import { ErrorResponse } from './types'; +import { hasOwn } from './utils/utils.js'; /** * @param {import('./types.js').PirAction} action @@ -53,7 +54,7 @@ export async function execute(action, inputData, root = document) { function data(action, data, defaultSource) { if (!data) return null; const source = action.dataSource || defaultSource; - if (Object.prototype.hasOwnProperty.call(data, source)) { + if (hasOwn(data, source)) { return data[source]; } return null; diff --git a/injected/src/features/broker-protection/types.js b/injected/src/features/broker-protection/types.js index 58c4cbe7627..ce91c0d1dee 100644 --- a/injected/src/features/broker-protection/types.js +++ b/injected/src/features/broker-protection/types.js @@ -2,17 +2,54 @@ * @typedef {SuccessResponse | ErrorResponse} ActionResponse * @typedef {{ result: true } | { result: false; error: string }} BooleanResult * @typedef {{type: "element" | "text" | "url"; selector: string; parent?: string; expect?: string; failSilently?: boolean}} Expectation + * @typedef {{ addressLine1?: string; city: string; state: string | null; zip?: string }} BrokerProtectionAddress + * @typedef {string | number | null | undefined | string[] | BrokerProtectionAddress[]} BrokerProtectionProfileValue + * @typedef {Record & { + * firstName?: string; + * middleName?: string | null; + * lastName?: string; + * suffix?: string | null; + * city?: string; + * state?: string; + * age?: string | number; + * birthYear?: string | number; + * phone?: string; + * name?: string; + * alternativeNames?: string[]; + * addresses?: BrokerProtectionAddress[]; + * phoneNumbers?: string[]; + * relatives?: string[]; + * profileUrl?: string; + * }} BrokerProtectionProfile + * @typedef {{ selector: string; type: string; min?: string; max?: string; useState?: boolean }} FillFormElement */ /** - * @typedef {object} PirAction + * @typedef {object} BaseAction * @property {string} id - * @property {"extract" | "fillForm" | "click" | "expectation" | "getCaptchaInfo" | "solveCaptcha" | "navigate" | "condition" | "scroll"} actionType - * @property {string} [selector] - * @property {string} [captchaType] - * @property {string} [injectCaptchaHandler] * @property {string} [dataSource] - * @property {string} [url] + * + * @typedef {BaseAction & { actionType: "fillForm"; selector: string; elements: FillFormElement[] }} FillFormAction + * @typedef {BaseAction & { actionType: "navigate"; url: string }} NavigateAction + * @typedef {BaseAction & { actionType: "extract"; selector: string; profile?: Record }} ExtractAction + * @typedef {BaseAction & { actionType: "click"; selector?: string; elements?: Record[]; choices?: Record[]; default?: Record }} ClickAction + * @typedef {BaseAction & { actionType: "expectation"; expectations: Expectation[]; actions?: PirAction[] }} ExpectationAction + * @typedef {BaseAction & { actionType: "getCaptchaInfo"; selector?: string; captchaType?: string }} GetCaptchaInfoAction + * @typedef {BaseAction & { actionType: "solveCaptcha"; selector?: string; injectCaptchaHandler?: string }} SolveCaptchaAction + * @typedef {BaseAction & { actionType: "condition"; expectations: Expectation[]; actions?: PirAction[] }} ConditionAction + * @typedef {BaseAction & { actionType: "scroll"; selector: string }} ScrollAction + * + * @typedef { + * | FillFormAction + * | NavigateAction + * | ExtractAction + * | ClickAction + * | ExpectationAction + * | GetCaptchaInfoAction + * | SolveCaptchaAction + * | ConditionAction + * | ScrollAction + * } PirAction */ /** diff --git a/injected/src/features/broker-protection/utils/utils.js b/injected/src/features/broker-protection/utils/utils.js index 8429232ead2..f06d6d48539 100644 --- a/injected/src/features/broker-protection/utils/utils.js +++ b/injected/src/features/broker-protection/utils/utils.js @@ -1,3 +1,5 @@ +import { hasOwnProperty as capturedHasOwnProperty } from '../../../captured-globals.js'; + /** * Get a single element. * @@ -232,6 +234,26 @@ export function matchingPair(a, b) { return a.toLowerCase().trim() === b.toLowerCase().trim(); } +/** + * @template {object} T + * @param {T} obj + * @param {any} key + * @return {key is keyof T} + */ +export function hasOwn(obj, key) { + return capturedHasOwnProperty.call(obj, key); +} + +/** + * @template {object} T + * @param {T} obj + * @param {PropertyKey | null | undefined} key + * @return {T[keyof T] | undefined} + */ +export function getOwn(obj, key) { + return key != null && hasOwn(obj, key) ? obj[key] : undefined; +} + /** * Sorts an array of addresses by state, then by city within the state. * diff --git a/injected/unit-test/broker-protection.js b/injected/unit-test/broker-protection.js index e3a38462a09..407295196d3 100644 --- a/injected/unit-test/broker-protection.js +++ b/injected/unit-test/broker-protection.js @@ -5,14 +5,23 @@ import { stringToList, extractValue } from '../src/features/broker-protection/ac import { addressMatch } from '../src/features/broker-protection/comparisons/address.js'; import { replaceTemplatedUrl } from '../src/features/broker-protection/actions/build-url.js'; import { processTemplateStringWithUserData } from '../src/features/broker-protection/actions/build-url-transforms.js'; -import { names } from '../src/features/broker-protection/comparisons/constants.js'; -import { generateRandomInt, hashObject, sortAddressesByStateAndCity } from '../src/features/broker-protection/utils/utils.js'; +import { names, states } from '../src/features/broker-protection/comparisons/constants.js'; +import { + generateRandomInt, + getOwn, + hasOwn, + hashObject, + sortAddressesByStateAndCity, +} from '../src/features/broker-protection/utils/utils.js'; import { generatePhoneNumber, generateZipCode, generateStreetAddress } from '../src/features/broker-protection/actions/generators.js'; +import STATE_CITY_ZIPS from '../src/features/broker-protection/actions/state-city-zips.json' with { type: 'json' }; import { CityStateExtractor } from '../src/features/broker-protection/extractors/address.js'; import { ProfileHashTransformer } from '../src/features/broker-protection/extractors/profile-url.js'; import { getComparisonFunction } from '../src/features/broker-protection/actions/click.js'; import { isElementType } from '../src/features/broker-protection/captcha-services/utils/element.js'; +const ANY_FIVE_DIGIT_ZIP = /^\d{5}$/; + describe('Actions', () => { describe('extract', () => { describe('isSameAge', () => { @@ -732,12 +741,67 @@ describe('generators', () => { }); }); describe('generateZipCode', () => { - it('generates a string of integers of an appropriate size', () => { - const zipCode = generateZipCode(); + it('always returns a 5-digit string', () => { + expect(generateZipCode()).toMatch(ANY_FIVE_DIGIT_ZIP); + expect(generateZipCode(null)).toMatch(ANY_FIVE_DIGIT_ZIP); + expect(generateZipCode({ state: 'ZZ', city: 'Nowhere' })).toMatch(ANY_FIVE_DIGIT_ZIP); + }); + + it('uses the city ZIPs when the city and state are provided', () => { + expect(STATE_CITY_ZIPS.IL.CHICAGO).toContain(generateZipCode({ state: 'IL', city: 'Chicago' })); + }); + + it('handles lowercase state and city', () => { + [ + { state: 'IL', city: 'CHICAGO' }, + { state: 'il', city: 'chicago' }, + { state: 'Il', city: 'cHiCaGo' }, + ].forEach((params) => { + expect(STATE_CITY_ZIPS.IL.CHICAGO).toContain(generateZipCode(params)); + }); + }); + + it('uses the default city ZIPs when only the state is provided', () => { + const defaultZips = STATE_CITY_ZIPS.IL[STATE_CITY_ZIPS.IL._default]; + expect(defaultZips).toContain(generateZipCode({ state: 'IL' })); + }); + + it('uses the default city ZIPs when the city is unknown', () => { + const defaultZips = STATE_CITY_ZIPS.IL[STATE_CITY_ZIPS.IL._default]; + expect(defaultZips).toContain(generateZipCode({ state: 'IL', city: 'Nonexistentville' })); + }); + + it('returns a random ZIP when the state is not a string', () => { + expect(generateZipCode({ state: 5, city: 'Chicago' })).toMatch(ANY_FIVE_DIGIT_ZIP); + }); - expect(typeof zipCode).toEqual('string'); - expect(zipCode.length).toBe(5); - expect(zipCode).toMatch(/^\d{5}$/); + it('uses the default city ZIPs when the city is not a string', () => { + const defaultZips = STATE_CITY_ZIPS.IL[STATE_CITY_ZIPS.IL._default]; + expect(defaultZips).toContain(generateZipCode({ state: 'IL', city: [] })); + }); + + it('includes ZIP data for every US state and DC', () => { + expect(Object.keys(STATE_CITY_ZIPS).sort()).toEqual(Object.keys(states).sort()); + }); + + it('uses a city key as each state default', () => { + Object.keys(STATE_CITY_ZIPS).forEach((state) => { + const stateCities = STATE_CITY_ZIPS[state]; + const defaultCity = stateCities._default; + + expect(Object.keys(stateCities)).toContain(defaultCity); + expect(stateCities[defaultCity]).toEqual(jasmine.arrayContaining([jasmine.stringMatching(ANY_FIVE_DIGIT_ZIP)])); + }); + }); + + it('stores every city ZIP as a 5-digit string', () => { + Object.entries(STATE_CITY_ZIPS).forEach(([, cities]) => { + const cityZipEntries = /** @type {[string, string[]][]} */ (Object.entries(cities).filter(([city]) => city !== '_default')); + cityZipEntries.forEach(([, zips]) => { + expect(zips).toEqual(jasmine.arrayContaining([jasmine.stringMatching(ANY_FIVE_DIGIT_ZIP)])); + expect(zips).toEqual(zips.map(() => jasmine.stringMatching(ANY_FIVE_DIGIT_ZIP))); + }); + }); }); }); describe('generateStreetAddress', () => { @@ -778,6 +842,23 @@ describe('captcha-services', () => { }); describe('utils', () => { + describe('hasOwn', () => { + it('uses the captured hasOwnProperty when Object.prototype is changed', () => { + const originalHasOwnProperty = Object.prototype.hasOwnProperty; + // eslint-disable-next-line no-extend-native + Object.defineProperty(Object.prototype, 'hasOwnProperty', { value: () => true, configurable: true }); + try { + const target = Object.create({ inherited: 'value' }); + + expect(hasOwn(target, 'inherited')).toBe(false); + expect(getOwn(target, 'inherited')).toBeUndefined(); + } finally { + // eslint-disable-next-line no-extend-native + Object.defineProperty(Object.prototype, 'hasOwnProperty', { value: originalHasOwnProperty, configurable: true }); + } + }); + }); + describe('generateRandomInt', () => { it('generates an integers between the min and max values', () => { fc.assert( diff --git a/injected/unit-test/verify-artifacts.js b/injected/unit-test/verify-artifacts.js index 6a533909899..b9fcc01e0df 100644 --- a/injected/unit-test/verify-artifacts.js +++ b/injected/unit-test/verify-artifacts.js @@ -8,7 +8,7 @@ console.log(ROOT); const BUILD = join(ROOT, 'build'); const APPLE_BUILD = join(ROOT, 'Sources/ContentScopeScripts/dist'); -let CSS_OUTPUT_SIZE = 860_000; +let CSS_OUTPUT_SIZE = 890_000; if (process.platform === 'win32') { CSS_OUTPUT_SIZE = CSS_OUTPUT_SIZE * 1.1; // 10% larger for Windows due to line endings }