在 TypeScript 项目中使用 Web OTP API 实现短信验证码自动填充时,很多开发者会碰到一个尴尬的情况:调用 navigator.credentials.get 传入 otp 参数时,编译器直接报错,提示 CredentialRequestOptions 类型上不存在 otp 属性。即便通过类型断言绕过了参数检查,返回的 credential 对象也被推断为基础 Credential 类型,无法直接访问验证码字段。造成这种现象的原因是 TypeScript 内置的 lib.dom.d.ts 尚未完整收录 Web OTP 规范中定义的数据类型。接下来我们结合规范与工程实践,把缺失的类型补全,让验证码自动填充逻辑具备完整的类型安全保障。

一、Web OTP API 属于哪一套标准,类型缺失从何而来
Web OTP API 是 Credential Management API 的一个扩展,由 W3C Web Incubator Community Group 提出,目标是让浏览器能够从短信中自动提取一次性验证码并填充到表单。规范里定义了 OTPCredential 接口,该接口继承自 Credential,额外包含一个 code 字符串属性。当网页调用 navigator.credentials.get({ otp: { transport: ['sms'] } }) 时,浏览器会监听符合格式的短信,并在用户授权后解析出验证码。
然而,TypeScript 官方维护的 lib.dom.d.ts 文件更新速度并不总是跟上浏览器的实验性特性。在不少 TypeScript 版本中,CredentialRequestOptions 接口里没有 otp 字段,同时也没有独立的 OTPCredential 类型。这就导致开发者要么使用类型断言绕过检查,要么默默接受 any,失去了类型系统应有的保障。如果项目开启了 strict 模式,这类问题会更加突出,因为 any 和未知属性会引发连锁报错。
为了解决这个问题,可以利用 TypeScript 的声明合并机制,在项目的类型声明文件里为已有接口补充属性,或者定义独立的 OTPCredential 接口并按需断言。两种方式各有适用场景,接下来分别说明。
二、给 CredentialRequestOptions 补上 otp 参数类型
navigator.credentials.get 方法的参数类型是 CredentialRequestOptions。标准接口定义中只包含 password、federated、publicKey 等字段,并没有 otp。因此,要传递短信验证码请求参数,必须扩展这个接口。TypeScript 允许在全局作用域中重新打开已经存在的接口,并添加新的可选属性。
具体做法是创建一个 .d.ts 文件,使用 declare global 和 interface 合并。示例代码如下:
declare global {
interface CredentialRequestOptions {
otp?: {
transport?: Array<'sms'>;
};
}
}
export {};
上面的代码里 transport 被定义为只包含 'sms' 的字符串字面量数组,这是因为 Web OTP 目前仅支持短信通道。写成可选属性是为了兼容那些不支持该特性的环境,避免运行时出现意外。注意 export {} 是必需的,它让这个文件被当作模块处理,从而允许 declare global 生效。如果你的项目已经使用了 import 或 export 语句,这个空导出可以省略。
三、定义 OTPCredential 接口并实现类型守卫
扩展请求参数后,返回值的类型仍然不够精确。navigator.credentials.get 默认返回基础 Credential 或 null,而基础 Credential 接口只有 id、type 等通用字段,并不包含 code。需要定义一个 OTPCredential 接口,让它继承 Credential 并追加 code 属性,然后在使用时进行类型收窄。
先给出接口定义和类型守卫函数的代码:
interface OTPCredential extends Credential {
code: string;
}
function isOTPCredential(credential: Credential | null): credential is OTPCredential {
return credential !== null && credential.type === 'otp' && typeof (credential as any).code === 'string';
}
类型守卫 isOTPCredential 返回 credential is OTPCredential,这意味着当函数返回 true 时,TypeScript 会将 credential 变量收窄为 OTPCredential 类型。在条件判断内部,可以直接访问 credential.code,编译器会认为它是 string 类型。函数体中使用了 as any 断言来读取 code 属性,因为基础 Credential 类型并没有这个字段。如果不想使用 any,也可以先用 in 运算符检查 'code' in credential,但那样需要额外的类型处理。这里的实现兼顾了简洁和可读性。
四、封装请求函数并处理超时与错误
有了类型定义和守卫,就可以封装一个完整的获取验证码函数。实际业务中,短信验证码通常有有效期,而且浏览器监听短信的行为不能一直持续,否则会消耗资源。Web OTP API 支持通过 AbortController 终止获取操作,可以配合 setTimeout 实现超时控制。
下面是封装后的代码,它返回一个 Promise,解析结果为验证码字符串。如果用户取消或超时,则抛出对应错误。
async function getOTPCode(timeoutMs: number = 60000): Promise<string> {
const abortController = new AbortController();
const timer = window.setTimeout(() => abortController.abort(), timeoutMs);
try {
const credential = await navigator.credentials.get({
otp: { transport: ['sms'] },
signal: abortController.signal
});
if (isOTPCredential(credential)) {
return credential.code;
}
throw new Error('未能获取到短信验证码');
} catch (error) {
if (error instanceof DOMException && error.name === 'AbortError') {
throw new Error('验证码获取超时,请重新发送');
}
throw error;
} finally {
window.clearTimeout(timer);
}
}
这段代码的核心在于 signal 属性的传递。navigator.credentials.get 在执行时接受一个 signal 选项,当外部调用 abort 时,请求会立即结束,并抛出 AbortError 异常。finally 块中清理定时器,避免内存泄漏。需要注意的是,浏览器只会在顶层页面且用户手势激活时显示短信接收提示,如果是从 iframe 中调用,很可能被拒绝。因此在实际项目中,建议在用户点击“获取验证码”按钮的事件回调里调用 getOTPCode,而不是在页面加载时主动调用。
另外,Web OTP 对短信文本格式有严格要求。短信最后一行必须包含 @ 符号,后面跟着域名,例如:您的验证码是 123456 @ipipp.com。浏览器会提取 @ 前的数字部分作为 code。发送方域名需要与当前页面域名一致或符合规范,否则浏览器不会解析。开发阶段可以使用本机模拟,但真实环境要注意这些限制。
有了上述类型定义和封装,TypeScript 项目里使用 Web OTP API 就不再需要担心类型缺失。建议将类型声明集中放在公共的 .d.ts 文件中,团队其他成员也能受益于准确的智能提示。随着 TypeScript 版本更新,lib.dom 可能会补全这些类型,届时可以删除自定义声明。当前阶段,手动补齐类型是保证类型安全最直接的办法。
TypeScriptWeb OTP API短信验证码自动填充修改时间:2026-10-04 09:08:11