jsfe 1.16.0 → 1.17.1
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 +39 -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, masked by maskEmail (jsfe/helpers). */
|
|
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":"
|
|
1
|
+
{"version":3,"file":"identity-flows.d.ts","sourceRoot":"","sources":["../../src/forms/identity-flows.js"],"names":[],"mappings":"AA6BA,+GAA+G;AAC/G,oFAOC;AAQD,wFAAwF;AACxF,iDAGC;AACD,6CAMC;AACD,qHAQC;AAMD,4EAKC;AAwFD,2DAOC;AACD,4DAOC;AACD,gHAAgH;AAChH,8DAGC;AA0ID,8EAGC;AA4BD,4FAA4F;AAC5F;;;;EAOC;AAQD,0BAA4I"}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
/** Shared identity protocols. Every conversational answer enters the ordinary FORM acceptance/matching pipeline. */
|
|
2
2
|
import { defineWorker, defineForm } from './contracts.js';
|
|
3
3
|
import { OTP_EXPIRY_MS } from '../helpers/otp-check.js';
|
|
4
|
+
// maskEmail is a standard function: these functions run as flow expressions too, where it is in scope by name.
|
|
5
|
+
import { maskEmail } from '../helpers/contact.js';
|
|
4
6
|
const vars = o => Object.fromEntries(Object.entries(o).map(([k, v]) => [k, { type: Array.isArray(v) ? 'array' : v === null || typeof v === 'object' ? 'object' : typeof v, value: v }]));
|
|
5
7
|
const param = (name, description, type = 'string') => ({ name, type, description });
|
|
6
8
|
const set = (id, variable, value) => ({ id, type: 'SET', variable, value });
|
|
@@ -151,7 +153,7 @@ const authKeys = ['retry_flow', 'cancel_flow', 'email_validator', 'cell_validato
|
|
|
151
153
|
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
154
|
set('auth_restart', 'auth_restart', "retrying === true || retrying === 'true'"),
|
|
153
155
|
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)")),
|
|
156
|
+
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
157
|
when('auth_already_verified', 'cargo.otpVerified === true', end('auth_verified_end')),
|
|
156
158
|
...authKeys.map(key => set('auth_option_' + key, key, `auth_restart && cargo.authOptions ? cargo.authOptions.${key} || ${key === 'retry_returns' ? 'false' : "''"} : ${key}`)),
|
|
157
159
|
set('auth_default_retry', 'retry_flow', "retry_flow || 'authenticate-user'"), set('auth_default_cancel', 'cancel_flow', "cancel_flow || 'contact-support'"),
|
|
@@ -168,7 +170,7 @@ const auth = worker('authenticate-user', 'AuthenticateUser', 'Authenticate a con
|
|
|
168
170
|
when('auth_join_challenge', 'auth_join', call('auth_join_collect', 'get-and-validate-otp-code')),
|
|
169
171
|
when('auth_join_release', 'auth_join', call('auth_join_gate_end', 'task-gate', { form: '{{parent_form}}', gate: 'authenticate-user', active: false })),
|
|
170
172
|
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)"),
|
|
173
|
+
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
174
|
call('auth_send', 'send-otp-code'), when('auth_send_failed', 'cargo.otpSendFailure', call('auth_send_failure', 'otp-send-failed', undefined, 'replace')),
|
|
173
175
|
call('auth_code', 'get-and-validate-otp-code'),
|
|
174
176
|
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 +181,9 @@ export function otpWhere(cargo, es = false) {
|
|
|
179
181
|
const number = String(cargo.otp_cell_number || '').replace(/\D/g, '').slice(-4), tail = cargo.voice ? number.split('').join(', ') : number;
|
|
180
182
|
if (cargo.otpSentOn === 'whatsapp')
|
|
181
183
|
return es ? 'Se lo acabo de enviar aquí en este chat.' : 'I just sent it to you here in this chat.';
|
|
184
|
+
const masked = maskEmail(cargo.otpAltEmail, cargo.voice, es ? 'es' : 'en');
|
|
185
|
+
if (cargo.otp_cell_number && cargo.otpAltSent === true)
|
|
186
|
+
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
187
|
if (cargo.otp_cell_number)
|
|
183
188
|
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
189
|
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 +191,26 @@ export function otpWhere(cargo, es = false) {
|
|
|
186
191
|
export function otpPrompt(cargo, es = false) {
|
|
187
192
|
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
193
|
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 || ''));
|
|
194
|
+
const also = cargo.otp_cell_number && cargo.otpAltSent === true ? (es ? ' o en su correo ' : ' or at your email ') + maskEmail(cargo.otpAltEmail, cargo.voice, es ? 'es' : 'en') : '';
|
|
189
195
|
const name = cargo.callerPreselected && cargo.callerNamed && cargo.displayName ? cargo.displayName : '';
|
|
190
196
|
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.`) : '');
|
|
197
|
+
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
198
|
}
|
|
199
|
+
/** What the caller hears when a second code goes to the account's email, masked by maskEmail (jsfe/helpers). */
|
|
200
|
+
export function otpAlsoSent(cargo, es = false) {
|
|
201
|
+
const masked = maskEmail(cargo.otpAltEmail, cargo.voice, es ? 'es' : 'en');
|
|
202
|
+
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 + '.';
|
|
203
|
+
}
|
|
204
|
+
// The caller cannot find the texted code (or asks for another way, or for email): a second code goes to the account's email
|
|
205
|
+
// (cargo.otpAltEmail, set by the tenant's contact step when the code goes to a cell), once per challenge, without a question.
|
|
206
|
+
// The texted code stays valid beside it (send-email-otp additional: otpAltHash). A refused address says where the code went.
|
|
207
|
+
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: [
|
|
208
|
+
{ 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', "''") },
|
|
209
|
+
effect('otp_email_too_state', "(cargo.otpAltSent=/^[0-9a-f]{64}$/.test(String(alt_hash || ''))?true:'failed',true)"),
|
|
210
|
+
set('otp_email_too_said', 'also_sent', expression(otpAlsoSent, 'cargo')), set('otp_email_too_said_es', 'also_sent_es', expression(otpAlsoSent, 'cargo', 'true')),
|
|
211
|
+
set('otp_email_too_where', 'otp_where', expression(otpWhere, 'cargo')), set('otp_email_too_where_es', 'otp_where_es', expression(otpWhere, 'cargo', 'true')),
|
|
212
|
+
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}}') })
|
|
213
|
+
] });
|
|
193
214
|
const OTP_PROFILES = [{ name: 'code', label: 'verification code', label_es: 'código de verificación', profile: { kind: 'digits', min: 6, max: 6 } }, raw];
|
|
194
215
|
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
216
|
when('otp_already_verified', 'cargo.otpVerified === true', end('otp_existing_verification')),
|
|
@@ -208,6 +229,9 @@ const OTP_WORDS = {
|
|
|
208
229
|
other_alone: ['otra', 'otro', 'another', 'other'],
|
|
209
230
|
email: ['email', 'e-mail', 'e mail', 'emails', 'correo', 'correos', 'correo electrónico', 'correo electronico', 'imeil'],
|
|
210
231
|
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'],
|
|
232
|
+
// the caller cannot find the texted code, or asks for another way to get it or to verify
|
|
233
|
+
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'],
|
|
234
|
+
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
235
|
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
236
|
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
237
|
};
|
|
@@ -221,24 +245,29 @@ function otpPhoneUnavailable(value) {
|
|
|
221
245
|
// What the reply ranker may take at the code question beside the code: the step's offers. Email and another account are
|
|
222
246
|
// offered only where the step offers them (a code sent by text; a caller-ID preselection).
|
|
223
247
|
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: [
|
|
248
|
+
{ 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)' }];
|
|
249
|
+
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
250
|
set('otp_cancel', 'otp_cancel', "cargo.authCancelFlow || 'contact-support'"),
|
|
251
|
+
set('otp_email_too_ready', 'email_too', "!!(cargo.otp_cell_number && cargo.otpAltSent !== true && cargo.otpAltSent !== 'failed' && validateEmail(String(cargo.otpAltEmail || '')))"),
|
|
227
252
|
set('otp_reset_no_code', 'no_code_given', 'false'), set('otp_reset_invite', 'whatsapp_invite_sent', 'false'), set('otp_reset_callback', 'answer_key', "''"),
|
|
228
253
|
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
254
|
set('otp_other_given', 'other_account_given', "answer_key === 'another_account' ? (cargo.authOtherAccount=String(answer_value || ''),true) : false"),
|
|
230
255
|
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
256
|
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}}' })),
|
|
257
|
+
set('otp_where_early', 'otp_where', expression(otpWhere, 'cargo')), set('otp_where_early_es', 'otp_where_es', expression(otpWhere, 'cargo', 'true')),
|
|
232
258
|
cases('otp_response', {
|
|
233
259
|
'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
260
|
'condition: wants_other_account': call('otp_change_account', 'authenticate-user', { cell_number: '', email: '', retrying: true, declined_caller: true }, 'replace'),
|
|
235
261
|
// Email asked for at a code sent by text: authentication is an input step, so the contact question is asked again
|
|
236
262
|
// (without the caller-ID offer) and the caller gives the email; a code sent by email keeps "no me llegó el correo" a resend.
|
|
263
|
+
['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
264
|
'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
265
|
"condition: isExitRequest(user_choice)": call('otp_exit', '{{otp_cancel}}', undefined, 'reboot'),
|
|
239
266
|
"condition: isAnswer(user_choice,'agent')": call('otp_agent', 'live-agent-requested', undefined, 'reboot'),
|
|
240
267
|
"condition: isAnswer(user_choice,'text')": call('otp_text', 'switch-to-text', undefined, 'reboot'),
|
|
241
268
|
'condition: matchesChoice(user_choice,otp_phrases.resend)': call('otp_resend', 'resend-otp-code', { resend_reason: 'requested' }),
|
|
269
|
+
'condition: matchesChoice(user_choice,otp_phrases.cannot_find)': say('otp_cannot_find', '{{otp_where}}', '{{otp_where_es}}'),
|
|
270
|
+
'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
271
|
'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
272
|
'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
273
|
'condition: normalized_otp_code.length > 0': effect('otp_format_cap_next', 'true'),
|
|
@@ -261,9 +290,11 @@ const validateCode = worker('validate-otp-code', 'ValidateOtpCode', 'Validate a
|
|
|
261
290
|
set('otp_where', 'otp_where', expression(otpWhere, 'cargo')), set('otp_where_es', 'otp_where_es', expression(otpWhere, 'cargo', 'true')),
|
|
262
291
|
set('otp_reply_picked', 'no_code_given', "no_code_given && !(otp_reply && otp_reply.key && String(otp_reply.key).indexOf('usecase:') !== 0)"),
|
|
263
292
|
cases('otp_reply_route', {
|
|
293
|
+
"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
294
|
"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
295
|
"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}}'),
|
|
296
|
+
"condition: !no_code_given && otp_reply && (otp_reply.key === 'where' || otp_reply.key === 'cannot_find')": say('otp_ranked_where', '{{otp_where}}', '{{otp_where_es}}'),
|
|
297
|
+
"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
298
|
"condition: !no_code_given && otp_reply && otp_reply.key === 'resend'": call('otp_ranked_resend', 'resend-otp-code', { resend_reason: 'requested' }),
|
|
268
299
|
"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
300
|
"condition: !no_code_given && otp_reply && otp_reply.key === '__agent'": call('otp_ranked_agent', 'live-agent-requested', undefined, 'reboot'),
|
|
@@ -278,7 +309,7 @@ const validateCode = worker('validate-otp-code', 'ValidateOtpCode', 'Validate a
|
|
|
278
309
|
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
310
|
when('otp_format_retry', 'normalized_otp_code.length !== 6', call('otp_collect_again', 'get-and-validate-otp-code', undefined, 'replace')),
|
|
280
311
|
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)')),
|
|
312
|
+
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
313
|
cases('otp_verified_result', {
|
|
283
314
|
'condition: cargo.otpVerified === true': effect('otp_verified_record', '(cargo.otpLateCode=String(normalized_otp_code),true)'),
|
|
284
315
|
"condition: cargo.otpFailure === 'expired' || cargo.otpFailure === 'missing'": call('otp_refresh', 'resend-otp-code', { resend_reason: '{{cargo.otpFailure}}' }),
|
|
@@ -366,4 +397,4 @@ const resume = worker('resume-identity-caller', 'ResumeIdentityCaller', 'Resume
|
|
|
366
397
|
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
398
|
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
399
|
});
|
|
369
|
-
export const FLOWS = [taskGate, resume, service, rawTransport, contact, declined, splitFailed, rejected, auth, code, validateCode, retry, email];
|
|
400
|
+
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:b277b5861474d6a664568a7ba90791e721acc952575b4182d58518de7f568e99";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jsfe",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.17.1",
|
|
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",
|