jsfe 1.16.0 → 1.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/forms/identity-flows.d.ts +2 -0
- package/dist/forms/identity-flows.d.ts.map +1 -1
- package/dist/forms/identity-flows.js +37 -8
- package/dist/helpers/index.d.ts +2 -1
- package/dist/helpers/index.d.ts.map +1 -1
- package/dist/helpers/index.js +2 -2
- package/dist/helpers/otp-check.d.ts +9 -3
- package/dist/helpers/otp-check.d.ts.map +1 -1
- package/dist/helpers/otp-check.js +20 -10
- package/dist/runtime-identity.js +1 -1
- package/package.json +1 -1
|
@@ -7,6 +7,8 @@ export function contactPrompt(cargo: any, options: any, callerAllowed: any, part
|
|
|
7
7
|
export function contactError(kind: any, options: any, es?: boolean): string;
|
|
8
8
|
export function otpWhere(cargo: any, es?: boolean): string;
|
|
9
9
|
export function otpPrompt(cargo: any, es?: boolean): string;
|
|
10
|
+
/** What the caller hears when a second code goes to the account's email: the address masked to its first letter and domain. */
|
|
11
|
+
export function otpAlsoSent(cargo: any, es?: boolean): string;
|
|
10
12
|
export function emailReadback(address: any, voice: any, es?: boolean): string;
|
|
11
13
|
/** Reenter the existing owning FORM or its gate without making the owner its own parent. */
|
|
12
14
|
export function identityResumeContext(forms: any, owner: any, target: any, identifiers?: {}): {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"identity-flows.d.ts","sourceRoot":"","sources":["../../src/forms/identity-flows.js"],"names":[],"mappings":"AA2BA,+GAA+G;AAC/G,oFAOC;AAQD,wFAAwF;AACxF,iDAGC;AACD,6CAMC;AACD,qHAQC;AAMD,4EAKC;AAwFD,
|
|
1
|
+
{"version":3,"file":"identity-flows.d.ts","sourceRoot":"","sources":["../../src/forms/identity-flows.js"],"names":[],"mappings":"AA2BA,+GAA+G;AAC/G,oFAOC;AAQD,wFAAwF;AACxF,iDAGC;AACD,6CAMC;AACD,qHAQC;AAMD,4EAKC;AAwFD,2DAOC;AACD,4DAOC;AACD,+HAA+H;AAC/H,8DAGC;AA0ID,8EAGC;AA4BD,4FAA4F;AAC5F;;;;EAOC;AAQD,0BAA4I"}
|
|
@@ -151,7 +151,7 @@ const authKeys = ['retry_flow', 'cancel_flow', 'email_validator', 'cell_validato
|
|
|
151
151
|
const auth = worker('authenticate-user', 'AuthenticateUser', 'Authenticate a contact using shared FORM collection and one-time-code services.', { parameters: Object.keys(AUTH_DEFAULTS).filter(k => !['contact_valid', 'auth_result', 'auth_contact_error', 'auth_contact_error_es', 'auth_previous_options', 'auth_restart', 'auth_join', 'otp_expiry_ms'].includes(k)).map(k => param(k, k.replace(/_/g, ' '), ['retry_returns', 'retrying', 'declined_caller'].includes(k) ? 'boolean' : 'string')).concat([param('caller_on_file', 'Caller-ID eligibility inherited from the invoking service')]), variables: vars(AUTH_DEFAULTS), contract: { role: 'gate', summary: 'Generic one-time-code identity gate', result: { paths: ['cargo.otpVerified', 'cargo.authRetry'], type: 'object' } }, steps: [
|
|
152
152
|
set('auth_restart', 'auth_restart', "retrying === true || retrying === 'true'"),
|
|
153
153
|
set('auth_previous_options', 'auth_previous_options', 'cargo.authOptions ? Object.assign({},cargo.authOptions) : null'),
|
|
154
|
-
when('auth_declined_caller', "declined_caller === true || declined_caller === 'true'", effect('auth_decline_state', "(cargo.callerDeclined=true,cargo.callerPreselected=false,cargo.otpHash=null,cargo.contactIntroSkip=true,cargo.callerNamed ? (cargo.displayName='',cargo.displayNameSetBy='flow',cargo.callerNamed=false) : true,true)")),
|
|
154
|
+
when('auth_declined_caller', "declined_caller === true || declined_caller === 'true'", effect('auth_decline_state', "(cargo.callerDeclined=true,cargo.callerPreselected=false,cargo.otpHash=null,cargo.otpAltHash=null,cargo.otpAltTimestamp=null,cargo.contactIntroSkip=true,cargo.callerNamed ? (cargo.displayName='',cargo.displayNameSetBy='flow',cargo.callerNamed=false) : true,true)")),
|
|
155
155
|
when('auth_already_verified', 'cargo.otpVerified === true', end('auth_verified_end')),
|
|
156
156
|
...authKeys.map(key => set('auth_option_' + key, key, `auth_restart && cargo.authOptions ? cargo.authOptions.${key} || ${key === 'retry_returns' ? 'false' : "''"} : ${key}`)),
|
|
157
157
|
set('auth_default_retry', 'retry_flow', "retry_flow || 'authenticate-user'"), set('auth_default_cancel', 'cancel_flow', "cancel_flow || 'contact-support'"),
|
|
@@ -168,7 +168,7 @@ const auth = worker('authenticate-user', 'AuthenticateUser', 'Authenticate a con
|
|
|
168
168
|
when('auth_join_challenge', 'auth_join', call('auth_join_collect', 'get-and-validate-otp-code')),
|
|
169
169
|
when('auth_join_release', 'auth_join', call('auth_join_gate_end', 'task-gate', { form: '{{parent_form}}', gate: 'authenticate-user', active: false })),
|
|
170
170
|
when('auth_join_return', 'auth_join', end('auth_join_complete')),
|
|
171
|
-
effect('auth_challenge_init', "(cargo.otpLateCode='',cargo.otpResends=0,cargo.otpMismatches=0,cargo.otpFormatMisses=0,cargo.otpWordMisses=0,cargo.otp_cell_number=cell_number || '',cargo.otp_email=email || '',true)"),
|
|
171
|
+
effect('auth_challenge_init', "(cargo.otpLateCode='',cargo.otpResends=0,cargo.otpMismatches=0,cargo.otpFormatMisses=0,cargo.otpWordMisses=0,cargo.otp_cell_number=cell_number || '',cargo.otp_email=email || '',cargo.otpAltSent=false,cargo.otpAltHash=null,cargo.otpAltTimestamp=null,true)"),
|
|
172
172
|
call('auth_send', 'send-otp-code'), when('auth_send_failed', 'cargo.otpSendFailure', call('auth_send_failure', 'otp-send-failed', undefined, 'replace')),
|
|
173
173
|
call('auth_code', 'get-and-validate-otp-code'),
|
|
174
174
|
cases('auth_complete', { 'condition: cargo.otpVerified === true': set('auth_ok', 'auth_result', 'true'), 'condition: cargo.authRetry': end('auth_return_retry'), default: otpMenu('auth_retry_code', "I couldn't verify your account. You can try again or use another contact.", 'No pude verificar su cuenta. Puede intentarlo de nuevo o usar otro contacto.', true) }),
|
|
@@ -179,6 +179,9 @@ export function otpWhere(cargo, es = false) {
|
|
|
179
179
|
const number = String(cargo.otp_cell_number || '').replace(/\D/g, '').slice(-4), tail = cargo.voice ? number.split('').join(', ') : number;
|
|
180
180
|
if (cargo.otpSentOn === 'whatsapp')
|
|
181
181
|
return es ? 'Se lo acabo de enviar aquí en este chat.' : 'I just sent it to you here in this chat.';
|
|
182
|
+
const mail = String(cargo.otpAltEmail || ''), at = mail.indexOf('@'), masked = cargo.voice ? (es ? 'que empieza con ' : 'starting with ') + mail.charAt(0).toUpperCase() + (es ? ', arroba ' : ', at ') + mail.slice(at + 1) : mail.charAt(0) + '…@' + mail.slice(at + 1);
|
|
183
|
+
if (cargo.otp_cell_number && cargo.otpAltSent === true)
|
|
184
|
+
return es ? 'Se lo envié por mensaje de texto al celular que termina con ' + tail + ', y también le envié un código a su correo ' + masked + '. Revise sus mensajes de texto y su correo.' : 'I sent it by text message to the cell ending with ' + tail + ', and I also sent a code to your email ' + masked + '. Check your text messages and your email.';
|
|
182
185
|
if (cargo.otp_cell_number)
|
|
183
186
|
return es ? 'Se lo envié por mensaje de texto al celular que termina con ' + tail + '. Revise sus mensajes de texto.' : 'I sent it by text message to the cell ending with ' + tail + '. Check your text messages.';
|
|
184
187
|
return es ? 'Se lo envié por correo electrónico a ' + String(cargo.otp_email || '') + '. Revise su correo.' : 'I sent it by email to ' + String(cargo.otp_email || '') + '. Check your email.';
|
|
@@ -186,10 +189,26 @@ export function otpWhere(cargo, es = false) {
|
|
|
186
189
|
export function otpPrompt(cargo, es = false) {
|
|
187
190
|
const verb = es ? cargo.verb_es : cargo.verb, number = String(cargo.otp_cell_number || '').replace(/\D/g, '').slice(-4), tail = cargo.voice ? number.split('').join(', ') : number;
|
|
188
191
|
const where = cargo.otpSentOn === 'whatsapp' ? (es ? 'que le acabo de enviar aquí en este chat' : 'I just sent you here in this chat') : (es ? 'que recibió en ' : 'you received at ') + (cargo.otp_cell_number ? (es ? 'celular que termina con ' : 'cell ending with ') + tail : (es ? 'correo electrónico: ' : 'email: ') + String(cargo.otp_email || ''));
|
|
192
|
+
const mail = String(cargo.otpAltEmail || ''), at = mail.indexOf('@'), also = cargo.otp_cell_number && cargo.otpAltSent === true ? (es ? ' o en su correo ' : ' or at your email ') + (cargo.voice ? (es ? 'que empieza con ' : 'starting with ') + mail.charAt(0).toUpperCase() + (es ? ', arroba ' : ', at ') + mail.slice(at + 1) : mail.charAt(0) + '…@' + mail.slice(at + 1)) : '';
|
|
189
193
|
const name = cargo.callerPreselected && cargo.callerNamed && cargo.displayName ? cargo.displayName : '';
|
|
190
194
|
const lead = String((es ? cargo.otpIntro_es : cargo.otpIntro) || '');
|
|
191
|
-
return (lead ? lead + ' ' : '') + (name ? name + (es ? ', por favor' : ', please') : es ? 'Por favor' : 'Please') + ' ' + verb + ' ' + (cargo.voice ? es ? 'o ingrese ' : 'or enter ' : '') + (es ? 'el código de verificación de 6 dígitos ' : 'the 6-digit verification code ') + where + '. ' + (es ? `También puede ${cargo.voice ? 'presionar * o decir' : 'escribir'} SALIR para cancelar.` : `You can also ${cargo.voice ? 'press * or ' : ''}${verb} EXIT to cancel.`) + (cargo.callerPreselected ? (es ? ` Para usar otra cuenta, ${verb} OTRA CUENTA.` : ` To use another account, ${verb} ANOTHER ACCOUNT.`) : '');
|
|
195
|
+
return (lead ? lead + ' ' : '') + (name ? name + (es ? ', por favor' : ', please') : es ? 'Por favor' : 'Please') + ' ' + verb + ' ' + (cargo.voice ? es ? 'o ingrese ' : 'or enter ' : '') + (es ? 'el código de verificación de 6 dígitos ' : 'the 6-digit verification code ') + where + also + '. ' + (es ? `También puede ${cargo.voice ? 'presionar * o decir' : 'escribir'} SALIR para cancelar.` : `You can also ${cargo.voice ? 'press * or ' : ''}${verb} EXIT to cancel.`) + (cargo.callerPreselected ? (es ? ` Para usar otra cuenta, ${verb} OTRA CUENTA.` : ` To use another account, ${verb} ANOTHER ACCOUNT.`) : '');
|
|
192
196
|
}
|
|
197
|
+
/** What the caller hears when a second code goes to the account's email: the address masked to its first letter and domain. */
|
|
198
|
+
export function otpAlsoSent(cargo, es = false) {
|
|
199
|
+
const mail = String(cargo.otpAltEmail || ''), at = mail.indexOf('@'), masked = cargo.voice ? (es ? 'que empieza con ' : 'starting with ') + mail.charAt(0).toUpperCase() + (es ? ', arroba ' : ', at ') + mail.slice(at + 1) : mail.charAt(0) + '…@' + mail.slice(at + 1);
|
|
200
|
+
return es ? 'Muy bien, también le envié un código a su correo ' + masked + '.' : 'Okay, I also sent a code to your email ' + masked + '.';
|
|
201
|
+
}
|
|
202
|
+
// The caller cannot find the texted code (or asks for another way, or for email): a second code goes to the account's email
|
|
203
|
+
// (cargo.otpAltEmail, set by the tenant's contact step when the code goes to a cell), once per challenge, without a question.
|
|
204
|
+
// The texted code stays valid beside it (send-email-otp additional: otpAltHash). A refused address says where the code went.
|
|
205
|
+
const emailToo = worker('send-otp-email-too', 'SendOtpEmailToo', "Email a second one-time code to the account's email beside the texted one, and say so.", { variables: vars({ alt_hash: '', also_sent: '', also_sent_es: '', otp_where: '', otp_where_es: '' }), contract: { role: 'worker', summary: "Second one-time code to the account's email", tools: ['send-email-otp'] }, steps: [
|
|
206
|
+
{ id: 'otp_email_too_send', type: 'CALL-TOOL', tool: 'send-email-otp', variable: 'alt_hash', args: { to: '{{cargo.otpAltEmail}}', container: '{{cargo}}', language: '{{language}}', additional: true }, onFail: set('otp_email_too_failed', 'alt_hash', "''") },
|
|
207
|
+
effect('otp_email_too_state', "(cargo.otpAltSent=/^[0-9a-f]{64}$/.test(String(alt_hash || ''))?true:'failed',true)"),
|
|
208
|
+
set('otp_email_too_said', 'also_sent', expression(otpAlsoSent, 'cargo')), set('otp_email_too_said_es', 'also_sent_es', expression(otpAlsoSent, 'cargo', 'true')),
|
|
209
|
+
set('otp_email_too_where', 'otp_where', expression(otpWhere, 'cargo')), set('otp_email_too_where_es', 'otp_where_es', expression(otpWhere, 'cargo', 'true')),
|
|
210
|
+
cases('otp_email_too_say', { 'condition: cargo.otpAltSent === true': say('otp_email_too_sent', '{{also_sent}}', '{{also_sent_es}}'), default: say('otp_email_too_refused', '{{otp_where}}', '{{otp_where_es}}') })
|
|
211
|
+
] });
|
|
193
212
|
const OTP_PROFILES = [{ name: 'code', label: 'verification code', label_es: 'código de verificación', profile: { kind: 'digits', min: 6, max: 6 } }, raw];
|
|
194
213
|
const code = worker('get-and-validate-otp-code', 'GetAndValidateOtpCode', 'Collect a one-time-code answer through the owning FORM ancestry, preserving protocol commands for validation.', { variables: vars({ otp_code: '', user_choice: '', normalized_otp_code: '', otp_question: '', otp_question_es: '', otp_profiles: OTP_PROFILES }), contract: { role: 'collector', summary: 'One-time-code answer collector using shared FORM matching' }, steps: [
|
|
195
214
|
when('otp_already_verified', 'cargo.otpVerified === true', end('otp_existing_verification')),
|
|
@@ -208,6 +227,9 @@ const OTP_WORDS = {
|
|
|
208
227
|
other_alone: ['otra', 'otro', 'another', 'other'],
|
|
209
228
|
email: ['email', 'e-mail', 'e mail', 'emails', 'correo', 'correos', 'correo electrónico', 'correo electronico', 'imeil'],
|
|
210
229
|
resend: ['resend', 'send again', 'send it again', 'not received', 'never received', 'did not receive', 'didn t receive', 'didn t get', 'haven t received', 'nothing came', 'no code', 'no text', 'no llegó', 'no llego', 'no llega', 'no ha llegado', 'no me llegó', 'no me llego', 'no le llegó', 'no le llego', 'no me ha llegado', 'no recibí', 'no recibi', 'no lo recibí', 'no lo recibi', 'no tengo el código', 'no tengo el codigo', 'no lo tengo', 'no alcancé', 'no alcance', 'mándame el código', 'mandame el codigo', 'envíame el código', 'enviame el codigo', 'reenviar', 'reenvíe', 'reenvie'],
|
|
230
|
+
// the caller cannot find the texted code, or asks for another way to get it or to verify
|
|
231
|
+
cannot_find: ['don t have it', 'i don t have it', 'don t have the code', 'i don t have the code', 'can t find', 'cannot find', 'can not find', 'couldn t find', 'could not find', 'can t find it', 'cannot find it', 'i can t find', 'i cannot find', 'not there', 'isn t there', 'it s not there', 'there s no code', 'no encuentro', 'no lo encuentro', 'no la encuentro', 'no encuentro el código', 'no encuentro el codigo', 'no aparece', 'no hay código', 'no hay codigo'],
|
|
232
|
+
another_way: ['another way', 'different way', 'other way', 'some other way', 'a different way', 'another option', 'other option', 'otra forma', 'otra manera', 'de otra forma', 'de otra manera', 'otra opción', 'otra opcion', 'hay otra forma', 'hay otra manera'],
|
|
211
233
|
later: ['driving', 'i am driving', 'i m busy', 'im busy', 'not now', 'later', 'call me later', 'call back', 'cant right now', 'can t right now', 'can t now', 'cannot right now', 'manejando', 'conduciendo', 'estoy manejando', 'voy manejando', 'ahora no', 'ahora no puedo', 'no puedo ahora', 'más tarde', 'mas tarde', 'estoy ocupado', 'estoy ocupada'],
|
|
212
234
|
noPhone: ['don t have that phone', 'don t have that number', 'don t have that phone anymore', 'don t have the phone', 'not my number anymore', 'not my phone anymore', 'that s an old number', 'old number', 'changed my number', 'changed my phone', 'lost my phone', 'no tengo ese teléfono', 'no tengo ese telefono', 'no tengo ese número', 'no tengo ese numero', 'ya no tengo ese', 'ya no tengo el teléfono', 'ya no tengo el telefono', 'ya no es mi número', 'ya no es mi numero', 'número viejo', 'numero viejo', 'cambié de número', 'cambie de numero', 'cambié mi número', 'perdí mi teléfono', 'perdi mi telefono', 'don t have a phone', 'don t have phone', 'don t have my phone', 'don t have a cell', 'don t have the cell', 'don t have my cell', 'don t have a cell phone', 'don t have access to that phone', 'don t have access to the phone', 'no tengo teléfono', 'no tengo telefono', 'no tengo celular', 'no tengo el celular', 'no tengo mi teléfono', 'no tengo mi telefono', 'no tengo mi celular', 'no tengo un teléfono', 'no tengo un telefono', 'no tengo acceso a ese teléfono', 'no tengo acceso a ese telefono']
|
|
213
235
|
};
|
|
@@ -221,24 +243,29 @@ function otpPhoneUnavailable(value) {
|
|
|
221
243
|
// What the reply ranker may take at the code question beside the code: the step's offers. Email and another account are
|
|
222
244
|
// offered only where the step offers them (a code sent by text; a caller-ID preselection).
|
|
223
245
|
const OTP_REPLY_CHOICES = [{ key: 'email', label: 'use the email on the account instead of the phone' }, { key: 'another_account', label: 'use another account' },
|
|
224
|
-
{ key: 'where', label: 'asks where the code
|
|
225
|
-
const validateCode = worker('validate-otp-code', 'ValidateOtpCode', 'Validate a challenge response with explicit bounded retries, alternate-contact handling and tenant callbacks.', { variables: vars({ otp_phrases: OTP_WORDS, no_code_given: false, wants_other_account: false, other_account_given: false, otp_validation_result: false, whatsapp_invite_sent: false, invite_text: '', otp_cancel: 'contact-support', answer_key: '', answer_value: '', otp_reply_choices: OTP_REPLY_CHOICES, otp_offers: [], otp_reply: {}, otp_where: '', otp_where_es: '' }), contract: { role: 'gate', summary: 'Validate the active one-time challenge', tools: ['validate-otp', 'send-whatsapp-invite'], result: { path: 'cargo.otpVerified', type: 'boolean' } }, steps: [
|
|
246
|
+
{ key: 'where', label: 'asks where the code was sent' }, { key: 'cannot_find', label: 'cannot find the code, or it is not there' }, { key: 'another_way', label: 'asks for another way to get the code or to verify' }, { key: 'resend', label: 'the code did not arrive; send it again' }, { key: 'no_phone', label: 'no longer has that phone or number' }, { key: '__agent', label: 'speak with a person (a live agent)' }];
|
|
247
|
+
const validateCode = worker('validate-otp-code', 'ValidateOtpCode', 'Validate a challenge response with explicit bounded retries, alternate-contact handling and tenant callbacks.', { variables: vars({ otp_phrases: OTP_WORDS, email_too: false, no_code_given: false, wants_other_account: false, other_account_given: false, otp_validation_result: false, whatsapp_invite_sent: false, invite_text: '', otp_cancel: 'contact-support', answer_key: '', answer_value: '', otp_reply_choices: OTP_REPLY_CHOICES, otp_offers: [], otp_reply: {}, otp_where: '', otp_where_es: '' }), contract: { role: 'gate', summary: 'Validate the active one-time challenge', tools: ['validate-otp', 'send-whatsapp-invite'], result: { path: 'cargo.otpVerified', type: 'boolean' } }, steps: [
|
|
226
248
|
set('otp_cancel', 'otp_cancel', "cargo.authCancelFlow || 'contact-support'"),
|
|
249
|
+
set('otp_email_too_ready', 'email_too', "!!(cargo.otp_cell_number && cargo.otpAltSent !== true && cargo.otpAltSent !== 'failed' && validateEmail(String(cargo.otpAltEmail || '')))"),
|
|
227
250
|
set('otp_reset_no_code', 'no_code_given', 'false'), set('otp_reset_invite', 'whatsapp_invite_sent', 'false'), set('otp_reset_callback', 'answer_key', "''"),
|
|
228
251
|
when('otp_tenant_code', 'cargo.authOptions && cargo.authOptions.code_validator && normalized_otp_code.length !== 6 && /[0-9]/.test(user_choice)', call('otp_tenant_code_call', '{{cargo.authOptions.code_validator}}', { answer: '{{user_choice}}' })),
|
|
229
252
|
set('otp_other_given', 'other_account_given', "answer_key === 'another_account' ? (cargo.authOtherAccount=String(answer_value || ''),true) : false"),
|
|
230
253
|
set('otp_other_requested', 'wants_other_account', 'other_account_given || !!(cargo.callerPreselected && normalized_otp_code.length !== 6 && (matchesChoice(user_choice,otp_phrases.other) || otp_phrases.other_alone.includes(user_choice.replace(/[.,!?¡¿]/g,\'\').trim())))'),
|
|
231
254
|
when('otp_tenant_answer', 'cargo.authOptions && cargo.authOptions.answer_handler && normalized_otp_code.length !== 6 && !wants_other_account', call('otp_tenant_answer_call', '{{cargo.authOptions.answer_handler}}', { answer: '{{user_choice}}' })),
|
|
255
|
+
set('otp_where_early', 'otp_where', expression(otpWhere, 'cargo')), set('otp_where_early_es', 'otp_where_es', expression(otpWhere, 'cargo', 'true')),
|
|
232
256
|
cases('otp_response', {
|
|
233
257
|
'condition: normalized_otp_code.length === 6': { id: 'otp_check_tool', type: 'CALL-TOOL', tool: 'validate-otp', variable: 'otp_validation_result', args: { otp: '{{normalized_otp_code}}', container: '{{cargo}}' }, onFail: call('otp_check_failed', 'get-and-validate-otp-code', undefined, 'replace') },
|
|
234
258
|
'condition: wants_other_account': call('otp_change_account', 'authenticate-user', { cell_number: '', email: '', retrying: true, declined_caller: true }, 'replace'),
|
|
235
259
|
// Email asked for at a code sent by text: authentication is an input step, so the contact question is asked again
|
|
236
260
|
// (without the caller-ID offer) and the caller gives the email; a code sent by email keeps "no me llegó el correo" a resend.
|
|
261
|
+
['condition: email_too && normalized_otp_code.length !== 6 && (' + 'matchesChoice(user_choice,otp_phrases.resend) || matchesChoice(user_choice,otp_phrases.cannot_find) || matchesChoice(user_choice,otp_phrases.another_way) || matchesChoice(user_choice,otp_phrases.email)' + ')']: call('otp_email_too', 'send-otp-email-too'),
|
|
237
262
|
'condition: !!cargo.otp_cell_number && normalized_otp_code.length !== 6 && matchesChoice(user_choice,otp_phrases.email)': call('otp_use_email', 'authenticate-user', { cell_number: '', email: '', retrying: true, declined_caller: true }, 'replace'),
|
|
238
263
|
"condition: isExitRequest(user_choice)": call('otp_exit', '{{otp_cancel}}', undefined, 'reboot'),
|
|
239
264
|
"condition: isAnswer(user_choice,'agent')": call('otp_agent', 'live-agent-requested', undefined, 'reboot'),
|
|
240
265
|
"condition: isAnswer(user_choice,'text')": call('otp_text', 'switch-to-text', undefined, 'reboot'),
|
|
241
266
|
'condition: matchesChoice(user_choice,otp_phrases.resend)': call('otp_resend', 'resend-otp-code', { resend_reason: 'requested' }),
|
|
267
|
+
'condition: matchesChoice(user_choice,otp_phrases.cannot_find)': say('otp_cannot_find', '{{otp_where}}', '{{otp_where_es}}'),
|
|
268
|
+
'condition: matchesChoice(user_choice,otp_phrases.another_way)': say('otp_no_other_way', '{{otp_where}} To reach a person, {{cargo.verb}} AGENT.', '{{otp_where_es}} Para hablar con una persona, {{cargo.verb_es}} AGENTE.'),
|
|
242
269
|
'condition: matchesChoice(user_choice,otp_phrases.later)': say('otp_later', "No problem. Say TEXT and I'll continue with you by message so you can do this when you are free, or {{cargo.verb}} the code whenever you are ready.", 'No hay problema. Diga TEXTO y sigo con usted por mensaje para que lo haga cuando esté libre, o {{cargo.verb_es}} el código cuando pueda.'),
|
|
243
270
|
'condition: normalized_otp_code.length > 0 && Number(cargo.otpFormatMisses || 0)<2': say('otp_bad_format', "I was expecting a 6-digit code. Let's try again. You can also {{cargo.voice ? 'press * or ' : ''}}{{cargo.verb}} EXIT to cancel.", "Esperaba un código de 6 dígitos. Intentemos de nuevo. También puede {{cargo.voice ? 'presionar * o decir' : 'escribir'}} SALIR para cancelar."),
|
|
244
271
|
'condition: normalized_otp_code.length > 0': effect('otp_format_cap_next', 'true'),
|
|
@@ -261,9 +288,11 @@ const validateCode = worker('validate-otp-code', 'ValidateOtpCode', 'Validate a
|
|
|
261
288
|
set('otp_where', 'otp_where', expression(otpWhere, 'cargo')), set('otp_where_es', 'otp_where_es', expression(otpWhere, 'cargo', 'true')),
|
|
262
289
|
set('otp_reply_picked', 'no_code_given', "no_code_given && !(otp_reply && otp_reply.key && String(otp_reply.key).indexOf('usecase:') !== 0)"),
|
|
263
290
|
cases('otp_reply_route', {
|
|
291
|
+
"condition: !no_code_given && email_too && otp_reply && ['email','resend','cannot_find','another_way'].includes(otp_reply.key)": call('otp_ranked_email_too', 'send-otp-email-too'),
|
|
264
292
|
"condition: !no_code_given && otp_reply && otp_reply.key === 'email'": call('otp_ranked_email', 'authenticate-user', { cell_number: '', email: '', retrying: true, declined_caller: true }, 'replace'),
|
|
265
293
|
"condition: !no_code_given && otp_reply && otp_reply.key === 'another_account'": call('otp_ranked_account', 'authenticate-user', { cell_number: '', email: '', retrying: true, declined_caller: true }, 'replace'),
|
|
266
|
-
"condition: !no_code_given && otp_reply && otp_reply.key === 'where'": say('otp_ranked_where', '{{otp_where}}', '{{otp_where_es}}'),
|
|
294
|
+
"condition: !no_code_given && otp_reply && (otp_reply.key === 'where' || otp_reply.key === 'cannot_find')": say('otp_ranked_where', '{{otp_where}}', '{{otp_where_es}}'),
|
|
295
|
+
"condition: !no_code_given && otp_reply && otp_reply.key === 'another_way'": say('otp_ranked_no_other_way', '{{otp_where}} To reach a person, {{cargo.verb}} AGENT.', '{{otp_where_es}} Para hablar con una persona, {{cargo.verb_es}} AGENTE.'),
|
|
267
296
|
"condition: !no_code_given && otp_reply && otp_reply.key === 'resend'": call('otp_ranked_resend', 'resend-otp-code', { resend_reason: 'requested' }),
|
|
268
297
|
"condition: !no_code_given && otp_reply && otp_reply.key === 'no_phone'": otpMenu('otp_ranked_no_phone', "No problem. Then the code can't reach you there. You can use the email on your account instead, or {{cargo.verb}} AGENT to reach a person.", 'No hay problema. Entonces el código no le puede llegar ahí. Puede usar el correo electrónico de su cuenta, o {{cargo.verb_es}} AGENTE para hablar con una persona.', false),
|
|
269
298
|
"condition: !no_code_given && otp_reply && otp_reply.key === '__agent'": call('otp_ranked_agent', 'live-agent-requested', undefined, 'reboot'),
|
|
@@ -278,7 +307,7 @@ const validateCode = worker('validate-otp-code', 'ValidateOtpCode', 'Validate a
|
|
|
278
307
|
when('otp_format_cap', 'normalized_otp_code.length !== 6 && cargo.otpFormatMisses >= 3', otpMenu('otp_format_menu', "I still don't have a 6-digit code. To reach a person, {{cargo.verb}} AGENT.", 'Todavía no tengo un código de 6 dígitos. Para hablar con una persona, {{cargo.verb_es}} AGENTE.', true)),
|
|
279
308
|
when('otp_format_retry', 'normalized_otp_code.length !== 6', call('otp_collect_again', 'get-and-validate-otp-code', undefined, 'replace')),
|
|
280
309
|
effect('otp_mismatch_count', "(cargo.otpMismatches=Number(cargo.otpMismatches || 0)+(!cargo.otpVerified && cargo.otpFailure === 'mismatch'?1:0),true)"),
|
|
281
|
-
when('otp_mismatch_cap', "!cargo.otpVerified && cargo.otpFailure === 'mismatch' && cargo.otpMismatches >= 3", effect('otp_withdraw', '(cargo.otpHash=null,true)')),
|
|
310
|
+
when('otp_mismatch_cap', "!cargo.otpVerified && cargo.otpFailure === 'mismatch' && cargo.otpMismatches >= 3", effect('otp_withdraw', '(cargo.otpHash=null,cargo.otpAltHash=null,cargo.otpAltTimestamp=null,true)')),
|
|
282
311
|
cases('otp_verified_result', {
|
|
283
312
|
'condition: cargo.otpVerified === true': effect('otp_verified_record', '(cargo.otpLateCode=String(normalized_otp_code),true)'),
|
|
284
313
|
"condition: cargo.otpFailure === 'expired' || cargo.otpFailure === 'missing'": call('otp_refresh', 'resend-otp-code', { resend_reason: '{{cargo.otpFailure}}' }),
|
|
@@ -366,4 +395,4 @@ const resume = worker('resume-identity-caller', 'ResumeIdentityCaller', 'Resume
|
|
|
366
395
|
steps: [set('identity_resume_context', 'resume_context', expression(identityResumeContext, 'cargo.forms', 'parent_form', 'retry_flow', '{acct_number:acct_number,cell_number:cell_number,email:email}')),
|
|
367
396
|
call('identity_resume_caller', '{{retry_flow}}', { form_instance: '{{resume_context.form_instance}}', parent_form: '{{resume_context.parent_form}}', result_input: '{{resume_context.result_input}}', gate_options: '{{gate_options}}', cell_number: '{{cell_number}}', email: '{{email}}', acct_number: '{{acct_number}}' }, 'replace')]
|
|
368
397
|
});
|
|
369
|
-
export const FLOWS = [taskGate, resume, service, rawTransport, contact, declined, splitFailed, rejected, auth, code, validateCode, retry, email];
|
|
398
|
+
export const FLOWS = [taskGate, resume, service, rawTransport, contact, declined, splitFailed, rejected, auth, code, validateCode, emailToo, retry, email];
|
package/dist/helpers/index.d.ts
CHANGED
|
@@ -234,6 +234,7 @@ import { formInstanceApplies } from './form-state.js';
|
|
|
234
234
|
import { formParentResume } from './form-state.js';
|
|
235
235
|
import { closeFormInstance } from './form-state.js';
|
|
236
236
|
import { checkOtp } from './otp-check.js';
|
|
237
|
+
import { clearOtp } from './otp-check.js';
|
|
237
238
|
import { maskDestination } from './otp-check.js';
|
|
238
239
|
import { validateOTP } from './otp-check.js';
|
|
239
240
|
import { OTP_EXPIRY_MS } from './otp-check.js';
|
|
@@ -242,5 +243,5 @@ import { validatePhone } from './validators.js';
|
|
|
242
243
|
import { validateEmail } from './validators.js';
|
|
243
244
|
import { urlToSpeech } from './url-speech.js';
|
|
244
245
|
import { textWithUrlToSpeech } from './url-speech.js';
|
|
245
|
-
export { continuationSafetyProtocol, cancelFormTask, contextualResponseProtocol, validateFormResponse, hasFormResponse, formProposalPolicy, stageFormProposal, nextFormProposal, formProposalReadings, bindFormProposal, decideFormProposal, commitFormProposals, settleFormProposalValidation, formProposalProtocol, validateFormProposalPolicy, validateFormProposalDescriptor, formProposalDescriptor, clearFormMisses, formNoOwner, missFormInput, formMustComplete, formInputPrompt, consumeFormFeedback, formConversationOutcome, readMoneyAmount, isMoneyAmount, moneyIntentAllows, isPastPaymentStatement, isPartialMoneyAnswer, validateMoneyOptions, takeChoiceOffer, matchesChoice, ANSWER_WORDS, CONFIRM_WORDS, isAnswer, isAgentRequest, isExitRequest, isOtherRequest, isSpelledFragment, isAmountAnswer, amountInAnswer, pickChoice, readEmail, readPhone, readInternationalPhone, readIdentifier, last4, maskEmail, callerTextNumber, textDestination, callerIsInternational, normalizeAndFindCapture, anythingElse, goodbye, sayName, dateToWords, amountToSpeech, validateCardNumber, validateExpiration, consumeActionReceipt, authorizeFormActionRetry, formSignature, selectFormAction, confirmFormRevision, prepareFormAction, actionConfirmationValid, beginFormAction, finishFormAction, actionGateVerified, sourceNeedsLoad, beginSourceRefresh, completeSourceRefresh, pickProviderQuestion, matchInputs, readFormAnswer, readInputProfile, formAcceptanceTrace, resolveFormInputs, formInputApplies, formInputFilled, nextFormInput, formInputChain, openFormInstance, validateFormIncompleteOutcome, formIncompleteOutcome, validateFormExhaustion, exhaustFormInput, formInitialReadings, orderFormReadings, proposedFormValues, applyFormValidation, formReadingDependencies, exclusiveFormReadings, enrichFormMatches, expandFormActivations, settleFormActivations, rememberFormArguments, resetFormInputs, hasFormValue, formPath, formValuesEqual, formReadingReady, formReadingRelevant, formPreparation, queueFormReadings, pendingFormReadings, formWorkLimit, answerResetsFormReplays, rejectFormReadings, invalidateFormInputs, acceptFormReadings, formInstanceApplies, formParentResume, closeFormInstance, checkOtp, maskDestination, validateOTP, OTP_EXPIRY_MS, validateDigits, validatePhone, validateEmail, urlToSpeech, textWithUrlToSpeech };
|
|
246
|
+
export { continuationSafetyProtocol, cancelFormTask, contextualResponseProtocol, validateFormResponse, hasFormResponse, formProposalPolicy, stageFormProposal, nextFormProposal, formProposalReadings, bindFormProposal, decideFormProposal, commitFormProposals, settleFormProposalValidation, formProposalProtocol, validateFormProposalPolicy, validateFormProposalDescriptor, formProposalDescriptor, clearFormMisses, formNoOwner, missFormInput, formMustComplete, formInputPrompt, consumeFormFeedback, formConversationOutcome, readMoneyAmount, isMoneyAmount, moneyIntentAllows, isPastPaymentStatement, isPartialMoneyAnswer, validateMoneyOptions, takeChoiceOffer, matchesChoice, ANSWER_WORDS, CONFIRM_WORDS, isAnswer, isAgentRequest, isExitRequest, isOtherRequest, isSpelledFragment, isAmountAnswer, amountInAnswer, pickChoice, readEmail, readPhone, readInternationalPhone, readIdentifier, last4, maskEmail, callerTextNumber, textDestination, callerIsInternational, normalizeAndFindCapture, anythingElse, goodbye, sayName, dateToWords, amountToSpeech, validateCardNumber, validateExpiration, consumeActionReceipt, authorizeFormActionRetry, formSignature, selectFormAction, confirmFormRevision, prepareFormAction, actionConfirmationValid, beginFormAction, finishFormAction, actionGateVerified, sourceNeedsLoad, beginSourceRefresh, completeSourceRefresh, pickProviderQuestion, matchInputs, readFormAnswer, readInputProfile, formAcceptanceTrace, resolveFormInputs, formInputApplies, formInputFilled, nextFormInput, formInputChain, openFormInstance, validateFormIncompleteOutcome, formIncompleteOutcome, validateFormExhaustion, exhaustFormInput, formInitialReadings, orderFormReadings, proposedFormValues, applyFormValidation, formReadingDependencies, exclusiveFormReadings, enrichFormMatches, expandFormActivations, settleFormActivations, rememberFormArguments, resetFormInputs, hasFormValue, formPath, formValuesEqual, formReadingReady, formReadingRelevant, formPreparation, queueFormReadings, pendingFormReadings, formWorkLimit, answerResetsFormReplays, rejectFormReadings, invalidateFormInputs, acceptFormReadings, formInstanceApplies, formParentResume, closeFormInstance, checkOtp, clearOtp, maskDestination, validateOTP, OTP_EXPIRY_MS, validateDigits, validatePhone, validateEmail, urlToSpeech, textWithUrlToSpeech };
|
|
246
247
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/helpers/index.js"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2CACyC,iCAAiC;+BAI0E,wBAAwB;2CAH9F,0BAA0B;qCAA1B,0BAA0B;gCAA1B,0BAA0B;mCACsE,qBAAqB;kCAArB,qBAAqB;iCAArB,qBAAqB;qCAArB,qBAAqB;iCAArB,qBAAqB;mCAArB,qBAAqB;oCAArB,qBAAqB;6CAArB,qBAAqB;qCAC/E,wBAAwB;2CAAxB,wBAAwB;+CAAxB,wBAAwB;uCAAxB,wBAAwB;gCACQ,wBAAwB;4BAAxB,wBAAwB;8BAAxB,wBAAwB;iCAAxB,wBAAwB;gCAAxB,wBAAwB;oCAAxB,wBAAwB;wCAAxB,wBAAwB;gCAmB7C,YAAY;8BAAZ,YAAY;kCAAZ,YAAY;uCAAZ,YAAY;qCAAZ,YAAY;qCAAZ,YAAY;gCAlB3G,mBAAmB;8BAiBrB,qBAAqB;6BAEiH,aAAa;8BAAb,aAAa;yBAAb,aAAa;+BAAb,aAAa;8BAAb,aAAa;+BAAb,aAAa;kCAAb,aAAa;+BAAb,aAAa;+BAAb,aAAa;2BAAb,aAAa;0BACxB,cAAc;0BAAd,cAAc;uCAAd,cAAc;+BAAd,cAAc;sBAAd,cAAc;0BAAd,cAAc;iCAAd,cAAc;gCAAd,cAAc;sCAAd,cAAc;wCAC/H,cAAc;6BACP,cAAc;wBAAd,cAAc;wBAAd,cAAc;4BACjC,iBAAiB;+BACd,uBAAuB;mCACC,kBAAkB;mCAAlB,kBAAkB;qCAxB+I,mBAAmB;yCAAnB,mBAAmB;8BAAnB,mBAAmB;iCAAnB,mBAAmB;oCAAnB,mBAAmB;kCAAnB,mBAAmB;wCAAnB,mBAAmB;gCAAnB,mBAAmB;iCAAnB,mBAAmB;mCAAnB,mBAAmB;gCAe1I,mBAAmB;mCAAnB,mBAAmB;sCAAnB,mBAAmB;qCAAnB,mBAAmB;4BAUjC,mBAAmB;+BAAnB,mBAAmB;iCAAnB,mBAAmB;oCAAnB,mBAAmB;kCACkoB,iBAAiB;iCAAjB,iBAAiB;gCAAjB,iBAAiB;8BAAjB,iBAAiB;+BAAjB,iBAAiB;iCAAjB,iBAAiB;8CAAjB,iBAAiB;sCAAjB,iBAAiB;uCAAjB,iBAAiB;iCAAjB,iBAAiB;oCAAjB,iBAAiB;kCAAjB,iBAAiB;mCAAjB,iBAAiB;oCAAjB,iBAAiB;wCAAjB,iBAAiB;sCAAjB,iBAAiB;kCAAjB,iBAAiB;sCAAjB,iBAAiB;sCAAjB,iBAAiB;sCAAjB,iBAAiB;gCAAjB,iBAAiB;6BAAjB,iBAAiB;yBAAjB,iBAAiB;gCAAjB,iBAAiB;iCAAjB,iBAAiB;oCAAjB,iBAAiB;gCAAjB,iBAAiB;kCAAjB,iBAAiB;oCAAjB,iBAAiB;8BAAjB,iBAAiB;wCAAjB,iBAAiB;mCAAjB,iBAAiB;qCAAjB,iBAAiB;mCAAjB,iBAAiB;oCAAjB,iBAAiB;iCAAjB,iBAAiB;kCAAjB,iBAAiB;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/helpers/index.js"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2CACyC,iCAAiC;+BAI0E,wBAAwB;2CAH9F,0BAA0B;qCAA1B,0BAA0B;gCAA1B,0BAA0B;mCACsE,qBAAqB;kCAArB,qBAAqB;iCAArB,qBAAqB;qCAArB,qBAAqB;iCAArB,qBAAqB;mCAArB,qBAAqB;oCAArB,qBAAqB;6CAArB,qBAAqB;qCAC/E,wBAAwB;2CAAxB,wBAAwB;+CAAxB,wBAAwB;uCAAxB,wBAAwB;gCACQ,wBAAwB;4BAAxB,wBAAwB;8BAAxB,wBAAwB;iCAAxB,wBAAwB;gCAAxB,wBAAwB;oCAAxB,wBAAwB;wCAAxB,wBAAwB;gCAmB7C,YAAY;8BAAZ,YAAY;kCAAZ,YAAY;uCAAZ,YAAY;qCAAZ,YAAY;qCAAZ,YAAY;gCAlB3G,mBAAmB;8BAiBrB,qBAAqB;6BAEiH,aAAa;8BAAb,aAAa;yBAAb,aAAa;+BAAb,aAAa;8BAAb,aAAa;+BAAb,aAAa;kCAAb,aAAa;+BAAb,aAAa;+BAAb,aAAa;2BAAb,aAAa;0BACxB,cAAc;0BAAd,cAAc;uCAAd,cAAc;+BAAd,cAAc;sBAAd,cAAc;0BAAd,cAAc;iCAAd,cAAc;gCAAd,cAAc;sCAAd,cAAc;wCAC/H,cAAc;6BACP,cAAc;wBAAd,cAAc;wBAAd,cAAc;4BACjC,iBAAiB;+BACd,uBAAuB;mCACC,kBAAkB;mCAAlB,kBAAkB;qCAxB+I,mBAAmB;yCAAnB,mBAAmB;8BAAnB,mBAAmB;iCAAnB,mBAAmB;oCAAnB,mBAAmB;kCAAnB,mBAAmB;wCAAnB,mBAAmB;gCAAnB,mBAAmB;iCAAnB,mBAAmB;mCAAnB,mBAAmB;gCAe1I,mBAAmB;mCAAnB,mBAAmB;sCAAnB,mBAAmB;qCAAnB,mBAAmB;4BAUjC,mBAAmB;+BAAnB,mBAAmB;iCAAnB,mBAAmB;oCAAnB,mBAAmB;kCACkoB,iBAAiB;iCAAjB,iBAAiB;gCAAjB,iBAAiB;8BAAjB,iBAAiB;+BAAjB,iBAAiB;iCAAjB,iBAAiB;8CAAjB,iBAAiB;sCAAjB,iBAAiB;uCAAjB,iBAAiB;iCAAjB,iBAAiB;oCAAjB,iBAAiB;kCAAjB,iBAAiB;mCAAjB,iBAAiB;oCAAjB,iBAAiB;wCAAjB,iBAAiB;sCAAjB,iBAAiB;kCAAjB,iBAAiB;sCAAjB,iBAAiB;sCAAjB,iBAAiB;sCAAjB,iBAAiB;gCAAjB,iBAAiB;6BAAjB,iBAAiB;yBAAjB,iBAAiB;gCAAjB,iBAAiB;iCAAjB,iBAAiB;oCAAjB,iBAAiB;gCAAjB,iBAAiB;kCAAjB,iBAAiB;oCAAjB,iBAAiB;8BAAjB,iBAAiB;wCAAjB,iBAAiB;mCAAjB,iBAAiB;qCAAjB,iBAAiB;mCAAjB,iBAAiB;oCAAjB,iBAAiB;iCAAjB,iBAAiB;kCAAjB,iBAAiB;yBACzqB,gBAAgB;yBAAhB,gBAAgB;gCAAhB,gBAAgB;4BAAhB,gBAAgB;8BAAhB,gBAAgB;+BACnC,iBAAiB;8BAAjB,iBAAiB;8BAAjB,iBAAiB;4BAC7B,iBAAiB;oCAAjB,iBAAiB"}
|
package/dist/helpers/index.js
CHANGED
|
@@ -32,10 +32,10 @@ import { amountToSpeech } from './amount-to-speech.js';
|
|
|
32
32
|
import { validateCardNumber, validateExpiration } from './card-checks.js';
|
|
33
33
|
import { matchInputs, readFormAnswer, readInputProfile, formAcceptanceTrace } from './match-inputs.js';
|
|
34
34
|
import { resolveFormInputs, formInputApplies, formInputFilled, nextFormInput, formInputChain, openFormInstance, validateFormIncompleteOutcome, formIncompleteOutcome, validateFormExhaustion, exhaustFormInput, formInitialReadings, orderFormReadings, proposedFormValues, applyFormValidation, formReadingDependencies, exclusiveFormReadings, enrichFormMatches, expandFormActivations, settleFormActivations, rememberFormArguments, resetFormInputs, hasFormValue, formPath, formValuesEqual, formReadingReady, formReadingRelevant, formPreparation, queueFormReadings, pendingFormReadings, formWorkLimit, answerResetsFormReplays, rejectFormReadings, invalidateFormInputs, acceptFormReadings, formInstanceApplies, formParentResume, closeFormInstance } from './form-state.js';
|
|
35
|
-
import { checkOtp, maskDestination, validateOTP, OTP_EXPIRY_MS } from './otp-check.js';
|
|
35
|
+
import { checkOtp, clearOtp, maskDestination, validateOTP, OTP_EXPIRY_MS } from './otp-check.js';
|
|
36
36
|
import { validateDigits, validatePhone, validateEmail } from './validators.js';
|
|
37
37
|
import { urlToSpeech, textWithUrlToSpeech } from './url-speech.js';
|
|
38
|
-
export { continuationSafetyProtocol, cancelFormTask, contextualResponseProtocol, validateFormResponse, hasFormResponse, formProposalPolicy, stageFormProposal, nextFormProposal, formProposalReadings, bindFormProposal, decideFormProposal, commitFormProposals, settleFormProposalValidation, formProposalProtocol, validateFormProposalPolicy, validateFormProposalDescriptor, formProposalDescriptor, clearFormMisses, formNoOwner, missFormInput, formMustComplete, formInputPrompt, consumeFormFeedback, formConversationOutcome, readMoneyAmount, isMoneyAmount, moneyIntentAllows, isPastPaymentStatement, isPartialMoneyAnswer, validateMoneyOptions, takeChoiceOffer, matchesChoice, ANSWER_WORDS, CONFIRM_WORDS, isAnswer, isAgentRequest, isExitRequest, isOtherRequest, isSpelledFragment, isAmountAnswer, amountInAnswer, pickChoice, readEmail, readPhone, readInternationalPhone, readIdentifier, last4, maskEmail, callerTextNumber, textDestination, callerIsInternational, normalizeAndFindCapture, anythingElse, goodbye, sayName, dateToWords, amountToSpeech, validateCardNumber, validateExpiration, consumeActionReceipt, authorizeFormActionRetry, formSignature, selectFormAction, confirmFormRevision, prepareFormAction, actionConfirmationValid, beginFormAction, finishFormAction, actionGateVerified, sourceNeedsLoad, beginSourceRefresh, completeSourceRefresh, pickProviderQuestion, matchInputs, readFormAnswer, readInputProfile, formAcceptanceTrace, resolveFormInputs, formInputApplies, formInputFilled, nextFormInput, formInputChain, openFormInstance, validateFormIncompleteOutcome, formIncompleteOutcome, validateFormExhaustion, exhaustFormInput, formInitialReadings, orderFormReadings, proposedFormValues, applyFormValidation, formReadingDependencies, exclusiveFormReadings, enrichFormMatches, expandFormActivations, settleFormActivations, rememberFormArguments, resetFormInputs, hasFormValue, formPath, formValuesEqual, formReadingReady, formReadingRelevant, formPreparation, queueFormReadings, pendingFormReadings, formWorkLimit, answerResetsFormReplays, rejectFormReadings, invalidateFormInputs, acceptFormReadings, formInstanceApplies, formParentResume, closeFormInstance, checkOtp, maskDestination, validateOTP, OTP_EXPIRY_MS, validateDigits, validatePhone, validateEmail, urlToSpeech, textWithUrlToSpeech, };
|
|
38
|
+
export { continuationSafetyProtocol, cancelFormTask, contextualResponseProtocol, validateFormResponse, hasFormResponse, formProposalPolicy, stageFormProposal, nextFormProposal, formProposalReadings, bindFormProposal, decideFormProposal, commitFormProposals, settleFormProposalValidation, formProposalProtocol, validateFormProposalPolicy, validateFormProposalDescriptor, formProposalDescriptor, clearFormMisses, formNoOwner, missFormInput, formMustComplete, formInputPrompt, consumeFormFeedback, formConversationOutcome, readMoneyAmount, isMoneyAmount, moneyIntentAllows, isPastPaymentStatement, isPartialMoneyAnswer, validateMoneyOptions, takeChoiceOffer, matchesChoice, ANSWER_WORDS, CONFIRM_WORDS, isAnswer, isAgentRequest, isExitRequest, isOtherRequest, isSpelledFragment, isAmountAnswer, amountInAnswer, pickChoice, readEmail, readPhone, readInternationalPhone, readIdentifier, last4, maskEmail, callerTextNumber, textDestination, callerIsInternational, normalizeAndFindCapture, anythingElse, goodbye, sayName, dateToWords, amountToSpeech, validateCardNumber, validateExpiration, consumeActionReceipt, authorizeFormActionRetry, formSignature, selectFormAction, confirmFormRevision, prepareFormAction, actionConfirmationValid, beginFormAction, finishFormAction, actionGateVerified, sourceNeedsLoad, beginSourceRefresh, completeSourceRefresh, pickProviderQuestion, matchInputs, readFormAnswer, readInputProfile, formAcceptanceTrace, resolveFormInputs, formInputApplies, formInputFilled, nextFormInput, formInputChain, openFormInstance, validateFormIncompleteOutcome, formIncompleteOutcome, validateFormExhaustion, exhaustFormInput, formInitialReadings, orderFormReadings, proposedFormValues, applyFormValidation, formReadingDependencies, exclusiveFormReadings, enrichFormMatches, expandFormActivations, settleFormActivations, rememberFormArguments, resetFormInputs, hasFormValue, formPath, formValuesEqual, formReadingReady, formReadingRelevant, formPreparation, queueFormReadings, pendingFormReadings, formWorkLimit, answerResetsFormReplays, rejectFormReadings, invalidateFormInputs, acceptFormReadings, formInstanceApplies, formParentResume, closeFormInstance, checkOtp, clearOtp, maskDestination, validateOTP, OTP_EXPIRY_MS, validateDigits, validatePhone, validateEmail, urlToSpeech, textWithUrlToSpeech, };
|
|
39
39
|
/** The functions flows call by name, ready to spread into a host's approved functions. */
|
|
40
40
|
export const STANDARD_FUNCTIONS = {
|
|
41
41
|
continuationSafetyProtocol, cancelFormTask, contextualResponseProtocol, validateFormResponse, hasFormResponse,
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* A second code may be live beside the first: one emailed to the account's address when the caller cannot find the texted
|
|
3
|
+
* code (otpAltHash / otpAltTimestamp), so either one is accepted until it expires, and neither replaces the other.
|
|
4
|
+
* @param {{otpHash?: string|null, otpTimestamp?: number|null, otpAltHash?: string|null, otpAltTimestamp?: number|null}} stored the container's pending codes
|
|
3
5
|
* @param {string} providedHash sha256 of what the customer entered
|
|
4
6
|
* @param {number} now
|
|
5
7
|
* @returns {{ok: boolean, failure: null|'missing'|'expired'|'mismatch'}}
|
|
@@ -7,6 +9,8 @@
|
|
|
7
9
|
export function checkOtp(stored: {
|
|
8
10
|
otpHash?: string | null;
|
|
9
11
|
otpTimestamp?: number | null;
|
|
12
|
+
otpAltHash?: string | null;
|
|
13
|
+
otpAltTimestamp?: number | null;
|
|
10
14
|
}, providedHash: string, now: number): {
|
|
11
15
|
ok: boolean;
|
|
12
16
|
failure: null | "missing" | "expired" | "mismatch";
|
|
@@ -15,11 +19,13 @@ export function checkOtp(stored: {
|
|
|
15
19
|
export function maskDestination(to: any): string;
|
|
16
20
|
/**
|
|
17
21
|
* A flow's code check (validateOTP): hashes what the customer entered (SHA-256, the hash the code senders store in
|
|
18
|
-
* container.otpHash) and checks it with checkOtp. Accepted: the pending
|
|
19
|
-
* otpVerifiedAt are set. Expired: the
|
|
22
|
+
* container.otpHash, and otpAltHash for a second code) and checks it with checkOtp. Accepted: the pending codes are cleared and
|
|
23
|
+
* container.otpVerified / otpVerifiedAt are set. Expired: the codes are cleared (they can never be accepted again). Every refusal leaves its reason
|
|
20
24
|
* in container.otpFailure ('missing' | 'expired' | 'mismatch'). Uses the platform's Web Crypto, so it needs no host.
|
|
21
25
|
*/
|
|
22
26
|
export function validateOTP(otp: any, container: any): Promise<boolean>;
|
|
27
|
+
/** Clears every pending code: the texted or emailed one (otpHash) and a second one emailed beside it (otpAltHash). */
|
|
28
|
+
export function clearOtp(container: any): boolean;
|
|
23
29
|
/**
|
|
24
30
|
* Is this one-time code accepted — and if not, WHY? (validateOTP)
|
|
25
31
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"otp-check.d.ts","sourceRoot":"","sources":["../../src/helpers/otp-check.js"],"names":[],"mappings":"AAeA
|
|
1
|
+
{"version":3,"file":"otp-check.d.ts","sourceRoot":"","sources":["../../src/helpers/otp-check.js"],"names":[],"mappings":"AAeA;;;;;;;GAOG;AACH,iCALW;IAAC,OAAO,CAAC,EAAE,MAAM,GAAC,IAAI,CAAC;IAAC,YAAY,CAAC,EAAE,MAAM,GAAC,IAAI,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,GAAC,IAAI,CAAC;IAAC,eAAe,CAAC,EAAE,MAAM,GAAC,IAAI,CAAA;CAAC,gBAC5G,MAAM,OACN,MAAM,GACJ;IAAC,EAAE,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,IAAI,GAAC,SAAS,GAAC,SAAS,GAAC,UAAU,CAAA;CAAC,CAcvE;AAED,6FAA6F;AAC7F,iDAQC;AAED;;;;;GAKG;AACH,wEAoBC;AAED,sHAAsH;AACtH,kDAMC;AAnFD;;;;;;;;;;;GAWG;AAEH,mCAA4C"}
|
|
@@ -12,19 +12,23 @@
|
|
|
12
12
|
*/
|
|
13
13
|
export const OTP_EXPIRY_MS = 10 * 60 * 1000;
|
|
14
14
|
/**
|
|
15
|
-
*
|
|
15
|
+
* A second code may be live beside the first: one emailed to the account's address when the caller cannot find the texted
|
|
16
|
+
* code (otpAltHash / otpAltTimestamp), so either one is accepted until it expires, and neither replaces the other.
|
|
17
|
+
* @param {{otpHash?: string|null, otpTimestamp?: number|null, otpAltHash?: string|null, otpAltTimestamp?: number|null}} stored the container's pending codes
|
|
16
18
|
* @param {string} providedHash sha256 of what the customer entered
|
|
17
19
|
* @param {number} now
|
|
18
20
|
* @returns {{ok: boolean, failure: null|'missing'|'expired'|'mismatch'}}
|
|
19
21
|
*/
|
|
20
22
|
export function checkOtp(stored, providedHash, now) {
|
|
21
|
-
|
|
23
|
+
const codes = stored ? [[stored.otpHash, stored.otpTimestamp], [stored.otpAltHash, stored.otpAltTimestamp]].filter(([hash, at]) => hash && at) : [];
|
|
24
|
+
const live = codes.filter(([, at]) => now - at <= OTP_EXPIRY_MS);
|
|
25
|
+
if (!codes.length) {
|
|
22
26
|
return { ok: false, failure: 'missing' };
|
|
23
27
|
}
|
|
24
|
-
else if (
|
|
28
|
+
else if (!live.length) {
|
|
25
29
|
return { ok: false, failure: 'expired' };
|
|
26
30
|
}
|
|
27
|
-
else if (
|
|
31
|
+
else if (!live.some(([hash]) => hash === providedHash)) {
|
|
28
32
|
return { ok: false, failure: 'mismatch' };
|
|
29
33
|
}
|
|
30
34
|
else {
|
|
@@ -44,8 +48,8 @@ export function maskDestination(to) {
|
|
|
44
48
|
}
|
|
45
49
|
/**
|
|
46
50
|
* A flow's code check (validateOTP): hashes what the customer entered (SHA-256, the hash the code senders store in
|
|
47
|
-
* container.otpHash) and checks it with checkOtp. Accepted: the pending
|
|
48
|
-
* otpVerifiedAt are set. Expired: the
|
|
51
|
+
* container.otpHash, and otpAltHash for a second code) and checks it with checkOtp. Accepted: the pending codes are cleared and
|
|
52
|
+
* container.otpVerified / otpVerifiedAt are set. Expired: the codes are cleared (they can never be accepted again). Every refusal leaves its reason
|
|
49
53
|
* in container.otpFailure ('missing' | 'expired' | 'mismatch'). Uses the platform's Web Crypto, so it needs no host.
|
|
50
54
|
*/
|
|
51
55
|
export async function validateOTP(otp, container) {
|
|
@@ -54,16 +58,14 @@ export async function validateOTP(otp, container) {
|
|
|
54
58
|
const providedHash = Array.from(new Uint8Array(digest)).map((b) => b.toString(16).padStart(2, '0')).join('');
|
|
55
59
|
const verdict = checkOtp(container, providedHash, Date.now());
|
|
56
60
|
if (verdict.ok) {
|
|
57
|
-
container
|
|
58
|
-
container.otpTimestamp = null;
|
|
61
|
+
clearOtp(container);
|
|
59
62
|
delete container.otpFailure;
|
|
60
63
|
container.otpVerified = true;
|
|
61
64
|
container.otpVerifiedAt = Date.now();
|
|
62
65
|
return true;
|
|
63
66
|
}
|
|
64
67
|
else if (verdict.failure === 'expired') {
|
|
65
|
-
container
|
|
66
|
-
container.otpTimestamp = null;
|
|
68
|
+
clearOtp(container);
|
|
67
69
|
container.otpVerified = false;
|
|
68
70
|
container.otpFailure = 'expired';
|
|
69
71
|
return false;
|
|
@@ -73,3 +75,11 @@ export async function validateOTP(otp, container) {
|
|
|
73
75
|
return false;
|
|
74
76
|
}
|
|
75
77
|
}
|
|
78
|
+
/** Clears every pending code: the texted or emailed one (otpHash) and a second one emailed beside it (otpAltHash). */
|
|
79
|
+
export function clearOtp(container) {
|
|
80
|
+
container.otpHash = null;
|
|
81
|
+
container.otpTimestamp = null;
|
|
82
|
+
container.otpAltHash = null;
|
|
83
|
+
container.otpAltTimestamp = null;
|
|
84
|
+
return true;
|
|
85
|
+
}
|
package/dist/runtime-identity.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const JSFE_RUNTIME_FINGERPRINT = "sha256:
|
|
1
|
+
export const JSFE_RUNTIME_FINGERPRINT = "sha256:d627609ff5d3f9bcce717128efd724ef64caddf410f63224fc63ee56dda3451b";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jsfe",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.17.0",
|
|
4
4
|
"description": "TypeScript workflow engine for conversational/automation apps: multi-flow orchestration, interrupt/resume, stack-based control, robust errors, and flexible tool/REST integration.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Ron Pinkas",
|