diff --git a/ember-primitives/src/components/one-time-password/input.gts b/ember-primitives/src/components/one-time-password/input.gts
index 98d0b9abe..f118f5a04 100644
--- a/ember-primitives/src/components/one-time-password/input.gts
+++ b/ember-primitives/src/components/one-time-password/input.gts
@@ -21,6 +21,8 @@ function labelFor(inputIndex: number, labelFn: undefined | ((index: number) => s
let waiter = buildWaiter('ember-primitives:OTPInput:handleChange');
+let isFirst = (i: number) => i === 0;
+
const Fields: TOC<{
/**
* Any attributes passed to this component will be applied to each input.
@@ -40,6 +42,7 @@ const Fields: TOC<{
type='text'
inputmode='numeric'
...attributes
+ autocomplete={{if (isFirst i) "one-time-code"}}
{{on 'input' autoAdvance}}
{{on 'input' @handleChange}}
{{on 'keydown' handleNavigation}}
diff --git a/ember-primitives/src/components/one-time-password/otp.gts b/ember-primitives/src/components/one-time-password/otp.gts
index a193ccdc2..ee17bc7f0 100644
--- a/ember-primitives/src/components/one-time-password/otp.gts
+++ b/ember-primitives/src/components/one-time-password/otp.gts
@@ -1,18 +1,23 @@
+/** eslint-disable @typescript-eslint/no-explicit-any */
+// any disabled for this file, because not all browsers have the APIs used
+import Component from '@glimmer/component';
import { assert } from '@ember/debug';
import { fn, hash } from '@ember/helper';
import { on } from '@ember/modifier';
import { buildWaiter } from '@ember/test-waiters';
+import { resource, resourceFactory } from 'ember-resources';
+
import { Reset, Submit } from './buttons';
import { OTPInput } from './input';
-import type { TOC } from '@ember/component/template-only';
import type { WithBoundArgs } from '@glint/template';
let waiter = buildWaiter('ember-primitives:OTP:handleAutoSubmitAttempt');
-const handleFormSubmit = (submit: (data: { code: string }) => void, event: SubmitEvent) => {
+const handleFormSubmit = (submit: (data: { code: string }) => void, signal: undefined | { abort: () => void }, event: SubmitEvent) => {
event.preventDefault();
+ signal?.abort?.();
assert(
'[BUG]: handleFormSubmit was not attached to a form. Please open an issue.',
@@ -63,7 +68,45 @@ function handleChange(
form.requestSubmit();
}
-export const OTP: TOC<{
+const GetOTP = resourceFactory(( receiveOTP: (code: string) => void ) => resource(({ on }) => {
+ let ac = new AbortController();
+
+ on.cleanup(() => ac.abort());
+
+ // Not All browsers support this.
+ // Mainly, the only browsers that do support it are ones
+ // that also have a corresponding phone ecosystem.
+ // (Chrome and Safari support this, but FireFox does not)
+ let hasOTPCredential = "OTPCredential" in window;
+
+ if (!hasOTPCredential) {
+ return;
+ }
+
+ navigator.credentials
+ .get({
+ otp: { transport: ["sms"] },
+ signal: ac.signal,
+ } as any)
+ .then((otp) => {
+ if (!otp) return;
+ // there is also a type property, but it's always the same "otp" string
+ receiveOTP(( otp as any).code);
+ })
+ .catch((err) => {
+ console.error(err);
+ });
+
+
+ return {
+ // To be called if the form is submitted,
+ // so we stop waiting for credentials.
+ abort: () => ac.abort(),
+ };
+}));
+
+
+export class OTP extends Component<{
/**
* The overall OTP Input is in its own form.
* Modern UI/UX Patterns usually have this sort of field
@@ -114,16 +157,27 @@ export const OTP: TOC<{
},
];
};
-}> =
-
-;
+}> {
+ handleOTP = (code) => {
+ // TODO:
+ // 1. ensure that the fields have been populated with the value
+ // 2. do form.requestSubmit()
+ }
+
+
+ {{#let (GetOTP this.handleOTP) as |signal|}}
+
+ {{/let}}
+
+}
+