@neofaceid/web-sdk 1.40.3 → 1.42.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/README.md +7 -7
- package/dist/index.d.ts +299 -6
- package/dist/neoface-id-sdk.es.js +1219 -321
- package/dist/neoface-id-sdk.umd.js +1 -1
- package/package.json +1 -1
|
@@ -148,7 +148,6 @@ const useCamera = () => {
|
|
|
148
148
|
};
|
|
149
149
|
var ErrorType = /* @__PURE__ */ ((ErrorType2) => {
|
|
150
150
|
ErrorType2["NETWORK"] = "NetworkError";
|
|
151
|
-
ErrorType2["NETWORK_ERROR"] = "NetworkError";
|
|
152
151
|
ErrorType2["INVALID_TOKEN"] = "InvalidTokenError";
|
|
153
152
|
ErrorType2["RECOGNITION_FAILED"] = "RecognitionFailedError";
|
|
154
153
|
ErrorType2["LOGIN_FAILED"] = "LoginFailedError";
|
|
@@ -4312,9 +4311,16 @@ function refine(fn, _params = {}) {
|
|
|
4312
4311
|
function superRefine(fn) {
|
|
4313
4312
|
return /* @__PURE__ */ _superRefine(fn);
|
|
4314
4313
|
}
|
|
4314
|
+
const AuthMethodSchema = _enum(["face", "password"]);
|
|
4315
4315
|
object({
|
|
4316
4316
|
success: boolean(),
|
|
4317
4317
|
accessToken: string().optional(),
|
|
4318
|
+
/**
|
|
4319
|
+
* Só chega quando `application.can_issue_user_tokens` está ligado no core.
|
|
4320
|
+
* Sessões longas (o console) dependem dele; fluxos curtos podem ignorá-lo.
|
|
4321
|
+
*/
|
|
4322
|
+
refreshToken: string().optional(),
|
|
4323
|
+
method: AuthMethodSchema.optional(),
|
|
4318
4324
|
alias: string().optional(),
|
|
4319
4325
|
user: object({
|
|
4320
4326
|
id: string(),
|
|
@@ -4548,6 +4554,10 @@ const ENVIRONMENT_URLS = {
|
|
|
4548
4554
|
sandbox: "https://sandbox-core.neofaceid.com",
|
|
4549
4555
|
production: "https://core.neofaceid.com.br"
|
|
4550
4556
|
};
|
|
4557
|
+
const DEFAULT_AUTO_FALLBACK = {
|
|
4558
|
+
toPhone: false,
|
|
4559
|
+
toPassword: true
|
|
4560
|
+
};
|
|
4551
4561
|
const DEFAULT_LOCALE = "pt-BR";
|
|
4552
4562
|
const DEFAULT_RADIUS = 16;
|
|
4553
4563
|
const DEFAULT_THEME = "light";
|
|
@@ -4564,13 +4574,31 @@ function makeDefaultConfig() {
|
|
|
4564
4574
|
theme: DEFAULT_THEME,
|
|
4565
4575
|
locale: DEFAULT_LOCALE,
|
|
4566
4576
|
resolvedTheme: null,
|
|
4567
|
-
consent: { ...DEFAULT_CONSENT }
|
|
4577
|
+
consent: { ...DEFAULT_CONSENT },
|
|
4578
|
+
autoFallback: { ...DEFAULT_AUTO_FALLBACK }
|
|
4568
4579
|
};
|
|
4569
4580
|
}
|
|
4570
4581
|
let globalConfig = makeDefaultConfig();
|
|
4571
4582
|
let consentWarningShown = false;
|
|
4572
4583
|
function init(options = {}) {
|
|
4573
|
-
const {
|
|
4584
|
+
const {
|
|
4585
|
+
environment,
|
|
4586
|
+
baseUrl,
|
|
4587
|
+
applicationToken,
|
|
4588
|
+
appName,
|
|
4589
|
+
accent,
|
|
4590
|
+
radius,
|
|
4591
|
+
theme,
|
|
4592
|
+
locale,
|
|
4593
|
+
consent,
|
|
4594
|
+
autoFallback
|
|
4595
|
+
} = options;
|
|
4596
|
+
if (autoFallback) {
|
|
4597
|
+
globalConfig.autoFallback = {
|
|
4598
|
+
toPhone: autoFallback.toPhone ?? DEFAULT_AUTO_FALLBACK.toPhone,
|
|
4599
|
+
toPassword: autoFallback.toPassword ?? DEFAULT_AUTO_FALLBACK.toPassword
|
|
4600
|
+
};
|
|
4601
|
+
}
|
|
4574
4602
|
if (consent) {
|
|
4575
4603
|
globalConfig.consent = consent;
|
|
4576
4604
|
} else {
|
|
@@ -4668,6 +4696,9 @@ function getAppName() {
|
|
|
4668
4696
|
function getAccent() {
|
|
4669
4697
|
return globalConfig.accent;
|
|
4670
4698
|
}
|
|
4699
|
+
function getAutoFallback() {
|
|
4700
|
+
return { ...globalConfig.autoFallback };
|
|
4701
|
+
}
|
|
4671
4702
|
function getRadius() {
|
|
4672
4703
|
return globalConfig.radius;
|
|
4673
4704
|
}
|
|
@@ -4804,11 +4835,14 @@ const validateToken = async (applicationToken) => {
|
|
|
4804
4835
|
} catch (error) {
|
|
4805
4836
|
if (error instanceof Error) {
|
|
4806
4837
|
if (error.name === "AbortError") {
|
|
4807
|
-
throw new NeoFaceError("Request timeout", ErrorType.
|
|
4838
|
+
throw new NeoFaceError("Request timeout", ErrorType.NETWORK);
|
|
4839
|
+
}
|
|
4840
|
+
if (error instanceof NeoFaceError) {
|
|
4841
|
+
throw error;
|
|
4808
4842
|
}
|
|
4809
|
-
throw new NeoFaceError(error.message, ErrorType.
|
|
4843
|
+
throw new NeoFaceError(error.message, ErrorType.NETWORK);
|
|
4810
4844
|
}
|
|
4811
|
-
throw new NeoFaceError("Unknown error", ErrorType.
|
|
4845
|
+
throw new NeoFaceError("Unknown error", ErrorType.NETWORK);
|
|
4812
4846
|
}
|
|
4813
4847
|
};
|
|
4814
4848
|
const getOnboardingDetails = async (applicationToken, onboardingToken) => {
|
|
@@ -4846,7 +4880,7 @@ const getOnboardingDetails = async (applicationToken, onboardingToken) => {
|
|
|
4846
4880
|
}
|
|
4847
4881
|
throw new NeoFaceError(error.message, ErrorType.NETWORK);
|
|
4848
4882
|
}
|
|
4849
|
-
throw new NeoFaceError("Erro desconhecido", ErrorType.
|
|
4883
|
+
throw new NeoFaceError("Erro desconhecido", ErrorType.NETWORK);
|
|
4850
4884
|
}
|
|
4851
4885
|
};
|
|
4852
4886
|
const validateOnboardingToken = async (applicationToken, onboardingToken) => {
|
|
@@ -4884,7 +4918,7 @@ const validateOnboardingToken = async (applicationToken, onboardingToken) => {
|
|
|
4884
4918
|
}
|
|
4885
4919
|
throw new NeoFaceError(error.message, ErrorType.NETWORK);
|
|
4886
4920
|
}
|
|
4887
|
-
throw new NeoFaceError("Erro desconhecido", ErrorType.
|
|
4921
|
+
throw new NeoFaceError("Erro desconhecido", ErrorType.NETWORK);
|
|
4888
4922
|
}
|
|
4889
4923
|
};
|
|
4890
4924
|
const completeOnboarding = async (applicationToken, onboardingToken, faceImage, documentImage) => {
|
|
@@ -4951,7 +4985,7 @@ const completeOnboarding = async (applicationToken, onboardingToken, faceImage,
|
|
|
4951
4985
|
}
|
|
4952
4986
|
throw new NeoFaceError(error.message, ErrorType.NETWORK);
|
|
4953
4987
|
}
|
|
4954
|
-
throw new NeoFaceError("Erro desconhecido", ErrorType.
|
|
4988
|
+
throw new NeoFaceError("Erro desconhecido", ErrorType.NETWORK);
|
|
4955
4989
|
}
|
|
4956
4990
|
};
|
|
4957
4991
|
const completeOnboardingWithData = async (applicationToken, onboardingToken, personData, personImages, documentImage) => {
|
|
@@ -5037,7 +5071,7 @@ const completeOnboardingWithData = async (applicationToken, onboardingToken, per
|
|
|
5037
5071
|
}
|
|
5038
5072
|
throw new NeoFaceError(error.message, ErrorType.NETWORK);
|
|
5039
5073
|
}
|
|
5040
|
-
throw new NeoFaceError("Erro desconhecido", ErrorType.
|
|
5074
|
+
throw new NeoFaceError("Erro desconhecido", ErrorType.NETWORK);
|
|
5041
5075
|
}
|
|
5042
5076
|
};
|
|
5043
5077
|
const recognize = async (image, applicationToken) => {
|
|
@@ -5089,7 +5123,7 @@ const recognize = async (image, applicationToken) => {
|
|
|
5089
5123
|
}
|
|
5090
5124
|
throw new NeoFaceError(error.message, ErrorType.NETWORK);
|
|
5091
5125
|
}
|
|
5092
|
-
throw new NeoFaceError("Unknown error occurred", ErrorType.
|
|
5126
|
+
throw new NeoFaceError("Unknown error occurred", ErrorType.NETWORK);
|
|
5093
5127
|
}
|
|
5094
5128
|
};
|
|
5095
5129
|
const loginWithBiometric = async (image, applicationToken) => {
|
|
@@ -5130,6 +5164,9 @@ const loginWithBiometric = async (image, applicationToken) => {
|
|
|
5130
5164
|
return {
|
|
5131
5165
|
success: true,
|
|
5132
5166
|
accessToken: data.accessToken || data.access_token || "",
|
|
5167
|
+
// Só vem quando `can_issue_user_tokens` está ligado na aplicação.
|
|
5168
|
+
refreshToken: data.refreshToken || data.refresh_token || void 0,
|
|
5169
|
+
method: "face",
|
|
5133
5170
|
alias: data.alias,
|
|
5134
5171
|
user: {
|
|
5135
5172
|
id: ((_a2 = data.user) == null ? void 0 : _a2.id) || "",
|
|
@@ -5156,7 +5193,7 @@ const loginWithBiometric = async (image, applicationToken) => {
|
|
|
5156
5193
|
}
|
|
5157
5194
|
throw new NeoFaceError(error.message, ErrorType.NETWORK);
|
|
5158
5195
|
}
|
|
5159
|
-
throw new NeoFaceError("Unknown error occurred", ErrorType.
|
|
5196
|
+
throw new NeoFaceError("Unknown error occurred", ErrorType.NETWORK);
|
|
5160
5197
|
}
|
|
5161
5198
|
};
|
|
5162
5199
|
const recognizeBiometric = async (image, applicationToken, livenessCheck = true, confidenceThreshold = 0.8) => {
|
|
@@ -5210,7 +5247,7 @@ const recognizeBiometric = async (image, applicationToken, livenessCheck = true,
|
|
|
5210
5247
|
}
|
|
5211
5248
|
throw new NeoFaceError(error.message, ErrorType.NETWORK);
|
|
5212
5249
|
}
|
|
5213
|
-
throw new NeoFaceError("Unknown error occurred", ErrorType.
|
|
5250
|
+
throw new NeoFaceError("Unknown error occurred", ErrorType.NETWORK);
|
|
5214
5251
|
}
|
|
5215
5252
|
};
|
|
5216
5253
|
const simpleIdentification = async (documentType, documentNumber, applicationToken, purpose = "LOGIN") => {
|
|
@@ -5256,7 +5293,7 @@ const simpleIdentification = async (documentType, documentNumber, applicationTok
|
|
|
5256
5293
|
}
|
|
5257
5294
|
throw new NeoFaceError(error.message, ErrorType.NETWORK);
|
|
5258
5295
|
}
|
|
5259
|
-
throw new NeoFaceError("Unknown error occurred", ErrorType.
|
|
5296
|
+
throw new NeoFaceError("Unknown error occurred", ErrorType.NETWORK);
|
|
5260
5297
|
}
|
|
5261
5298
|
};
|
|
5262
5299
|
const recognizeByPurpose = async (image, applicationToken, purpose, confidenceThreshold = 0.8, signature, sessionData) => {
|
|
@@ -5322,7 +5359,7 @@ const recognizeByPurpose = async (image, applicationToken, purpose, confidenceTh
|
|
|
5322
5359
|
}
|
|
5323
5360
|
throw new NeoFaceError(error.message, ErrorType.NETWORK);
|
|
5324
5361
|
}
|
|
5325
|
-
throw new NeoFaceError("Unknown error occurred", ErrorType.
|
|
5362
|
+
throw new NeoFaceError("Unknown error occurred", ErrorType.NETWORK);
|
|
5326
5363
|
}
|
|
5327
5364
|
};
|
|
5328
5365
|
const registerPersonWithoutFace = async (personData, applicationToken, options) => {
|
|
@@ -5590,7 +5627,7 @@ const registerPersonWithBiometric = async (personData, facePhotos, applicationTo
|
|
|
5590
5627
|
}
|
|
5591
5628
|
throw new NeoFaceError(error.message, ErrorType.NETWORK);
|
|
5592
5629
|
}
|
|
5593
|
-
throw new NeoFaceError("Unknown error occurred", ErrorType.
|
|
5630
|
+
throw new NeoFaceError("Unknown error occurred", ErrorType.NETWORK);
|
|
5594
5631
|
}
|
|
5595
5632
|
};
|
|
5596
5633
|
const loginWithEmail = async (email2, password, applicationToken) => {
|
|
@@ -5616,6 +5653,8 @@ const loginWithEmail = async (email2, password, applicationToken) => {
|
|
|
5616
5653
|
success: true,
|
|
5617
5654
|
// Compatível com diferentes formatos de resposta do backend
|
|
5618
5655
|
accessToken: data.access_token || data.access,
|
|
5656
|
+
refreshToken: data.refresh_token || data.refresh || void 0,
|
|
5657
|
+
method: "password",
|
|
5619
5658
|
alias: data.user && data.user.alias || data.alias,
|
|
5620
5659
|
user: {
|
|
5621
5660
|
id: data.user && data.user.id || "",
|
|
@@ -5635,7 +5674,99 @@ const loginWithEmail = async (email2, password, applicationToken) => {
|
|
|
5635
5674
|
}
|
|
5636
5675
|
throw new NeoFaceError(error.message, ErrorType.NETWORK);
|
|
5637
5676
|
}
|
|
5638
|
-
throw new NeoFaceError("Unknown error occurred", ErrorType.
|
|
5677
|
+
throw new NeoFaceError("Unknown error occurred", ErrorType.NETWORK);
|
|
5678
|
+
}
|
|
5679
|
+
};
|
|
5680
|
+
const requestDeviceEnrollmentToken = async (userAccessToken, applicationToken) => {
|
|
5681
|
+
var _a2, _b, _c;
|
|
5682
|
+
if (!userAccessToken) {
|
|
5683
|
+
throw new NeoFaceError(
|
|
5684
|
+
"Inscrição de aparelho exige um usuário autenticado (access token ausente).",
|
|
5685
|
+
ErrorType.INVALID_TOKEN
|
|
5686
|
+
);
|
|
5687
|
+
}
|
|
5688
|
+
const controller = createTimeoutController();
|
|
5689
|
+
try {
|
|
5690
|
+
const response = await fetch(`${getApiBaseUrl()}/api/v1/devices/enrollment-token/`, {
|
|
5691
|
+
method: "POST",
|
|
5692
|
+
headers: {
|
|
5693
|
+
"Content-Type": "application/json",
|
|
5694
|
+
"X-App-Token": applicationToken,
|
|
5695
|
+
Authorization: `Bearer ${userAccessToken}`
|
|
5696
|
+
},
|
|
5697
|
+
signal: controller.signal
|
|
5698
|
+
});
|
|
5699
|
+
if (!response.ok) {
|
|
5700
|
+
if (response.status === 401 || response.status === 403) {
|
|
5701
|
+
throw new NeoFaceError(
|
|
5702
|
+
"Não foi possível inscrever o aparelho: sessão expirada ou conta sem permissão. Faça login novamente.",
|
|
5703
|
+
ErrorType.INVALID_TOKEN
|
|
5704
|
+
);
|
|
5705
|
+
}
|
|
5706
|
+
throw new NeoFaceError(`API Error ${response.status}`, ErrorType.API_ERROR);
|
|
5707
|
+
}
|
|
5708
|
+
const data = await response.json();
|
|
5709
|
+
const token = data.token ?? ((_a2 = data.data) == null ? void 0 : _a2.token);
|
|
5710
|
+
if (!token) {
|
|
5711
|
+
throw new NeoFaceError("Resposta sem token de inscrição", ErrorType.API_ERROR);
|
|
5712
|
+
}
|
|
5713
|
+
return {
|
|
5714
|
+
token,
|
|
5715
|
+
expiresIn: data.expires_in ?? ((_b = data.data) == null ? void 0 : _b.expires_in) ?? 300,
|
|
5716
|
+
purpose: data.purpose ?? ((_c = data.data) == null ? void 0 : _c.purpose) ?? "device-enrollment"
|
|
5717
|
+
};
|
|
5718
|
+
} catch (error) {
|
|
5719
|
+
if (error instanceof NeoFaceError) throw error;
|
|
5720
|
+
if (error instanceof Error) {
|
|
5721
|
+
if (error.name === "AbortError") {
|
|
5722
|
+
throw new NeoFaceError("Request timed out", ErrorType.NETWORK);
|
|
5723
|
+
}
|
|
5724
|
+
throw new NeoFaceError(error.message, ErrorType.NETWORK);
|
|
5725
|
+
}
|
|
5726
|
+
throw new NeoFaceError("Unknown error occurred", ErrorType.NETWORK);
|
|
5727
|
+
}
|
|
5728
|
+
};
|
|
5729
|
+
const refreshSession = async (refreshToken, applicationToken) => {
|
|
5730
|
+
const controller = createTimeoutController();
|
|
5731
|
+
try {
|
|
5732
|
+
const response = await fetch(`${getApiBaseUrl()}/api/v1/token/refresh/`, {
|
|
5733
|
+
method: "POST",
|
|
5734
|
+
headers: {
|
|
5735
|
+
"Content-Type": "application/json",
|
|
5736
|
+
"X-App-Token": applicationToken
|
|
5737
|
+
},
|
|
5738
|
+
body: JSON.stringify({ refresh: refreshToken }),
|
|
5739
|
+
signal: controller.signal
|
|
5740
|
+
});
|
|
5741
|
+
if (!response.ok) {
|
|
5742
|
+
if (response.status === 401 || response.status === 403) {
|
|
5743
|
+
throw new NeoFaceError(
|
|
5744
|
+
"Sessão expirada — faça login novamente",
|
|
5745
|
+
ErrorType.INVALID_TOKEN
|
|
5746
|
+
);
|
|
5747
|
+
}
|
|
5748
|
+
throw new NeoFaceError(`API Error ${response.status}`, ErrorType.API_ERROR);
|
|
5749
|
+
}
|
|
5750
|
+
const data = await response.json();
|
|
5751
|
+
const access = data.access ?? data.access_token;
|
|
5752
|
+
if (!access) {
|
|
5753
|
+
throw new NeoFaceError("Resposta de refresh sem access token", ErrorType.API_ERROR);
|
|
5754
|
+
}
|
|
5755
|
+
return {
|
|
5756
|
+
accessToken: access,
|
|
5757
|
+
// Com rotação ligada o core devolve refresh novo. Se algum ambiente
|
|
5758
|
+
// estiver sem rotação, o refresh atual segue valendo.
|
|
5759
|
+
refreshToken: data.refresh ?? data.refresh_token ?? refreshToken
|
|
5760
|
+
};
|
|
5761
|
+
} catch (error) {
|
|
5762
|
+
if (error instanceof NeoFaceError) throw error;
|
|
5763
|
+
if (error instanceof Error) {
|
|
5764
|
+
if (error.name === "AbortError") {
|
|
5765
|
+
throw new NeoFaceError("Request timed out", ErrorType.NETWORK);
|
|
5766
|
+
}
|
|
5767
|
+
throw new NeoFaceError(error.message, ErrorType.NETWORK);
|
|
5768
|
+
}
|
|
5769
|
+
throw new NeoFaceError("Unknown error occurred", ErrorType.NETWORK);
|
|
5639
5770
|
}
|
|
5640
5771
|
};
|
|
5641
5772
|
const identifyPerson = async (image, applicationToken) => {
|
|
@@ -5703,7 +5834,7 @@ const identifyPerson = async (image, applicationToken) => {
|
|
|
5703
5834
|
}
|
|
5704
5835
|
throw new NeoFaceError(error.message, ErrorType.NETWORK);
|
|
5705
5836
|
}
|
|
5706
|
-
throw new NeoFaceError("Unknown error occurred", ErrorType.
|
|
5837
|
+
throw new NeoFaceError("Unknown error occurred", ErrorType.NETWORK);
|
|
5707
5838
|
}
|
|
5708
5839
|
};
|
|
5709
5840
|
const registerBiometric = async (personId, faceImage, applicationToken) => {
|
|
@@ -5779,7 +5910,7 @@ const registerBiometric = async (personId, faceImage, applicationToken) => {
|
|
|
5779
5910
|
}
|
|
5780
5911
|
throw new NeoFaceError(error.message, ErrorType.NETWORK);
|
|
5781
5912
|
}
|
|
5782
|
-
throw new NeoFaceError("Unknown error occurred", ErrorType.
|
|
5913
|
+
throw new NeoFaceError("Unknown error occurred", ErrorType.NETWORK);
|
|
5783
5914
|
}
|
|
5784
5915
|
};
|
|
5785
5916
|
const registerApplication = async (jwtToken, consumerId, applicationData) => {
|
|
@@ -5848,7 +5979,7 @@ const registerApplication = async (jwtToken, consumerId, applicationData) => {
|
|
|
5848
5979
|
}
|
|
5849
5980
|
throw new NeoFaceError(error.message, ErrorType.NETWORK);
|
|
5850
5981
|
}
|
|
5851
|
-
throw new NeoFaceError("Unknown error occurred", ErrorType.
|
|
5982
|
+
throw new NeoFaceError("Unknown error occurred", ErrorType.NETWORK);
|
|
5852
5983
|
}
|
|
5853
5984
|
};
|
|
5854
5985
|
const recordFaceVideo = async (options = {}) => {
|
|
@@ -5950,7 +6081,7 @@ const recordFaceVideo = async (options = {}) => {
|
|
|
5950
6081
|
});
|
|
5951
6082
|
};
|
|
5952
6083
|
const pollTaskStatus = async (taskId, applicationToken, options = {}) => {
|
|
5953
|
-
var _a2, _b, _c, _d, _e, _f;
|
|
6084
|
+
var _a2, _b, _c, _d, _e, _f, _g, _h, _i, _j;
|
|
5954
6085
|
const { maxAttempts = 60, intervalMs = 1e3, onStatusChange } = options;
|
|
5955
6086
|
let attempts = 0;
|
|
5956
6087
|
while (attempts < maxAttempts) {
|
|
@@ -5982,6 +6113,10 @@ const pollTaskStatus = async (taskId, applicationToken, options = {}) => {
|
|
|
5982
6113
|
if (status === "revoked") {
|
|
5983
6114
|
throw new NeoFaceError("Task was cancelled", ErrorType.RECOGNITION_FAILED);
|
|
5984
6115
|
}
|
|
6116
|
+
if (data.success === false) {
|
|
6117
|
+
const reason = ((_h = (_g = data.data) == null ? void 0 : _g.result) == null ? void 0 : _h.message) || ((_i = data.data) == null ? void 0 : _i.error) || ((_j = data.data) == null ? void 0 : _j.message) || data.message || "Task failed";
|
|
6118
|
+
throw new NeoFaceError(reason, ErrorType.RECOGNITION_FAILED);
|
|
6119
|
+
}
|
|
5985
6120
|
await new Promise((resolve) => setTimeout(resolve, intervalMs));
|
|
5986
6121
|
} catch (error) {
|
|
5987
6122
|
if (error instanceof NeoFaceError) {
|
|
@@ -6048,7 +6183,7 @@ const startProofOfLife = async (videoBase64, applicationToken) => {
|
|
|
6048
6183
|
}
|
|
6049
6184
|
throw new NeoFaceError(error.message, ErrorType.NETWORK);
|
|
6050
6185
|
}
|
|
6051
|
-
throw new NeoFaceError("Unknown error occurred", ErrorType.
|
|
6186
|
+
throw new NeoFaceError("Unknown error occurred", ErrorType.NETWORK);
|
|
6052
6187
|
}
|
|
6053
6188
|
};
|
|
6054
6189
|
const videoToBase64 = async (videoBlob) => new Promise((resolve, reject) => {
|
|
@@ -6101,10 +6236,7 @@ const registerDocumentByImage = async (personId, jwtToken, images) => {
|
|
|
6101
6236
|
});
|
|
6102
6237
|
const data = await response.json();
|
|
6103
6238
|
if (!response.ok) {
|
|
6104
|
-
throw new NeoFaceError(
|
|
6105
|
-
(data == null ? void 0 : data.message) || `API Error ${response.status}`,
|
|
6106
|
-
ErrorType.NETWORK_ERROR
|
|
6107
|
-
);
|
|
6239
|
+
throw new NeoFaceError((data == null ? void 0 : data.message) || `API Error ${response.status}`, ErrorType.NETWORK);
|
|
6108
6240
|
}
|
|
6109
6241
|
return {
|
|
6110
6242
|
success: true,
|
|
@@ -6116,11 +6248,11 @@ const registerDocumentByImage = async (personId, jwtToken, images) => {
|
|
|
6116
6248
|
if (error instanceof NeoFaceError) throw error;
|
|
6117
6249
|
if (error instanceof Error) {
|
|
6118
6250
|
if (error.name === "AbortError") {
|
|
6119
|
-
throw new NeoFaceError("Request timeout", ErrorType.
|
|
6251
|
+
throw new NeoFaceError("Request timeout", ErrorType.NETWORK);
|
|
6120
6252
|
}
|
|
6121
|
-
throw new NeoFaceError(error.message, ErrorType.
|
|
6253
|
+
throw new NeoFaceError(error.message, ErrorType.NETWORK);
|
|
6122
6254
|
}
|
|
6123
|
-
throw new NeoFaceError("Unknown error occurred", ErrorType.
|
|
6255
|
+
throw new NeoFaceError("Unknown error occurred", ErrorType.NETWORK);
|
|
6124
6256
|
}
|
|
6125
6257
|
};
|
|
6126
6258
|
const getTaskStatus = async (taskId, applicationToken) => {
|
|
@@ -6141,10 +6273,7 @@ const getTaskStatus = async (taskId, applicationToken) => {
|
|
|
6141
6273
|
});
|
|
6142
6274
|
const data = await response.json();
|
|
6143
6275
|
if (!response.ok) {
|
|
6144
|
-
throw new NeoFaceError(
|
|
6145
|
-
(data == null ? void 0 : data.message) || `API Error ${response.status}`,
|
|
6146
|
-
ErrorType.NETWORK_ERROR
|
|
6147
|
-
);
|
|
6276
|
+
throw new NeoFaceError((data == null ? void 0 : data.message) || `API Error ${response.status}`, ErrorType.NETWORK);
|
|
6148
6277
|
}
|
|
6149
6278
|
return {
|
|
6150
6279
|
success: data.success,
|
|
@@ -6154,7 +6283,7 @@ const getTaskStatus = async (taskId, applicationToken) => {
|
|
|
6154
6283
|
};
|
|
6155
6284
|
} catch (error) {
|
|
6156
6285
|
if (error instanceof NeoFaceError) throw error;
|
|
6157
|
-
throw new NeoFaceError("Failed to fetch task status", ErrorType.
|
|
6286
|
+
throw new NeoFaceError("Failed to fetch task status", ErrorType.NETWORK);
|
|
6158
6287
|
}
|
|
6159
6288
|
};
|
|
6160
6289
|
const identifyPersonAsync = async (image, applicationToken, options = {}) => {
|
|
@@ -6190,7 +6319,7 @@ const identifyPersonAsync = async (image, applicationToken, options = {}) => {
|
|
|
6190
6319
|
}
|
|
6191
6320
|
await new Promise((resolve) => setTimeout(resolve, interval));
|
|
6192
6321
|
}
|
|
6193
|
-
throw new NeoFaceError("Timed out waiting for identification", ErrorType.
|
|
6322
|
+
throw new NeoFaceError("Timed out waiting for identification", ErrorType.NETWORK);
|
|
6194
6323
|
};
|
|
6195
6324
|
async function requestPasswordReset(email2, applicationToken) {
|
|
6196
6325
|
const baseUrl = getBaseUrl();
|
|
@@ -6246,11 +6375,13 @@ const api = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty(
|
|
|
6246
6375
|
recognizeBiometric,
|
|
6247
6376
|
recognizeByPurpose,
|
|
6248
6377
|
recordFaceVideo,
|
|
6378
|
+
refreshSession,
|
|
6249
6379
|
registerApplication,
|
|
6250
6380
|
registerBiometric,
|
|
6251
6381
|
registerDocumentByImage,
|
|
6252
6382
|
registerPersonWithBiometric,
|
|
6253
6383
|
registerPersonWithoutFace,
|
|
6384
|
+
requestDeviceEnrollmentToken,
|
|
6254
6385
|
requestPasswordReset,
|
|
6255
6386
|
simpleIdentification,
|
|
6256
6387
|
startProofOfLife,
|
|
@@ -8736,7 +8867,7 @@ function BiometricRegistrationModal({
|
|
|
8736
8867
|
/* @__PURE__ */ jsx("canvas", { ref: canvasRef })
|
|
8737
8868
|
] });
|
|
8738
8869
|
}
|
|
8739
|
-
const VERSION = "1.
|
|
8870
|
+
const VERSION = "1.42.0";
|
|
8740
8871
|
const RELEASE_DATE = "2026-09-10";
|
|
8741
8872
|
const CONSENT_TRAIL_STORAGE_KEY = "neoface_consent_trail_v1";
|
|
8742
8873
|
const CONSENT_TRAIL_MAX_LIMIT = 100;
|
|
@@ -9103,85 +9234,968 @@ function ConsentModalComponent({
|
|
|
9103
9234
|
children: copy.primary
|
|
9104
9235
|
}
|
|
9105
9236
|
),
|
|
9106
|
-
/* @__PURE__ */ jsx(
|
|
9107
|
-
"button",
|
|
9237
|
+
/* @__PURE__ */ jsx(
|
|
9238
|
+
"button",
|
|
9239
|
+
{
|
|
9240
|
+
type: "button",
|
|
9241
|
+
className: "neofaceid-consent-button neofaceid-consent-button-ghost",
|
|
9242
|
+
onClick: onDecline,
|
|
9243
|
+
children: "Agora não"
|
|
9244
|
+
}
|
|
9245
|
+
)
|
|
9246
|
+
] }),
|
|
9247
|
+
/* @__PURE__ */ jsx("div", { className: "neofaceid-consent-footer", children: /* @__PURE__ */ jsx(BrandFooter, {}) })
|
|
9248
|
+
] }) })
|
|
9249
|
+
] });
|
|
9250
|
+
}
|
|
9251
|
+
class ConsentModal {
|
|
9252
|
+
constructor() {
|
|
9253
|
+
__publicField(this, "container", null);
|
|
9254
|
+
__publicField(this, "root", null);
|
|
9255
|
+
}
|
|
9256
|
+
show(props) {
|
|
9257
|
+
if (this.container) return;
|
|
9258
|
+
injectThemeStyles();
|
|
9259
|
+
this.container = document.createElement("div");
|
|
9260
|
+
this.container.id = "neofaceid-consent-modal-container";
|
|
9261
|
+
document.body.appendChild(this.container);
|
|
9262
|
+
this.root = createRoot(this.container);
|
|
9263
|
+
this.root.render(
|
|
9264
|
+
/* @__PURE__ */ jsx(
|
|
9265
|
+
ConsentModalComponent,
|
|
9266
|
+
{
|
|
9267
|
+
...props,
|
|
9268
|
+
onAccept: () => {
|
|
9269
|
+
this.close();
|
|
9270
|
+
props.onAccept();
|
|
9271
|
+
},
|
|
9272
|
+
onDecline: () => {
|
|
9273
|
+
this.close();
|
|
9274
|
+
props.onDecline();
|
|
9275
|
+
}
|
|
9276
|
+
}
|
|
9277
|
+
)
|
|
9278
|
+
);
|
|
9279
|
+
}
|
|
9280
|
+
close() {
|
|
9281
|
+
if (this.container && document.body.contains(this.container)) {
|
|
9282
|
+
if (this.root) this.root.unmount();
|
|
9283
|
+
document.body.removeChild(this.container);
|
|
9284
|
+
this.container = null;
|
|
9285
|
+
this.root = null;
|
|
9286
|
+
}
|
|
9287
|
+
}
|
|
9288
|
+
}
|
|
9289
|
+
const DEFAULT_APP_NAME = "sua aplicação";
|
|
9290
|
+
function requestConsent(info = {}) {
|
|
9291
|
+
const globalAppName = getAppName();
|
|
9292
|
+
const appName = info.appName ?? globalAppName ?? DEFAULT_APP_NAME;
|
|
9293
|
+
if (!info.appName && !globalAppName) {
|
|
9294
|
+
console.warn(
|
|
9295
|
+
'[NeoFaceID SDK] requestConsent sem appName — usando fallback "sua aplicação". Passe via NeoFaceSDK.init({ appName }) ou opts do caller.'
|
|
9296
|
+
);
|
|
9297
|
+
}
|
|
9298
|
+
const flow = info.flow ?? "verification";
|
|
9299
|
+
return new Promise((resolve) => {
|
|
9300
|
+
const modal = new ConsentModal();
|
|
9301
|
+
modal.show({
|
|
9302
|
+
appName,
|
|
9303
|
+
flow,
|
|
9304
|
+
privacyPolicyUrl: info.privacyPolicyUrl ?? DEFAULT_PRIVACY_URL,
|
|
9305
|
+
retentionDays: info.retentionDays ?? DEFAULT_RETENTION_DAYS,
|
|
9306
|
+
onAccept: () => resolve(true),
|
|
9307
|
+
onDecline: () => resolve(false)
|
|
9308
|
+
});
|
|
9309
|
+
});
|
|
9310
|
+
}
|
|
9311
|
+
const DEFAULT_CONSENT_INFO = {
|
|
9312
|
+
flow: "verification",
|
|
9313
|
+
privacyPolicyUrl: DEFAULT_PRIVACY_URL,
|
|
9314
|
+
retentionDays: DEFAULT_RETENTION_DAYS
|
|
9315
|
+
};
|
|
9316
|
+
function Sheet({
|
|
9317
|
+
open,
|
|
9318
|
+
onDismiss,
|
|
9319
|
+
dismissOnOverlay = true,
|
|
9320
|
+
ariaLabel,
|
|
9321
|
+
ariaLabelledBy,
|
|
9322
|
+
children,
|
|
9323
|
+
className,
|
|
9324
|
+
style
|
|
9325
|
+
}) {
|
|
9326
|
+
useEffect(() => {
|
|
9327
|
+
injectThemeStyles();
|
|
9328
|
+
}, []);
|
|
9329
|
+
if (!open) return null;
|
|
9330
|
+
const handleOverlay = () => {
|
|
9331
|
+
if (dismissOnOverlay && onDismiss) onDismiss();
|
|
9332
|
+
};
|
|
9333
|
+
return /* @__PURE__ */ jsx(
|
|
9334
|
+
"div",
|
|
9335
|
+
{
|
|
9336
|
+
className: "neofaceid-root",
|
|
9337
|
+
role: "presentation",
|
|
9338
|
+
style: {
|
|
9339
|
+
position: "fixed",
|
|
9340
|
+
inset: 0,
|
|
9341
|
+
zIndex: 10001,
|
|
9342
|
+
display: "flex",
|
|
9343
|
+
alignItems: "center",
|
|
9344
|
+
justifyContent: "center",
|
|
9345
|
+
padding: 20,
|
|
9346
|
+
background: `var(${CSS_VAR.overlay})`,
|
|
9347
|
+
backdropFilter: "blur(3px)",
|
|
9348
|
+
WebkitBackdropFilter: "blur(3px)"
|
|
9349
|
+
},
|
|
9350
|
+
onClick: handleOverlay,
|
|
9351
|
+
children: /* @__PURE__ */ jsx(
|
|
9352
|
+
"div",
|
|
9353
|
+
{
|
|
9354
|
+
role: "dialog",
|
|
9355
|
+
"aria-modal": "true",
|
|
9356
|
+
"aria-label": ariaLabel,
|
|
9357
|
+
"aria-labelledby": ariaLabelledBy,
|
|
9358
|
+
className,
|
|
9359
|
+
onClick: (e) => e.stopPropagation(),
|
|
9360
|
+
style: {
|
|
9361
|
+
background: `var(${CSS_VAR.surface})`,
|
|
9362
|
+
color: `var(${CSS_VAR.text})`,
|
|
9363
|
+
borderRadius: `var(${CSS_VAR.radius}, 16px)`,
|
|
9364
|
+
padding: 28,
|
|
9365
|
+
maxWidth: 440,
|
|
9366
|
+
width: "100%",
|
|
9367
|
+
boxShadow: "0 1px 3px rgba(10,19,32,.08)",
|
|
9368
|
+
boxSizing: "border-box",
|
|
9369
|
+
...style
|
|
9370
|
+
},
|
|
9371
|
+
children
|
|
9372
|
+
}
|
|
9373
|
+
)
|
|
9374
|
+
}
|
|
9375
|
+
);
|
|
9376
|
+
}
|
|
9377
|
+
const NON_TERMINAL_STATUSES = /* @__PURE__ */ new Set(["connected", "heartbeat", "processing"]);
|
|
9378
|
+
const DEFAULT_TIMEOUT_MS = 31e4;
|
|
9379
|
+
const DEFAULT_MAX_RECONNECTS = 5;
|
|
9380
|
+
const RECONNECT_BASE_DELAY_MS = 500;
|
|
9381
|
+
const RECONNECT_MAX_DELAY_MS = 8e3;
|
|
9382
|
+
function delay(ms, signal) {
|
|
9383
|
+
return new Promise((resolve) => {
|
|
9384
|
+
const id = setTimeout(resolve, ms);
|
|
9385
|
+
signal == null ? void 0 : signal.addEventListener(
|
|
9386
|
+
"abort",
|
|
9387
|
+
() => {
|
|
9388
|
+
clearTimeout(id);
|
|
9389
|
+
resolve();
|
|
9390
|
+
},
|
|
9391
|
+
{ once: true }
|
|
9392
|
+
);
|
|
9393
|
+
});
|
|
9394
|
+
}
|
|
9395
|
+
async function consumeSseStream(options) {
|
|
9396
|
+
const {
|
|
9397
|
+
requestTicket,
|
|
9398
|
+
buildUrl,
|
|
9399
|
+
classify,
|
|
9400
|
+
timeoutMs = DEFAULT_TIMEOUT_MS,
|
|
9401
|
+
maxReconnects = DEFAULT_MAX_RECONNECTS,
|
|
9402
|
+
signal,
|
|
9403
|
+
eventSourceFactory,
|
|
9404
|
+
onProgress
|
|
9405
|
+
} = options;
|
|
9406
|
+
const makeEventSource = eventSourceFactory ?? ((url) => {
|
|
9407
|
+
const Ctor = globalThis.EventSource;
|
|
9408
|
+
if (!Ctor) {
|
|
9409
|
+
throw new NeoFaceError(
|
|
9410
|
+
"EventSource indisponível neste ambiente",
|
|
9411
|
+
ErrorType.INITIALIZATION_ERROR
|
|
9412
|
+
);
|
|
9413
|
+
}
|
|
9414
|
+
return new Ctor(url);
|
|
9415
|
+
});
|
|
9416
|
+
const startedAt = Date.now();
|
|
9417
|
+
let reconnects = 0;
|
|
9418
|
+
for (; ; ) {
|
|
9419
|
+
if (signal == null ? void 0 : signal.aborted) {
|
|
9420
|
+
throw new NeoFaceError("Fluxo cancelado", ErrorType.UNKNOWN);
|
|
9421
|
+
}
|
|
9422
|
+
const remaining = timeoutMs - (Date.now() - startedAt);
|
|
9423
|
+
if (remaining <= 0) {
|
|
9424
|
+
throw new NeoFaceError("Tempo esgotado aguardando resposta", ErrorType.NETWORK);
|
|
9425
|
+
}
|
|
9426
|
+
const ticket = await requestTicket();
|
|
9427
|
+
const outcome = await new Promise((resolve, reject) => {
|
|
9428
|
+
let source;
|
|
9429
|
+
try {
|
|
9430
|
+
source = makeEventSource(buildUrl(ticket));
|
|
9431
|
+
} catch (err) {
|
|
9432
|
+
reject(err);
|
|
9433
|
+
return;
|
|
9434
|
+
}
|
|
9435
|
+
let settled = false;
|
|
9436
|
+
let timerId;
|
|
9437
|
+
let onAbort;
|
|
9438
|
+
const cleanup = () => {
|
|
9439
|
+
if (timerId !== void 0) clearTimeout(timerId);
|
|
9440
|
+
if (onAbort) signal == null ? void 0 : signal.removeEventListener("abort", onAbort);
|
|
9441
|
+
source.close();
|
|
9442
|
+
};
|
|
9443
|
+
timerId = setTimeout(() => {
|
|
9444
|
+
if (settled) return;
|
|
9445
|
+
settled = true;
|
|
9446
|
+
cleanup();
|
|
9447
|
+
reject(new NeoFaceError("Tempo esgotado aguardando resposta", ErrorType.NETWORK));
|
|
9448
|
+
}, remaining);
|
|
9449
|
+
onAbort = () => {
|
|
9450
|
+
if (settled) return;
|
|
9451
|
+
settled = true;
|
|
9452
|
+
cleanup();
|
|
9453
|
+
reject(new NeoFaceError("Fluxo cancelado", ErrorType.UNKNOWN));
|
|
9454
|
+
};
|
|
9455
|
+
signal == null ? void 0 : signal.addEventListener("abort", onAbort, { once: true });
|
|
9456
|
+
source.onmessage = (event) => {
|
|
9457
|
+
if (settled) return;
|
|
9458
|
+
let frame;
|
|
9459
|
+
try {
|
|
9460
|
+
frame = JSON.parse(event.data);
|
|
9461
|
+
} catch {
|
|
9462
|
+
return;
|
|
9463
|
+
}
|
|
9464
|
+
if (frame.status && NON_TERMINAL_STATUSES.has(frame.status)) {
|
|
9465
|
+
if (onProgress) onProgress(frame);
|
|
9466
|
+
return;
|
|
9467
|
+
}
|
|
9468
|
+
const verdict = classify(frame);
|
|
9469
|
+
if (verdict.kind === "pending") {
|
|
9470
|
+
if (onProgress) onProgress(frame);
|
|
9471
|
+
return;
|
|
9472
|
+
}
|
|
9473
|
+
settled = true;
|
|
9474
|
+
cleanup();
|
|
9475
|
+
resolve({ type: "settled", verdict });
|
|
9476
|
+
};
|
|
9477
|
+
source.onerror = () => {
|
|
9478
|
+
if (settled) return;
|
|
9479
|
+
settled = true;
|
|
9480
|
+
cleanup();
|
|
9481
|
+
resolve({ type: "transport" });
|
|
9482
|
+
};
|
|
9483
|
+
});
|
|
9484
|
+
if (outcome.type === "settled") {
|
|
9485
|
+
const { verdict } = outcome;
|
|
9486
|
+
if (verdict.kind === "resolve") return verdict.value;
|
|
9487
|
+
throw verdict.error;
|
|
9488
|
+
}
|
|
9489
|
+
reconnects += 1;
|
|
9490
|
+
if (reconnects > maxReconnects) {
|
|
9491
|
+
throw new NeoFaceError(
|
|
9492
|
+
`Conexão perdida após ${maxReconnects} tentativas de reconexão`,
|
|
9493
|
+
ErrorType.NETWORK
|
|
9494
|
+
);
|
|
9495
|
+
}
|
|
9496
|
+
const backoff = Math.min(
|
|
9497
|
+
RECONNECT_BASE_DELAY_MS * 2 ** (reconnects - 1),
|
|
9498
|
+
RECONNECT_MAX_DELAY_MS
|
|
9499
|
+
);
|
|
9500
|
+
await delay(backoff, signal);
|
|
9501
|
+
}
|
|
9502
|
+
}
|
|
9503
|
+
const BASE_STYLE = {
|
|
9504
|
+
minHeight: 44,
|
|
9505
|
+
minWidth: 44,
|
|
9506
|
+
padding: "12px 20px",
|
|
9507
|
+
borderRadius: 16,
|
|
9508
|
+
border: "none",
|
|
9509
|
+
fontSize: 15,
|
|
9510
|
+
fontWeight: 600,
|
|
9511
|
+
fontFamily: "inherit",
|
|
9512
|
+
cursor: "pointer",
|
|
9513
|
+
transition: "background 120ms ease-out",
|
|
9514
|
+
display: "inline-flex",
|
|
9515
|
+
alignItems: "center",
|
|
9516
|
+
justifyContent: "center",
|
|
9517
|
+
gap: 8
|
|
9518
|
+
};
|
|
9519
|
+
const Button = forwardRef(
|
|
9520
|
+
({ variant = "primary", style, type = "button", ...rest }, ref) => {
|
|
9521
|
+
const variantStyle = variant === "primary" ? {
|
|
9522
|
+
background: `var(${CSS_VAR.action})`,
|
|
9523
|
+
color: `var(${CSS_VAR.actionText})`
|
|
9524
|
+
} : variant === "secondary" ? {
|
|
9525
|
+
background: `var(${CSS_VAR.surfaceMuted})`,
|
|
9526
|
+
color: `var(${CSS_VAR.text})`
|
|
9527
|
+
} : {
|
|
9528
|
+
background: "transparent",
|
|
9529
|
+
color: `var(${CSS_VAR.textMuted})`
|
|
9530
|
+
};
|
|
9531
|
+
return (
|
|
9532
|
+
// eslint-disable-next-line react/button-has-type
|
|
9533
|
+
/* @__PURE__ */ jsx(
|
|
9534
|
+
"button",
|
|
9535
|
+
{
|
|
9536
|
+
ref,
|
|
9537
|
+
type,
|
|
9538
|
+
...rest,
|
|
9539
|
+
style: { ...BASE_STYLE, ...variantStyle, ...style }
|
|
9540
|
+
}
|
|
9541
|
+
)
|
|
9542
|
+
);
|
|
9543
|
+
}
|
|
9544
|
+
);
|
|
9545
|
+
Button.displayName = "Button";
|
|
9546
|
+
function StatusPill({ tone, children, icon, className }) {
|
|
9547
|
+
const palette = SEMANTIC[tone];
|
|
9548
|
+
return /* @__PURE__ */ jsxs(
|
|
9549
|
+
"span",
|
|
9550
|
+
{
|
|
9551
|
+
className,
|
|
9552
|
+
style: {
|
|
9553
|
+
display: "inline-flex",
|
|
9554
|
+
alignItems: "center",
|
|
9555
|
+
gap: 6,
|
|
9556
|
+
padding: "4px 10px",
|
|
9557
|
+
borderRadius: 9999,
|
|
9558
|
+
background: palette.bg,
|
|
9559
|
+
color: palette.fg,
|
|
9560
|
+
fontSize: 13,
|
|
9561
|
+
fontWeight: 600,
|
|
9562
|
+
fontFamily: "inherit",
|
|
9563
|
+
lineHeight: 1.3
|
|
9564
|
+
},
|
|
9565
|
+
children: [
|
|
9566
|
+
icon,
|
|
9567
|
+
children
|
|
9568
|
+
]
|
|
9569
|
+
}
|
|
9570
|
+
);
|
|
9571
|
+
}
|
|
9572
|
+
const STYLE_ID$2 = "neofaceid-awaiting-push-styles";
|
|
9573
|
+
const CSS$1 = `
|
|
9574
|
+
.neofaceid-root .nfid-push {
|
|
9575
|
+
display: flex; flex-direction: column; align-items: center; text-align: center; gap: 10px;
|
|
9576
|
+
padding: 8px 8px 4px;
|
|
9577
|
+
}
|
|
9578
|
+
.neofaceid-root .nfid-push h2 {
|
|
9579
|
+
margin: 6px 0 0; font-size: 20px; font-weight: 700; color: var(--nfid-text); line-height: 1.25;
|
|
9580
|
+
letter-spacing: -0.015em;
|
|
9581
|
+
}
|
|
9582
|
+
.neofaceid-root .nfid-push p {
|
|
9583
|
+
margin: 0; font-size: 14px; color: var(--nfid-text-muted); line-height: 1.5;
|
|
9584
|
+
}
|
|
9585
|
+
.neofaceid-root .nfid-push-phone-icon {
|
|
9586
|
+
width: 56px; height: 56px; color: var(--nfid-action);
|
|
9587
|
+
}
|
|
9588
|
+
.neofaceid-root .nfid-push-code {
|
|
9589
|
+
display: flex; flex-direction: column; align-items: center; gap: 6px;
|
|
9590
|
+
margin-top: 14px; padding: 16px 28px;
|
|
9591
|
+
background: var(--nfid-surface-muted); border-radius: 16px; width: 100%;
|
|
9592
|
+
}
|
|
9593
|
+
.neofaceid-root .nfid-push-code-label {
|
|
9594
|
+
font-size: 11px; font-weight: 600; letter-spacing: 0.08em; text-transform: uppercase;
|
|
9595
|
+
color: var(--nfid-text-subtle);
|
|
9596
|
+
}
|
|
9597
|
+
.neofaceid-root .nfid-push-code-value {
|
|
9598
|
+
font-size: 52px; font-weight: 800; line-height: 1; letter-spacing: 0.08em;
|
|
9599
|
+
color: var(--nfid-text); font-variant-numeric: tabular-nums;
|
|
9600
|
+
}
|
|
9601
|
+
.neofaceid-root .nfid-push-countdown {
|
|
9602
|
+
font-size: 13px; color: var(--nfid-text-muted); font-variant-numeric: tabular-nums;
|
|
9603
|
+
margin-top: 12px;
|
|
9604
|
+
}
|
|
9605
|
+
.neofaceid-root .nfid-push-actions {
|
|
9606
|
+
display: flex; flex-direction: column; gap: 10px; width: 100%; margin-top: 16px;
|
|
9607
|
+
}
|
|
9608
|
+
.neofaceid-root .nfid-push-footer {
|
|
9609
|
+
display: flex; justify-content: center; margin-top: 16px; padding-top: 12px;
|
|
9610
|
+
border-top: 1px solid var(--nfid-line);
|
|
9611
|
+
width: 100%;
|
|
9612
|
+
}
|
|
9613
|
+
.neofaceid-root .nfid-push-pulse {
|
|
9614
|
+
animation: nfidPushPulse 1.8s ease-in-out infinite;
|
|
9615
|
+
}
|
|
9616
|
+
@keyframes nfidPushPulse {
|
|
9617
|
+
0%, 100% { opacity: 1; }
|
|
9618
|
+
50% { opacity: 0.45; }
|
|
9619
|
+
}
|
|
9620
|
+
@media (prefers-reduced-motion: reduce) {
|
|
9621
|
+
.neofaceid-root .nfid-push-pulse { animation: none; }
|
|
9622
|
+
}
|
|
9623
|
+
`;
|
|
9624
|
+
function formatRemaining(seconds) {
|
|
9625
|
+
const safe = Math.max(0, seconds);
|
|
9626
|
+
const mm = Math.floor(safe / 60);
|
|
9627
|
+
const ss = safe % 60;
|
|
9628
|
+
return `${mm}:${String(ss).padStart(2, "0")}`;
|
|
9629
|
+
}
|
|
9630
|
+
function AwaitingPushView({
|
|
9631
|
+
appName,
|
|
9632
|
+
numericCode,
|
|
9633
|
+
remainingSeconds,
|
|
9634
|
+
onCancel
|
|
9635
|
+
}) {
|
|
9636
|
+
useEffect(() => {
|
|
9637
|
+
injectThemeStyles();
|
|
9638
|
+
injectScopedStyles(STYLE_ID$2, CSS$1);
|
|
9639
|
+
}, []);
|
|
9640
|
+
return /* @__PURE__ */ jsxs("div", { className: "nfid-push", role: "status", "aria-live": "polite", children: [
|
|
9641
|
+
/* @__PURE__ */ jsx(StatusPill, { tone: "attention", children: "Aguardando confirmação" }),
|
|
9642
|
+
/* @__PURE__ */ jsxs(
|
|
9643
|
+
"svg",
|
|
9644
|
+
{
|
|
9645
|
+
className: "nfid-push-phone-icon nfid-push-pulse",
|
|
9646
|
+
viewBox: "0 0 24 24",
|
|
9647
|
+
fill: "none",
|
|
9648
|
+
stroke: "currentColor",
|
|
9649
|
+
strokeWidth: "1.75",
|
|
9650
|
+
strokeLinecap: "round",
|
|
9651
|
+
strokeLinejoin: "round",
|
|
9652
|
+
"aria-hidden": "true",
|
|
9653
|
+
children: [
|
|
9654
|
+
/* @__PURE__ */ jsx("rect", { x: "6", y: "2", width: "12", height: "20", rx: "3" }),
|
|
9655
|
+
/* @__PURE__ */ jsx("path", { d: "M11 18h2" })
|
|
9656
|
+
]
|
|
9657
|
+
}
|
|
9658
|
+
),
|
|
9659
|
+
/* @__PURE__ */ jsx("h2", { children: "Confirme no seu celular" }),
|
|
9660
|
+
/* @__PURE__ */ jsxs("p", { children: [
|
|
9661
|
+
"Enviamos um pedido de confirmação para o seu aparelho. Abra o NeoFaceID Push e confirme com o seu rosto para continuar no ",
|
|
9662
|
+
appName,
|
|
9663
|
+
"."
|
|
9664
|
+
] }),
|
|
9665
|
+
numericCode && /* @__PURE__ */ jsxs("div", { className: "nfid-push-code", children: [
|
|
9666
|
+
/* @__PURE__ */ jsx("span", { className: "nfid-push-code-label", children: "Digite este número no celular" }),
|
|
9667
|
+
/* @__PURE__ */ jsx(
|
|
9668
|
+
"span",
|
|
9669
|
+
{
|
|
9670
|
+
className: "nfid-push-code-value",
|
|
9671
|
+
"aria-label": `Código de confirmação: ${numericCode.split("").join(" ")}`,
|
|
9672
|
+
children: numericCode
|
|
9673
|
+
}
|
|
9674
|
+
)
|
|
9675
|
+
] }),
|
|
9676
|
+
/* @__PURE__ */ jsxs("p", { className: "nfid-push-countdown", children: [
|
|
9677
|
+
"Expira em ",
|
|
9678
|
+
formatRemaining(remainingSeconds)
|
|
9679
|
+
] }),
|
|
9680
|
+
/* @__PURE__ */ jsx("div", { className: "nfid-push-actions", children: /* @__PURE__ */ jsx(Button, { variant: "ghost", onClick: onCancel, children: "Cancelar" }) }),
|
|
9681
|
+
/* @__PURE__ */ jsx("div", { className: "nfid-push-footer", children: /* @__PURE__ */ jsx(BrandFooter, {}) })
|
|
9682
|
+
] });
|
|
9683
|
+
}
|
|
9684
|
+
function PushExpiredView({ onFallback, onCancel }) {
|
|
9685
|
+
useEffect(() => {
|
|
9686
|
+
injectThemeStyles();
|
|
9687
|
+
injectScopedStyles(STYLE_ID$2, CSS$1);
|
|
9688
|
+
}, []);
|
|
9689
|
+
return /* @__PURE__ */ jsxs("div", { className: "nfid-push", role: "alert", "aria-live": "assertive", children: [
|
|
9690
|
+
/* @__PURE__ */ jsxs(
|
|
9691
|
+
"svg",
|
|
9692
|
+
{
|
|
9693
|
+
className: "nfid-push-phone-icon",
|
|
9694
|
+
viewBox: "0 0 24 24",
|
|
9695
|
+
fill: "none",
|
|
9696
|
+
stroke: "currentColor",
|
|
9697
|
+
strokeWidth: "1.75",
|
|
9698
|
+
strokeLinecap: "round",
|
|
9699
|
+
strokeLinejoin: "round",
|
|
9700
|
+
"aria-hidden": "true",
|
|
9701
|
+
style: { color: "var(--nfid-text-muted)" },
|
|
9702
|
+
children: [
|
|
9703
|
+
/* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "9" }),
|
|
9704
|
+
/* @__PURE__ */ jsx("path", { d: "M12 7v5l3 2" })
|
|
9705
|
+
]
|
|
9706
|
+
}
|
|
9707
|
+
),
|
|
9708
|
+
/* @__PURE__ */ jsx("h2", { children: "O tempo esgotou" }),
|
|
9709
|
+
/* @__PURE__ */ jsx("p", { children: "Não recebemos a confirmação do seu celular a tempo." }),
|
|
9710
|
+
/* @__PURE__ */ jsx("div", { className: "nfid-push-actions", children: onFallback ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
9711
|
+
/* @__PURE__ */ jsx(Button, { variant: "primary", onClick: onFallback, children: "Entrar de outro jeito" }),
|
|
9712
|
+
/* @__PURE__ */ jsx(Button, { variant: "ghost", onClick: onCancel, children: "Agora não" })
|
|
9713
|
+
] }) : /* @__PURE__ */ jsx(Button, { variant: "primary", onClick: onCancel, children: "Entendi" }) }),
|
|
9714
|
+
/* @__PURE__ */ jsx("div", { className: "nfid-push-footer", children: /* @__PURE__ */ jsx(BrandFooter, {}) })
|
|
9715
|
+
] });
|
|
9716
|
+
}
|
|
9717
|
+
function PushDeniedView({ appName, onCancel }) {
|
|
9718
|
+
useEffect(() => {
|
|
9719
|
+
injectThemeStyles();
|
|
9720
|
+
injectScopedStyles(STYLE_ID$2, CSS$1);
|
|
9721
|
+
}, []);
|
|
9722
|
+
return /* @__PURE__ */ jsxs("div", { className: "nfid-push", role: "alert", "aria-live": "assertive", children: [
|
|
9723
|
+
/* @__PURE__ */ jsxs(
|
|
9724
|
+
"svg",
|
|
9725
|
+
{
|
|
9726
|
+
className: "nfid-push-phone-icon",
|
|
9727
|
+
viewBox: "0 0 24 24",
|
|
9728
|
+
fill: "none",
|
|
9729
|
+
stroke: "currentColor",
|
|
9730
|
+
strokeWidth: "1.75",
|
|
9731
|
+
strokeLinecap: "round",
|
|
9732
|
+
strokeLinejoin: "round",
|
|
9733
|
+
"aria-hidden": "true",
|
|
9734
|
+
style: { color: "#D64545" },
|
|
9735
|
+
children: [
|
|
9736
|
+
/* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "9" }),
|
|
9737
|
+
/* @__PURE__ */ jsx("path", { d: "M15 9l-6 6M9 9l6 6" })
|
|
9738
|
+
]
|
|
9739
|
+
}
|
|
9740
|
+
),
|
|
9741
|
+
/* @__PURE__ */ jsx("h2", { children: "Confirmação recusada" }),
|
|
9742
|
+
/* @__PURE__ */ jsxs("p", { children: [
|
|
9743
|
+
"O pedido foi recusado no celular. Se não foi você, procure o suporte do ",
|
|
9744
|
+
appName,
|
|
9745
|
+
"."
|
|
9746
|
+
] }),
|
|
9747
|
+
/* @__PURE__ */ jsx("div", { className: "nfid-push-actions", children: /* @__PURE__ */ jsx(Button, { variant: "primary", onClick: onCancel, children: "Entendi" }) }),
|
|
9748
|
+
/* @__PURE__ */ jsx("div", { className: "nfid-push-footer", children: /* @__PURE__ */ jsx(BrandFooter, {}) })
|
|
9749
|
+
] });
|
|
9750
|
+
}
|
|
9751
|
+
const CONTAINER_ID = "neofaceid-push-approval-container";
|
|
9752
|
+
function classifyAuthorizationFrame(frame) {
|
|
9753
|
+
var _a2, _b;
|
|
9754
|
+
switch (frame.status) {
|
|
9755
|
+
case "approved":
|
|
9756
|
+
case "success":
|
|
9757
|
+
return { kind: "resolve", value: { status: "approved", data: frame.data ?? {} } };
|
|
9758
|
+
case "denied":
|
|
9759
|
+
return {
|
|
9760
|
+
kind: "resolve",
|
|
9761
|
+
value: { status: "denied", reason: ((_a2 = frame.data) == null ? void 0 : _a2.reason) ?? frame.message }
|
|
9762
|
+
};
|
|
9763
|
+
case "expired":
|
|
9764
|
+
return { kind: "resolve", value: { status: "expired" } };
|
|
9765
|
+
case "failed":
|
|
9766
|
+
case "error":
|
|
9767
|
+
return {
|
|
9768
|
+
kind: "reject",
|
|
9769
|
+
error: new NeoFaceError(
|
|
9770
|
+
frame.message ?? ((_b = frame.data) == null ? void 0 : _b.message) ?? "Falha na confirmação",
|
|
9771
|
+
ErrorType.API_ERROR
|
|
9772
|
+
)
|
|
9773
|
+
};
|
|
9774
|
+
default:
|
|
9775
|
+
return { kind: "pending" };
|
|
9776
|
+
}
|
|
9777
|
+
}
|
|
9778
|
+
function PushApprovalModal({
|
|
9779
|
+
requestId,
|
|
9780
|
+
applicationToken,
|
|
9781
|
+
expiresIn,
|
|
9782
|
+
numericCode,
|
|
9783
|
+
appName,
|
|
9784
|
+
onFallback,
|
|
9785
|
+
onSettle
|
|
9786
|
+
}) {
|
|
9787
|
+
const headerAppName = appName ?? getAppName() ?? "sua aplicação";
|
|
9788
|
+
const [remaining, setRemaining] = useState(expiresIn);
|
|
9789
|
+
const [phase, setPhase] = useState({ kind: "waiting" });
|
|
9790
|
+
useEffect(() => {
|
|
9791
|
+
if (phase.kind !== "waiting") return void 0;
|
|
9792
|
+
const id = setInterval(() => {
|
|
9793
|
+
setRemaining((r) => r > 0 ? r - 1 : 0);
|
|
9794
|
+
}, 1e3);
|
|
9795
|
+
return () => clearInterval(id);
|
|
9796
|
+
}, [phase.kind]);
|
|
9797
|
+
useEffect(() => {
|
|
9798
|
+
const controller = new AbortController();
|
|
9799
|
+
let done = false;
|
|
9800
|
+
const base = getBaseUrl();
|
|
9801
|
+
const ticketUrl = `${base}/api/v1/authorization/events/${encodeURIComponent(requestId)}/ticket/`;
|
|
9802
|
+
consumeSseStream({
|
|
9803
|
+
signal: controller.signal,
|
|
9804
|
+
timeoutMs: (expiresIn + 30) * 1e3,
|
|
9805
|
+
requestTicket: async () => {
|
|
9806
|
+
var _a2;
|
|
9807
|
+
const res = await fetch(ticketUrl, {
|
|
9808
|
+
method: "POST",
|
|
9809
|
+
headers: { "Content-Type": "application/json", "X-App-Token": applicationToken }
|
|
9810
|
+
});
|
|
9811
|
+
if (!res.ok) {
|
|
9812
|
+
throw new NeoFaceError(
|
|
9813
|
+
`Falha ao abrir canal de confirmação: ${res.status}`,
|
|
9814
|
+
res.status === 401 || res.status === 403 ? ErrorType.INVALID_TOKEN : ErrorType.NETWORK
|
|
9815
|
+
);
|
|
9816
|
+
}
|
|
9817
|
+
const body = await res.json();
|
|
9818
|
+
const ticket = ((_a2 = body == null ? void 0 : body.data) == null ? void 0 : _a2.ticket) ?? (body == null ? void 0 : body.ticket);
|
|
9819
|
+
if (!ticket) {
|
|
9820
|
+
throw new NeoFaceError("Canal de confirmação sem ticket", ErrorType.API_ERROR);
|
|
9821
|
+
}
|
|
9822
|
+
return ticket;
|
|
9823
|
+
},
|
|
9824
|
+
buildUrl: (ticket) => `${base}/api/v1/authorization/events/${encodeURIComponent(requestId)}/?ticket=${encodeURIComponent(ticket)}`,
|
|
9825
|
+
classify: classifyAuthorizationFrame
|
|
9826
|
+
}).then((outcome) => {
|
|
9827
|
+
if (done) return;
|
|
9828
|
+
done = true;
|
|
9829
|
+
if (outcome.status === "denied") {
|
|
9830
|
+
setPhase({ kind: "denied" });
|
|
9831
|
+
return;
|
|
9832
|
+
}
|
|
9833
|
+
if (outcome.status === "expired") {
|
|
9834
|
+
setPhase({ kind: "expired" });
|
|
9835
|
+
return;
|
|
9836
|
+
}
|
|
9837
|
+
onSettle(outcome);
|
|
9838
|
+
}).catch(() => {
|
|
9839
|
+
if (done || controller.signal.aborted) return;
|
|
9840
|
+
done = true;
|
|
9841
|
+
setPhase({ kind: "expired" });
|
|
9842
|
+
});
|
|
9843
|
+
return () => {
|
|
9844
|
+
done = true;
|
|
9845
|
+
controller.abort();
|
|
9846
|
+
};
|
|
9847
|
+
}, [requestId]);
|
|
9848
|
+
if (phase.kind === "denied") {
|
|
9849
|
+
return /* @__PURE__ */ jsx(Sheet, { open: true, dismissOnOverlay: false, ariaLabel: "Confirmação recusada", children: /* @__PURE__ */ jsx(PushDeniedView, { appName: headerAppName, onCancel: () => onSettle({ status: "denied" }) }) });
|
|
9850
|
+
}
|
|
9851
|
+
if (phase.kind === "expired") {
|
|
9852
|
+
return /* @__PURE__ */ jsx(Sheet, { open: true, dismissOnOverlay: false, ariaLabel: "Tempo esgotado", children: /* @__PURE__ */ jsx(
|
|
9853
|
+
PushExpiredView,
|
|
9854
|
+
{
|
|
9855
|
+
onFallback: onFallback ? () => {
|
|
9856
|
+
onFallback();
|
|
9857
|
+
onSettle({ status: "expired" });
|
|
9858
|
+
} : void 0,
|
|
9859
|
+
onCancel: () => onSettle({ status: "expired" })
|
|
9860
|
+
}
|
|
9861
|
+
) });
|
|
9862
|
+
}
|
|
9863
|
+
return /* @__PURE__ */ jsx(Sheet, { open: true, dismissOnOverlay: false, ariaLabel: "Aguardando confirmação no celular", children: /* @__PURE__ */ jsx(
|
|
9864
|
+
AwaitingPushView,
|
|
9865
|
+
{
|
|
9866
|
+
appName: headerAppName,
|
|
9867
|
+
numericCode,
|
|
9868
|
+
remainingSeconds: remaining,
|
|
9869
|
+
onCancel: () => onSettle({ status: "cancelled" })
|
|
9870
|
+
}
|
|
9871
|
+
) });
|
|
9872
|
+
}
|
|
9873
|
+
function awaitPushApproval(options) {
|
|
9874
|
+
return new Promise((resolve) => {
|
|
9875
|
+
const container = document.createElement("div");
|
|
9876
|
+
container.id = CONTAINER_ID;
|
|
9877
|
+
document.body.appendChild(container);
|
|
9878
|
+
const root = createRoot(container);
|
|
9879
|
+
const settle = (outcome) => {
|
|
9880
|
+
root.unmount();
|
|
9881
|
+
if (document.body.contains(container)) document.body.removeChild(container);
|
|
9882
|
+
resolve(outcome);
|
|
9883
|
+
};
|
|
9884
|
+
root.render(/* @__PURE__ */ jsx(PushApprovalModal, { ...options, onSettle: settle }));
|
|
9885
|
+
});
|
|
9886
|
+
}
|
|
9887
|
+
function evaluateFacePosition(box, videoWidth, videoHeight) {
|
|
9888
|
+
if (!box || !videoWidth || !videoHeight) {
|
|
9889
|
+
return {
|
|
9890
|
+
faceState: "searching",
|
|
9891
|
+
instruction: "Centralize o rosto"
|
|
9892
|
+
};
|
|
9893
|
+
}
|
|
9894
|
+
const faceCenterX = box.x + box.width / 2;
|
|
9895
|
+
const faceCenterY = box.y + box.height / 2;
|
|
9896
|
+
const frameCenterX = videoWidth / 2;
|
|
9897
|
+
const frameCenterY = videoHeight / 2;
|
|
9898
|
+
const offsetX = Math.abs(faceCenterX - frameCenterX) / videoWidth;
|
|
9899
|
+
const offsetY = Math.abs(faceCenterY - frameCenterY) / videoHeight;
|
|
9900
|
+
const faceArea = box.width * box.height;
|
|
9901
|
+
const frameArea = videoWidth * videoHeight;
|
|
9902
|
+
const faceAreaRatio = faceArea / frameArea;
|
|
9903
|
+
if (faceAreaRatio < 0.08) {
|
|
9904
|
+
return {
|
|
9905
|
+
faceState: "too-far",
|
|
9906
|
+
instruction: "Aproxime-se"
|
|
9907
|
+
};
|
|
9908
|
+
}
|
|
9909
|
+
if (faceAreaRatio > 0.45) {
|
|
9910
|
+
return {
|
|
9911
|
+
faceState: "too-close",
|
|
9912
|
+
instruction: "Afaste-se"
|
|
9913
|
+
};
|
|
9914
|
+
}
|
|
9915
|
+
if (offsetX > 0.18 || offsetY > 0.18) {
|
|
9916
|
+
return {
|
|
9917
|
+
faceState: "off-center",
|
|
9918
|
+
instruction: "Centralize o rosto"
|
|
9919
|
+
};
|
|
9920
|
+
}
|
|
9921
|
+
return {
|
|
9922
|
+
faceState: "centered",
|
|
9923
|
+
instruction: "Perfeito, mantenha"
|
|
9924
|
+
};
|
|
9925
|
+
}
|
|
9926
|
+
function showCameraOvalGuideModal(initialOptions = {}) {
|
|
9927
|
+
const overlayDiv = document.createElement("div");
|
|
9928
|
+
overlayDiv.className = "neofaceid-camera-oval-guide-overlay-root";
|
|
9929
|
+
Object.assign(overlayDiv.style, {
|
|
9930
|
+
position: "fixed",
|
|
9931
|
+
top: "0",
|
|
9932
|
+
left: "0",
|
|
9933
|
+
width: "100vw",
|
|
9934
|
+
height: "100vh",
|
|
9935
|
+
backgroundColor: "rgba(0, 0, 0, 0.85)",
|
|
9936
|
+
backdropFilter: "blur(8px)",
|
|
9937
|
+
zIndex: "99999",
|
|
9938
|
+
display: "flex",
|
|
9939
|
+
flexDirection: "column",
|
|
9940
|
+
alignItems: "center",
|
|
9941
|
+
justifyContent: "center"
|
|
9942
|
+
});
|
|
9943
|
+
document.body.appendChild(overlayDiv);
|
|
9944
|
+
let root = createRoot(overlayDiv);
|
|
9945
|
+
let currentStream = initialOptions.stream || null;
|
|
9946
|
+
let currentFaceState = initialOptions.faceState || "searching";
|
|
9947
|
+
let currentInstruction = initialOptions.instruction || "Centralize o rosto";
|
|
9948
|
+
const renderModal = (stream, faceState, instruction) => {
|
|
9949
|
+
if (!root) return;
|
|
9950
|
+
root.render(
|
|
9951
|
+
/* @__PURE__ */ jsxs("div", { style: { position: "relative", width: "100%", height: "100%", display: "flex", alignItems: "center", justifyContent: "center" }, children: [
|
|
9952
|
+
initialOptions.onCancel && /* @__PURE__ */ jsx(
|
|
9953
|
+
"button",
|
|
9954
|
+
{
|
|
9955
|
+
onClick: () => {
|
|
9956
|
+
var _a2;
|
|
9957
|
+
handle.close();
|
|
9958
|
+
(_a2 = initialOptions.onCancel) == null ? void 0 : _a2.call(initialOptions);
|
|
9959
|
+
},
|
|
9960
|
+
style: {
|
|
9961
|
+
position: "absolute",
|
|
9962
|
+
top: "20px",
|
|
9963
|
+
right: "20px",
|
|
9964
|
+
background: "rgba(255, 255, 255, 0.2)",
|
|
9965
|
+
border: "none",
|
|
9966
|
+
color: "#fff",
|
|
9967
|
+
fontSize: "24px",
|
|
9968
|
+
width: "40px",
|
|
9969
|
+
height: "40px",
|
|
9970
|
+
borderRadius: "50%",
|
|
9971
|
+
cursor: "pointer",
|
|
9972
|
+
zIndex: 10,
|
|
9973
|
+
display: "flex",
|
|
9974
|
+
alignItems: "center",
|
|
9975
|
+
justifyContent: "center"
|
|
9976
|
+
},
|
|
9977
|
+
"aria-label": "Cancelar",
|
|
9978
|
+
children: "✕"
|
|
9979
|
+
}
|
|
9980
|
+
),
|
|
9981
|
+
/* @__PURE__ */ jsx(
|
|
9982
|
+
CameraOvalGuide,
|
|
9983
|
+
{
|
|
9984
|
+
stream,
|
|
9985
|
+
faceState,
|
|
9986
|
+
instruction
|
|
9987
|
+
}
|
|
9988
|
+
)
|
|
9989
|
+
] })
|
|
9990
|
+
);
|
|
9991
|
+
};
|
|
9992
|
+
renderModal(currentStream, currentFaceState, currentInstruction);
|
|
9993
|
+
const handle = {
|
|
9994
|
+
update: (stream, faceState, instruction) => {
|
|
9995
|
+
currentStream = stream;
|
|
9996
|
+
currentFaceState = faceState;
|
|
9997
|
+
currentInstruction = instruction;
|
|
9998
|
+
renderModal(stream, faceState, instruction);
|
|
9999
|
+
},
|
|
10000
|
+
close: () => {
|
|
10001
|
+
if (root) {
|
|
10002
|
+
root.unmount();
|
|
10003
|
+
root = null;
|
|
10004
|
+
}
|
|
10005
|
+
if (overlayDiv.parentElement) {
|
|
10006
|
+
overlayDiv.parentElement.removeChild(overlayDiv);
|
|
10007
|
+
}
|
|
10008
|
+
}
|
|
10009
|
+
};
|
|
10010
|
+
return handle;
|
|
10011
|
+
}
|
|
10012
|
+
function getHaloStyles(state) {
|
|
10013
|
+
switch (state) {
|
|
10014
|
+
case "centered":
|
|
10015
|
+
return {
|
|
10016
|
+
borderColor: "#0E9F6E",
|
|
10017
|
+
boxShadow: "0 0 0 4px rgba(14, 159, 110, 0.8), 0 0 45px rgba(14, 159, 110, 0.45)",
|
|
10018
|
+
svgStroke: "#0E9F6E",
|
|
10019
|
+
badgeBg: "rgba(14, 159, 110, 0.95)",
|
|
10020
|
+
badgeColor: "#FFFFFF"
|
|
10021
|
+
};
|
|
10022
|
+
case "too-far":
|
|
10023
|
+
case "too-close":
|
|
10024
|
+
case "off-center":
|
|
10025
|
+
return {
|
|
10026
|
+
borderColor: "#F59E0B",
|
|
10027
|
+
boxShadow: "0 0 0 4px rgba(245, 158, 11, 0.7), 0 0 35px rgba(245, 158, 11, 0.35)",
|
|
10028
|
+
svgStroke: "#F59E0B",
|
|
10029
|
+
badgeBg: "rgba(245, 158, 11, 0.95)",
|
|
10030
|
+
badgeColor: "#1F2937"
|
|
10031
|
+
};
|
|
10032
|
+
case "searching":
|
|
10033
|
+
default:
|
|
10034
|
+
return {
|
|
10035
|
+
borderColor: "rgba(156, 163, 175, 0.5)",
|
|
10036
|
+
boxShadow: "0 0 0 4px rgba(156, 163, 175, 0.3), 0 0 20px rgba(156, 163, 175, 0.15)",
|
|
10037
|
+
svgStroke: "#9CA3AF",
|
|
10038
|
+
badgeBg: "rgba(55, 65, 81, 0.9)",
|
|
10039
|
+
badgeColor: "#FFFFFF"
|
|
10040
|
+
};
|
|
10041
|
+
}
|
|
10042
|
+
}
|
|
10043
|
+
function CameraOvalGuide({
|
|
10044
|
+
stream,
|
|
10045
|
+
faceState,
|
|
10046
|
+
instruction,
|
|
10047
|
+
className = "",
|
|
10048
|
+
style = {}
|
|
10049
|
+
}) {
|
|
10050
|
+
const videoRef = useRef(null);
|
|
10051
|
+
useEffect(() => {
|
|
10052
|
+
const videoEl = videoRef.current;
|
|
10053
|
+
if (!videoEl) return;
|
|
10054
|
+
if (stream) {
|
|
10055
|
+
videoEl.srcObject = stream;
|
|
10056
|
+
videoEl.play().catch(() => {
|
|
10057
|
+
});
|
|
10058
|
+
} else {
|
|
10059
|
+
videoEl.srcObject = null;
|
|
10060
|
+
}
|
|
10061
|
+
return () => {
|
|
10062
|
+
if (stream) {
|
|
10063
|
+
stream.getTracks().forEach((track) => track.stop());
|
|
10064
|
+
}
|
|
10065
|
+
if (videoEl) {
|
|
10066
|
+
videoEl.srcObject = null;
|
|
10067
|
+
}
|
|
10068
|
+
};
|
|
10069
|
+
}, [stream]);
|
|
10070
|
+
const halo = getHaloStyles(faceState);
|
|
10071
|
+
return /* @__PURE__ */ jsxs(
|
|
10072
|
+
"div",
|
|
10073
|
+
{
|
|
10074
|
+
className: `neofaceid-camera-oval-guide-container ${className}`,
|
|
10075
|
+
style: {
|
|
10076
|
+
display: "flex",
|
|
10077
|
+
flexDirection: "column",
|
|
10078
|
+
alignItems: "center",
|
|
10079
|
+
justifyContent: "center",
|
|
10080
|
+
position: "relative",
|
|
10081
|
+
width: "100%",
|
|
10082
|
+
maxHeight: "100%",
|
|
10083
|
+
padding: "16px",
|
|
10084
|
+
boxSizing: "border-box",
|
|
10085
|
+
...style
|
|
10086
|
+
},
|
|
10087
|
+
children: [
|
|
10088
|
+
/* @__PURE__ */ jsx("style", { children: `
|
|
10089
|
+
.neofaceid-oval-frame {
|
|
10090
|
+
width: 320px;
|
|
10091
|
+
height: 400px;
|
|
10092
|
+
border-radius: 50%;
|
|
10093
|
+
overflow: hidden;
|
|
10094
|
+
position: relative;
|
|
10095
|
+
background: #000000;
|
|
10096
|
+
transition: box-shadow 0.3s ease, border-color 0.3s ease;
|
|
10097
|
+
}
|
|
10098
|
+
|
|
10099
|
+
.neofaceid-oval-video {
|
|
10100
|
+
width: 100%;
|
|
10101
|
+
height: 100%;
|
|
10102
|
+
object-fit: cover;
|
|
10103
|
+
transform: scaleX(-1);
|
|
10104
|
+
display: block;
|
|
10105
|
+
}
|
|
10106
|
+
|
|
10107
|
+
.neofaceid-oval-svg-overlay {
|
|
10108
|
+
position: absolute;
|
|
10109
|
+
inset: 0;
|
|
10110
|
+
width: 100%;
|
|
10111
|
+
height: 100%;
|
|
10112
|
+
pointer-events: none;
|
|
10113
|
+
z-index: 2;
|
|
10114
|
+
}
|
|
10115
|
+
|
|
10116
|
+
.neofaceid-oval-instruction-badge {
|
|
10117
|
+
margin-top: 20px;
|
|
10118
|
+
padding: 10px 20px;
|
|
10119
|
+
border-radius: 20px;
|
|
10120
|
+
font-size: 15px;
|
|
10121
|
+
font-weight: 600;
|
|
10122
|
+
text-align: center;
|
|
10123
|
+
max-width: 320px;
|
|
10124
|
+
box-shadow: 0 4px 14px rgba(0, 0, 0, 0.25);
|
|
10125
|
+
backdrop-filter: blur(8px);
|
|
10126
|
+
transition: background-color 0.3s ease, color 0.3s ease;
|
|
10127
|
+
z-index: 5;
|
|
10128
|
+
}
|
|
10129
|
+
|
|
10130
|
+
@media (max-width: 640px) {
|
|
10131
|
+
.neofaceid-oval-frame {
|
|
10132
|
+
width: 280px;
|
|
10133
|
+
height: 350px;
|
|
10134
|
+
}
|
|
10135
|
+
}
|
|
10136
|
+
` }),
|
|
10137
|
+
/* @__PURE__ */ jsxs(
|
|
10138
|
+
"div",
|
|
10139
|
+
{
|
|
10140
|
+
className: "neofaceid-oval-frame",
|
|
10141
|
+
style: {
|
|
10142
|
+
boxShadow: halo.boxShadow,
|
|
10143
|
+
borderColor: halo.borderColor
|
|
10144
|
+
},
|
|
10145
|
+
children: [
|
|
10146
|
+
/* @__PURE__ */ jsx(
|
|
10147
|
+
"video",
|
|
10148
|
+
{
|
|
10149
|
+
ref: videoRef,
|
|
10150
|
+
className: "neofaceid-oval-video",
|
|
10151
|
+
autoPlay: true,
|
|
10152
|
+
playsInline: true,
|
|
10153
|
+
muted: true,
|
|
10154
|
+
"data-testid": "camera-oval-video"
|
|
10155
|
+
}
|
|
10156
|
+
),
|
|
10157
|
+
/* @__PURE__ */ jsx(
|
|
10158
|
+
"svg",
|
|
10159
|
+
{
|
|
10160
|
+
className: "neofaceid-oval-svg-overlay",
|
|
10161
|
+
viewBox: "0 0 320 400",
|
|
10162
|
+
fill: "none",
|
|
10163
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
10164
|
+
children: /* @__PURE__ */ jsx(
|
|
10165
|
+
"ellipse",
|
|
10166
|
+
{
|
|
10167
|
+
cx: "160",
|
|
10168
|
+
cy: "200",
|
|
10169
|
+
rx: "140",
|
|
10170
|
+
ry: "180",
|
|
10171
|
+
stroke: halo.svgStroke,
|
|
10172
|
+
strokeWidth: "3",
|
|
10173
|
+
strokeDasharray: faceState === "searching" ? "8 8" : "none",
|
|
10174
|
+
style: { transition: "stroke 0.3s ease" }
|
|
10175
|
+
}
|
|
10176
|
+
)
|
|
10177
|
+
}
|
|
10178
|
+
)
|
|
10179
|
+
]
|
|
10180
|
+
}
|
|
10181
|
+
),
|
|
10182
|
+
instruction && /* @__PURE__ */ jsx(
|
|
10183
|
+
"div",
|
|
9108
10184
|
{
|
|
9109
|
-
|
|
9110
|
-
|
|
9111
|
-
|
|
9112
|
-
|
|
10185
|
+
className: "neofaceid-oval-instruction-badge",
|
|
10186
|
+
"aria-live": "assertive",
|
|
10187
|
+
style: {
|
|
10188
|
+
backgroundColor: halo.badgeBg,
|
|
10189
|
+
color: halo.badgeColor
|
|
10190
|
+
},
|
|
10191
|
+
"data-testid": "camera-oval-instruction",
|
|
10192
|
+
children: instruction
|
|
9113
10193
|
}
|
|
9114
10194
|
)
|
|
9115
|
-
]
|
|
9116
|
-
/* @__PURE__ */ jsx("div", { className: "neofaceid-consent-footer", children: /* @__PURE__ */ jsx(BrandFooter, {}) })
|
|
9117
|
-
] }) })
|
|
9118
|
-
] });
|
|
9119
|
-
}
|
|
9120
|
-
class ConsentModal {
|
|
9121
|
-
constructor() {
|
|
9122
|
-
__publicField(this, "container", null);
|
|
9123
|
-
__publicField(this, "root", null);
|
|
9124
|
-
}
|
|
9125
|
-
show(props) {
|
|
9126
|
-
if (this.container) return;
|
|
9127
|
-
injectThemeStyles();
|
|
9128
|
-
this.container = document.createElement("div");
|
|
9129
|
-
this.container.id = "neofaceid-consent-modal-container";
|
|
9130
|
-
document.body.appendChild(this.container);
|
|
9131
|
-
this.root = createRoot(this.container);
|
|
9132
|
-
this.root.render(
|
|
9133
|
-
/* @__PURE__ */ jsx(
|
|
9134
|
-
ConsentModalComponent,
|
|
9135
|
-
{
|
|
9136
|
-
...props,
|
|
9137
|
-
onAccept: () => {
|
|
9138
|
-
this.close();
|
|
9139
|
-
props.onAccept();
|
|
9140
|
-
},
|
|
9141
|
-
onDecline: () => {
|
|
9142
|
-
this.close();
|
|
9143
|
-
props.onDecline();
|
|
9144
|
-
}
|
|
9145
|
-
}
|
|
9146
|
-
)
|
|
9147
|
-
);
|
|
9148
|
-
}
|
|
9149
|
-
close() {
|
|
9150
|
-
if (this.container && document.body.contains(this.container)) {
|
|
9151
|
-
if (this.root) this.root.unmount();
|
|
9152
|
-
document.body.removeChild(this.container);
|
|
9153
|
-
this.container = null;
|
|
9154
|
-
this.root = null;
|
|
10195
|
+
]
|
|
9155
10196
|
}
|
|
9156
|
-
|
|
9157
|
-
}
|
|
9158
|
-
const DEFAULT_APP_NAME = "sua aplicação";
|
|
9159
|
-
function requestConsent(info = {}) {
|
|
9160
|
-
const globalAppName = getAppName();
|
|
9161
|
-
const appName = info.appName ?? globalAppName ?? DEFAULT_APP_NAME;
|
|
9162
|
-
if (!info.appName && !globalAppName) {
|
|
9163
|
-
console.warn(
|
|
9164
|
-
'[NeoFaceID SDK] requestConsent sem appName — usando fallback "sua aplicação". Passe via NeoFaceSDK.init({ appName }) ou opts do caller.'
|
|
9165
|
-
);
|
|
9166
|
-
}
|
|
9167
|
-
const flow = info.flow ?? "verification";
|
|
9168
|
-
return new Promise((resolve) => {
|
|
9169
|
-
const modal = new ConsentModal();
|
|
9170
|
-
modal.show({
|
|
9171
|
-
appName,
|
|
9172
|
-
flow,
|
|
9173
|
-
privacyPolicyUrl: info.privacyPolicyUrl ?? DEFAULT_PRIVACY_URL,
|
|
9174
|
-
retentionDays: info.retentionDays ?? DEFAULT_RETENTION_DAYS,
|
|
9175
|
-
onAccept: () => resolve(true),
|
|
9176
|
-
onDecline: () => resolve(false)
|
|
9177
|
-
});
|
|
9178
|
-
});
|
|
10197
|
+
);
|
|
9179
10198
|
}
|
|
9180
|
-
const DEFAULT_CONSENT_INFO = {
|
|
9181
|
-
flow: "verification",
|
|
9182
|
-
privacyPolicyUrl: DEFAULT_PRIVACY_URL,
|
|
9183
|
-
retentionDays: DEFAULT_RETENTION_DAYS
|
|
9184
|
-
};
|
|
9185
10199
|
const DARK_THRESHOLD = 0.235;
|
|
9186
10200
|
const IMPROVEMENT_TARGET = 0.12;
|
|
9187
10201
|
const SAMPLE_INTERVAL_MS = 600;
|
|
@@ -10606,7 +11620,6 @@ const CAMERA_READY_DELAY = 200;
|
|
|
10606
11620
|
const CAMERA_STABILITY_DELAY = 100;
|
|
10607
11621
|
const FACE_DETECTION_TIME = 5e3;
|
|
10608
11622
|
const DETECTION_INTERVAL = 100;
|
|
10609
|
-
const SUCCESS_DELAY = 200;
|
|
10610
11623
|
let modelsLoaded = false;
|
|
10611
11624
|
let modelsLoading = null;
|
|
10612
11625
|
async function preloadFaceDetectionModels() {
|
|
@@ -10763,14 +11776,13 @@ async function captureFaceSilently() {
|
|
|
10763
11776
|
});
|
|
10764
11777
|
});
|
|
10765
11778
|
}
|
|
10766
|
-
async function
|
|
11779
|
+
async function detectFaceWithPosition(video, stream, guideModal, maxTime = FACE_DETECTION_TIME, interval = DETECTION_INTERVAL) {
|
|
10767
11780
|
return new Promise((resolve) => {
|
|
10768
11781
|
let attempts = 0;
|
|
10769
11782
|
const maxAttempts = Math.ceil(maxTime / interval);
|
|
10770
11783
|
let timeoutId = null;
|
|
10771
11784
|
let isResolved = false;
|
|
10772
|
-
let
|
|
10773
|
-
const ATTEMPTS_BEFORE_WAITING = Math.ceil(3e3 / interval);
|
|
11785
|
+
let centeredDurationMs = 0;
|
|
10774
11786
|
const finish = (success) => {
|
|
10775
11787
|
if (isResolved) return;
|
|
10776
11788
|
isResolved = true;
|
|
@@ -10781,11 +11793,7 @@ async function detectFaceQuickly(video, maxTime = FACE_DETECTION_TIME, interval
|
|
|
10781
11793
|
if (isResolved) return;
|
|
10782
11794
|
attempts++;
|
|
10783
11795
|
if (!modelsLoaded) {
|
|
10784
|
-
|
|
10785
|
-
overlay.updateStatus("waiting-for-face");
|
|
10786
|
-
overlayShowingWaiting = true;
|
|
10787
|
-
console.log("⏳ Models not loaded - waiting for face...");
|
|
10788
|
-
}
|
|
11796
|
+
guideModal.update(stream, "searching", "Carregando modelos de detecção...");
|
|
10789
11797
|
if (attempts >= maxAttempts) {
|
|
10790
11798
|
finish(false);
|
|
10791
11799
|
return;
|
|
@@ -10798,33 +11806,29 @@ async function detectFaceQuickly(video, maxTime = FACE_DETECTION_TIME, interval
|
|
|
10798
11806
|
video,
|
|
10799
11807
|
new faceapi.TinyFaceDetectorOptions({ inputSize: 160, scoreThreshold: 0.3 })
|
|
10800
11808
|
);
|
|
10801
|
-
|
|
10802
|
-
|
|
10803
|
-
|
|
10804
|
-
|
|
10805
|
-
|
|
11809
|
+
const { faceState, instruction } = evaluateFacePosition(
|
|
11810
|
+
detection ? detection.box : null,
|
|
11811
|
+
video.videoWidth,
|
|
11812
|
+
video.videoHeight
|
|
11813
|
+
);
|
|
11814
|
+
guideModal.update(stream, faceState, instruction);
|
|
11815
|
+
if (faceState === "centered") {
|
|
11816
|
+
centeredDurationMs += interval;
|
|
11817
|
+
if (centeredDurationMs >= 1e3) {
|
|
11818
|
+
console.log("✅ Rosto mantido centralizado por 1s consecutivo");
|
|
11819
|
+
finish(true);
|
|
11820
|
+
return;
|
|
10806
11821
|
}
|
|
10807
|
-
|
|
10808
|
-
|
|
10809
|
-
return;
|
|
10810
|
-
}
|
|
10811
|
-
if (overlay && !overlayShowingWaiting && attempts >= ATTEMPTS_BEFORE_WAITING) {
|
|
10812
|
-
overlay.updateStatus("waiting-for-face");
|
|
10813
|
-
overlayShowingWaiting = true;
|
|
10814
|
-
console.log("⏳ Waiting for face...");
|
|
11822
|
+
} else {
|
|
11823
|
+
centeredDurationMs = 0;
|
|
10815
11824
|
}
|
|
10816
11825
|
if (attempts >= maxAttempts) {
|
|
10817
|
-
console.warn("⏱️ Face detection timeout - no face found");
|
|
10818
11826
|
finish(false);
|
|
10819
11827
|
return;
|
|
10820
11828
|
}
|
|
10821
11829
|
timeoutId = window.setTimeout(checkFace, interval);
|
|
10822
11830
|
} catch (error) {
|
|
10823
|
-
console.error("❌
|
|
10824
|
-
if (overlay && !overlayShowingWaiting && attempts >= ATTEMPTS_BEFORE_WAITING) {
|
|
10825
|
-
overlay.updateStatus("waiting-for-face");
|
|
10826
|
-
overlayShowingWaiting = true;
|
|
10827
|
-
}
|
|
11831
|
+
console.error("❌ Erro na detecção facial:", error);
|
|
10828
11832
|
if (attempts >= maxAttempts) {
|
|
10829
11833
|
finish(false);
|
|
10830
11834
|
return;
|
|
@@ -10834,19 +11838,19 @@ async function detectFaceQuickly(video, maxTime = FACE_DETECTION_TIME, interval
|
|
|
10834
11838
|
};
|
|
10835
11839
|
window.setTimeout(() => {
|
|
10836
11840
|
if (!isResolved) {
|
|
10837
|
-
console.warn("⏱️ Face detection safety timeout reached");
|
|
10838
11841
|
finish(false);
|
|
10839
11842
|
}
|
|
10840
11843
|
}, maxTime + 500);
|
|
10841
11844
|
checkFace();
|
|
10842
11845
|
});
|
|
10843
11846
|
}
|
|
10844
|
-
async function attemptLogin(applicationToken,
|
|
10845
|
-
|
|
10846
|
-
|
|
10847
|
-
|
|
10848
|
-
|
|
10849
|
-
|
|
11847
|
+
async function attemptLogin(applicationToken, fastMode = false, isRetry = false, onCancel) {
|
|
11848
|
+
const guideModal = showCameraOvalGuideModal({
|
|
11849
|
+
stream: null,
|
|
11850
|
+
faceState: "searching",
|
|
11851
|
+
instruction: "Iniciando câmera...",
|
|
11852
|
+
onCancel
|
|
11853
|
+
});
|
|
10850
11854
|
const video = document.createElement("video");
|
|
10851
11855
|
video.style.position = "fixed";
|
|
10852
11856
|
video.style.top = "-9999px";
|
|
@@ -10877,6 +11881,7 @@ async function attemptLogin(applicationToken, overlay, fastMode = false, isRetry
|
|
|
10877
11881
|
});
|
|
10878
11882
|
}
|
|
10879
11883
|
video.srcObject = stream;
|
|
11884
|
+
guideModal.update(stream, "searching", "Centralize o rosto");
|
|
10880
11885
|
await new Promise((resolve) => {
|
|
10881
11886
|
if (video.readyState >= 1) {
|
|
10882
11887
|
resolve();
|
|
@@ -10905,18 +11910,19 @@ async function attemptLogin(applicationToken, overlay, fastMode = false, isRetry
|
|
|
10905
11910
|
checkSize();
|
|
10906
11911
|
}
|
|
10907
11912
|
});
|
|
10908
|
-
overlay.updateStatus("detecting");
|
|
10909
11913
|
await new Promise((resolve) => setTimeout(resolve, CAMERA_STABILITY_DELAY));
|
|
10910
11914
|
let faceDetected = true;
|
|
10911
11915
|
if (!fastMode) {
|
|
10912
|
-
faceDetected = await
|
|
11916
|
+
faceDetected = await detectFaceWithPosition(
|
|
10913
11917
|
video,
|
|
11918
|
+
stream,
|
|
11919
|
+
guideModal,
|
|
10914
11920
|
FACE_DETECTION_TIME,
|
|
10915
|
-
DETECTION_INTERVAL
|
|
10916
|
-
overlay
|
|
11921
|
+
DETECTION_INTERVAL
|
|
10917
11922
|
);
|
|
10918
11923
|
}
|
|
10919
11924
|
if (!faceDetected) {
|
|
11925
|
+
guideModal.close();
|
|
10920
11926
|
if (stream) {
|
|
10921
11927
|
stream.getTracks().forEach((track) => track.stop());
|
|
10922
11928
|
}
|
|
@@ -10924,16 +11930,17 @@ async function attemptLogin(applicationToken, overlay, fastMode = false, isRetry
|
|
|
10924
11930
|
video.parentElement.removeChild(video);
|
|
10925
11931
|
}
|
|
10926
11932
|
throw new NeoFaceError(
|
|
10927
|
-
"Nenhum rosto detectado. Por favor,
|
|
11933
|
+
"Nenhum rosto centralizado detectado. Por favor, tente novamente.",
|
|
10928
11934
|
ErrorType.VALIDATION_ERROR
|
|
10929
11935
|
);
|
|
10930
11936
|
}
|
|
10931
|
-
|
|
11937
|
+
guideModal.update(stream, "centered", "Capturando imagem...");
|
|
10932
11938
|
const canvas = document.createElement("canvas");
|
|
10933
11939
|
canvas.width = video.videoWidth;
|
|
10934
11940
|
canvas.height = video.videoHeight;
|
|
10935
11941
|
const ctx = canvas.getContext("2d");
|
|
10936
11942
|
if (!ctx) {
|
|
11943
|
+
guideModal.close();
|
|
10937
11944
|
if (stream) {
|
|
10938
11945
|
stream.getTracks().forEach((track) => track.stop());
|
|
10939
11946
|
}
|
|
@@ -10943,12 +11950,6 @@ async function attemptLogin(applicationToken, overlay, fastMode = false, isRetry
|
|
|
10943
11950
|
throw new NeoFaceError("Erro ao capturar imagem", ErrorType.CAPTURE_ERROR);
|
|
10944
11951
|
}
|
|
10945
11952
|
ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
|
|
10946
|
-
if (stream) {
|
|
10947
|
-
stream.getTracks().forEach((track) => track.stop());
|
|
10948
|
-
}
|
|
10949
|
-
if (video.parentElement) {
|
|
10950
|
-
video.parentElement.removeChild(video);
|
|
10951
|
-
}
|
|
10952
11953
|
const imageBlob = await new Promise((resolve, reject) => {
|
|
10953
11954
|
canvas.toBlob(
|
|
10954
11955
|
(blob) => {
|
|
@@ -10963,13 +11964,21 @@ async function attemptLogin(applicationToken, overlay, fastMode = false, isRetry
|
|
|
10963
11964
|
0.85
|
|
10964
11965
|
);
|
|
10965
11966
|
});
|
|
10966
|
-
|
|
11967
|
+
guideModal.update(stream, "centered", "Verificando com o servidor...");
|
|
10967
11968
|
const result = await loginWithBiometric(imageBlob, applicationToken);
|
|
11969
|
+
guideModal.close();
|
|
11970
|
+
if (stream) {
|
|
11971
|
+
stream.getTracks().forEach((track) => track.stop());
|
|
11972
|
+
}
|
|
11973
|
+
if (video.parentElement) {
|
|
11974
|
+
video.parentElement.removeChild(video);
|
|
11975
|
+
}
|
|
10968
11976
|
if (!result.success) {
|
|
10969
11977
|
throw new NeoFaceError("Login falhou", ErrorType.LOGIN_FAILED);
|
|
10970
11978
|
}
|
|
10971
11979
|
return result;
|
|
10972
11980
|
} catch (error) {
|
|
11981
|
+
guideModal.close();
|
|
10973
11982
|
if (stream) {
|
|
10974
11983
|
stream.getTracks().forEach((track) => track.stop());
|
|
10975
11984
|
}
|
|
@@ -10994,7 +12003,6 @@ async function executeBiometricLoginFlow(options) {
|
|
|
10994
12003
|
onCancel,
|
|
10995
12004
|
fastMode = false
|
|
10996
12005
|
} = options;
|
|
10997
|
-
const overlay = new BiometricStatusOverlay();
|
|
10998
12006
|
const fallbackPrompt = new FallbackPrompt();
|
|
10999
12007
|
let attempts = 0;
|
|
11000
12008
|
let lastError;
|
|
@@ -11002,34 +12010,26 @@ async function executeBiometricLoginFlow(options) {
|
|
|
11002
12010
|
preloadFaceDetectionModels().catch(() => {
|
|
11003
12011
|
});
|
|
11004
12012
|
}
|
|
11005
|
-
overlay.show("preparing");
|
|
11006
12013
|
const tryLogin = async () => {
|
|
11007
12014
|
try {
|
|
11008
12015
|
attempts++;
|
|
11009
|
-
const result = await attemptLogin(applicationToken,
|
|
11010
|
-
|
|
11011
|
-
setTimeout(() => {
|
|
11012
|
-
overlay.close();
|
|
11013
|
-
onSuccess(result);
|
|
11014
|
-
}, SUCCESS_DELAY);
|
|
12016
|
+
const result = await attemptLogin(applicationToken, fastMode, attempts > 1, onCancel);
|
|
12017
|
+
onSuccess(result);
|
|
11015
12018
|
} catch (error) {
|
|
11016
12019
|
lastError = error instanceof NeoFaceError ? error : new NeoFaceError(
|
|
11017
12020
|
error instanceof Error ? error.message : "Erro desconhecido",
|
|
11018
12021
|
ErrorType.UNKNOWN
|
|
11019
12022
|
);
|
|
11020
|
-
overlay.updateStatus("error");
|
|
11021
12023
|
if (attempts < MAX_ATTEMPTS$1) {
|
|
11022
12024
|
setTimeout(() => {
|
|
11023
12025
|
tryLogin();
|
|
11024
12026
|
}, RETRY_DELAY);
|
|
11025
12027
|
} else {
|
|
11026
12028
|
setTimeout(() => {
|
|
11027
|
-
overlay.close();
|
|
11028
12029
|
fallbackPrompt.show(
|
|
11029
12030
|
lastError,
|
|
11030
12031
|
() => {
|
|
11031
12032
|
attempts = 0;
|
|
11032
|
-
overlay.show("preparing");
|
|
11033
12033
|
setTimeout(() => tryLogin(), RETRY_DELAY);
|
|
11034
12034
|
},
|
|
11035
12035
|
() => {
|
|
@@ -11051,9 +12051,7 @@ async function executeBiometricLoginFlow(options) {
|
|
|
11051
12051
|
}
|
|
11052
12052
|
}
|
|
11053
12053
|
};
|
|
11054
|
-
|
|
11055
|
-
tryLogin();
|
|
11056
|
-
}
|
|
12054
|
+
tryLogin();
|
|
11057
12055
|
}
|
|
11058
12056
|
const biometricLoginFlow = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
|
|
11059
12057
|
__proto__: null,
|
|
@@ -11062,6 +12060,25 @@ const biometricLoginFlow = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.
|
|
|
11062
12060
|
preloadFaceDetectionModels
|
|
11063
12061
|
}, Symbol.toStringTag, { value: "Module" }));
|
|
11064
12062
|
const CONSENT_DENIED_MESSAGE$1 = "Consentimento recusado pelo usuário.";
|
|
12063
|
+
function openPasswordFallback(options) {
|
|
12064
|
+
if (!getAutoFallback().toPassword) {
|
|
12065
|
+
options.onError(
|
|
12066
|
+
new NeoFaceError(
|
|
12067
|
+
"Biometria não reconhecida e a queda automática para senha está desligada.",
|
|
12068
|
+
ErrorType.LOGIN_FAILED
|
|
12069
|
+
)
|
|
12070
|
+
);
|
|
12071
|
+
return;
|
|
12072
|
+
}
|
|
12073
|
+
const fallbackOptions = {
|
|
12074
|
+
applicationToken: options.applicationToken,
|
|
12075
|
+
onSuccess: options.onSuccess,
|
|
12076
|
+
onError: options.onError,
|
|
12077
|
+
title: "Login Alternativo",
|
|
12078
|
+
subtitle: "Biometria não reconhecida. Por favor, informe email e senha."
|
|
12079
|
+
};
|
|
12080
|
+
new EmailPasswordModal(fallbackOptions).open();
|
|
12081
|
+
}
|
|
11065
12082
|
async function startFaceLogin(options) {
|
|
11066
12083
|
const consentAccepted = await requestConsent({
|
|
11067
12084
|
appName: options.appName,
|
|
@@ -11076,17 +12093,7 @@ async function startFaceLogin(options) {
|
|
|
11076
12093
|
applicationToken: options.applicationToken,
|
|
11077
12094
|
onSuccess: options.onSuccess,
|
|
11078
12095
|
onError: options.onError,
|
|
11079
|
-
onFallbackRequest: options.onFallbackRequest || (() =>
|
|
11080
|
-
const fallbackOptions = {
|
|
11081
|
-
applicationToken: options.applicationToken,
|
|
11082
|
-
onSuccess: options.onSuccess,
|
|
11083
|
-
onError: options.onError,
|
|
11084
|
-
title: "Login Alternativo",
|
|
11085
|
-
subtitle: "Biometria não reconhecida. Por favor, informe email e senha."
|
|
11086
|
-
};
|
|
11087
|
-
const modal = new EmailPasswordModal(fallbackOptions);
|
|
11088
|
-
modal.open();
|
|
11089
|
-
}),
|
|
12096
|
+
onFallbackRequest: options.onFallbackRequest || (() => openPasswordFallback(options)),
|
|
11090
12097
|
onCancel: options.onCancel
|
|
11091
12098
|
});
|
|
11092
12099
|
} catch (error) {
|
|
@@ -12255,15 +13262,23 @@ const authorizeOperation = async (applicationToken, cpf, options) => {
|
|
|
12255
13262
|
resolve();
|
|
12256
13263
|
};
|
|
12257
13264
|
});
|
|
13265
|
+
const guideModal = showCameraOvalGuideModal({
|
|
13266
|
+
stream,
|
|
13267
|
+
faceState: "centered",
|
|
13268
|
+
instruction: PHOTO_INSTRUCTIONS[0]
|
|
13269
|
+
});
|
|
12258
13270
|
const canvas = document.createElement("canvas");
|
|
12259
13271
|
canvas.width = video.videoWidth;
|
|
12260
13272
|
canvas.height = video.videoHeight;
|
|
12261
13273
|
const ctx = canvas.getContext("2d");
|
|
12262
13274
|
if (!ctx) {
|
|
13275
|
+
guideModal.close();
|
|
13276
|
+
stream.getTracks().forEach((track) => track.stop());
|
|
12263
13277
|
throw new NeoFaceError("Failed to get canvas context", ErrorType.CAPTURE_ERROR);
|
|
12264
13278
|
}
|
|
12265
13279
|
for (let i = 0; i < TOTAL_PHOTOS; i++) {
|
|
12266
13280
|
const instruction = PHOTO_INSTRUCTIONS[i];
|
|
13281
|
+
guideModal.update(stream, "centered", instruction);
|
|
12267
13282
|
if (options == null ? void 0 : options.onProgress) {
|
|
12268
13283
|
options.onProgress(i + 1, TOTAL_PHOTOS, instruction);
|
|
12269
13284
|
}
|
|
@@ -12289,6 +13304,7 @@ const authorizeOperation = async (applicationToken, cpf, options) => {
|
|
|
12289
13304
|
options.onPhotoTaken(i + 1, blob);
|
|
12290
13305
|
}
|
|
12291
13306
|
}
|
|
13307
|
+
guideModal.close();
|
|
12292
13308
|
stream.getTracks().forEach((track) => track.stop());
|
|
12293
13309
|
const lastPhoto = capturedPhotos[TOTAL_PHOTOS - 1];
|
|
12294
13310
|
const sessionData = {
|
|
@@ -12336,110 +13352,6 @@ const authorizeOperation = async (applicationToken, cpf, options) => {
|
|
|
12336
13352
|
throw new NeoFaceError("Unknown error during authorization", ErrorType.UNKNOWN);
|
|
12337
13353
|
}
|
|
12338
13354
|
};
|
|
12339
|
-
function Sheet({
|
|
12340
|
-
open,
|
|
12341
|
-
onDismiss,
|
|
12342
|
-
dismissOnOverlay = true,
|
|
12343
|
-
ariaLabel,
|
|
12344
|
-
ariaLabelledBy,
|
|
12345
|
-
children,
|
|
12346
|
-
className,
|
|
12347
|
-
style
|
|
12348
|
-
}) {
|
|
12349
|
-
useEffect(() => {
|
|
12350
|
-
injectThemeStyles();
|
|
12351
|
-
}, []);
|
|
12352
|
-
if (!open) return null;
|
|
12353
|
-
const handleOverlay = () => {
|
|
12354
|
-
if (dismissOnOverlay && onDismiss) onDismiss();
|
|
12355
|
-
};
|
|
12356
|
-
return /* @__PURE__ */ jsx(
|
|
12357
|
-
"div",
|
|
12358
|
-
{
|
|
12359
|
-
className: "neofaceid-root",
|
|
12360
|
-
role: "presentation",
|
|
12361
|
-
style: {
|
|
12362
|
-
position: "fixed",
|
|
12363
|
-
inset: 0,
|
|
12364
|
-
zIndex: 10001,
|
|
12365
|
-
display: "flex",
|
|
12366
|
-
alignItems: "center",
|
|
12367
|
-
justifyContent: "center",
|
|
12368
|
-
padding: 20,
|
|
12369
|
-
background: `var(${CSS_VAR.overlay})`,
|
|
12370
|
-
backdropFilter: "blur(3px)",
|
|
12371
|
-
WebkitBackdropFilter: "blur(3px)"
|
|
12372
|
-
},
|
|
12373
|
-
onClick: handleOverlay,
|
|
12374
|
-
children: /* @__PURE__ */ jsx(
|
|
12375
|
-
"div",
|
|
12376
|
-
{
|
|
12377
|
-
role: "dialog",
|
|
12378
|
-
"aria-modal": "true",
|
|
12379
|
-
"aria-label": ariaLabel,
|
|
12380
|
-
"aria-labelledby": ariaLabelledBy,
|
|
12381
|
-
className,
|
|
12382
|
-
onClick: (e) => e.stopPropagation(),
|
|
12383
|
-
style: {
|
|
12384
|
-
background: `var(${CSS_VAR.surface})`,
|
|
12385
|
-
color: `var(${CSS_VAR.text})`,
|
|
12386
|
-
borderRadius: `var(${CSS_VAR.radius}, 16px)`,
|
|
12387
|
-
padding: 28,
|
|
12388
|
-
maxWidth: 440,
|
|
12389
|
-
width: "100%",
|
|
12390
|
-
boxShadow: "0 1px 3px rgba(10,19,32,.08)",
|
|
12391
|
-
boxSizing: "border-box",
|
|
12392
|
-
...style
|
|
12393
|
-
},
|
|
12394
|
-
children
|
|
12395
|
-
}
|
|
12396
|
-
)
|
|
12397
|
-
}
|
|
12398
|
-
);
|
|
12399
|
-
}
|
|
12400
|
-
const BASE_STYLE = {
|
|
12401
|
-
minHeight: 44,
|
|
12402
|
-
minWidth: 44,
|
|
12403
|
-
padding: "12px 20px",
|
|
12404
|
-
borderRadius: 16,
|
|
12405
|
-
border: "none",
|
|
12406
|
-
fontSize: 15,
|
|
12407
|
-
fontWeight: 600,
|
|
12408
|
-
fontFamily: "inherit",
|
|
12409
|
-
cursor: "pointer",
|
|
12410
|
-
transition: "background 120ms ease-out",
|
|
12411
|
-
display: "inline-flex",
|
|
12412
|
-
alignItems: "center",
|
|
12413
|
-
justifyContent: "center",
|
|
12414
|
-
gap: 8
|
|
12415
|
-
};
|
|
12416
|
-
const Button = forwardRef(
|
|
12417
|
-
({ variant = "primary", style, type = "button", ...rest }, ref) => {
|
|
12418
|
-
const variantStyle = variant === "primary" ? {
|
|
12419
|
-
background: `var(${CSS_VAR.action})`,
|
|
12420
|
-
color: `var(${CSS_VAR.actionText})`
|
|
12421
|
-
} : variant === "secondary" ? {
|
|
12422
|
-
background: `var(${CSS_VAR.surfaceMuted})`,
|
|
12423
|
-
color: `var(${CSS_VAR.text})`
|
|
12424
|
-
} : {
|
|
12425
|
-
background: "transparent",
|
|
12426
|
-
color: `var(${CSS_VAR.textMuted})`
|
|
12427
|
-
};
|
|
12428
|
-
return (
|
|
12429
|
-
// eslint-disable-next-line react/button-has-type
|
|
12430
|
-
/* @__PURE__ */ jsx(
|
|
12431
|
-
"button",
|
|
12432
|
-
{
|
|
12433
|
-
ref,
|
|
12434
|
-
type,
|
|
12435
|
-
...rest,
|
|
12436
|
-
style: { ...BASE_STYLE, ...variantStyle, ...style }
|
|
12437
|
-
}
|
|
12438
|
-
)
|
|
12439
|
-
);
|
|
12440
|
-
}
|
|
12441
|
-
);
|
|
12442
|
-
Button.displayName = "Button";
|
|
12443
13355
|
function FocusFrame({
|
|
12444
13356
|
size = 96,
|
|
12445
13357
|
color,
|
|
@@ -12667,32 +13579,6 @@ function pointsFor(g) {
|
|
|
12667
13579
|
return ["24 24", "24 24", "24 24"];
|
|
12668
13580
|
}
|
|
12669
13581
|
}
|
|
12670
|
-
function StatusPill({ tone, children, icon, className }) {
|
|
12671
|
-
const palette = SEMANTIC[tone];
|
|
12672
|
-
return /* @__PURE__ */ jsxs(
|
|
12673
|
-
"span",
|
|
12674
|
-
{
|
|
12675
|
-
className,
|
|
12676
|
-
style: {
|
|
12677
|
-
display: "inline-flex",
|
|
12678
|
-
alignItems: "center",
|
|
12679
|
-
gap: 6,
|
|
12680
|
-
padding: "4px 10px",
|
|
12681
|
-
borderRadius: 9999,
|
|
12682
|
-
background: palette.bg,
|
|
12683
|
-
color: palette.fg,
|
|
12684
|
-
fontSize: 13,
|
|
12685
|
-
fontWeight: 600,
|
|
12686
|
-
fontFamily: "inherit",
|
|
12687
|
-
lineHeight: 1.3
|
|
12688
|
-
},
|
|
12689
|
-
children: [
|
|
12690
|
-
icon,
|
|
12691
|
-
children
|
|
12692
|
-
]
|
|
12693
|
-
}
|
|
12694
|
-
);
|
|
12695
|
-
}
|
|
12696
13582
|
const DEFAULT = {
|
|
12697
13583
|
title: "Não deu certo desta vez",
|
|
12698
13584
|
hint: "Tente se posicionar em um ambiente mais estável.",
|
|
@@ -14313,7 +15199,7 @@ const CONSENT_DENIED_MESSAGE = "Consentimento recusado pelo usuário.";
|
|
|
14313
15199
|
function start(applicationToken, callbacks, options) {
|
|
14314
15200
|
requestConsent({ appName: options == null ? void 0 : options.appName, flow: "verification" }).then((accepted) => {
|
|
14315
15201
|
if (!accepted) {
|
|
14316
|
-
callbacks.onError(
|
|
15202
|
+
callbacks.onError(new NeoFaceError(CONSENT_DENIED_MESSAGE, ErrorType.CONSENT_DENIED));
|
|
14317
15203
|
return;
|
|
14318
15204
|
}
|
|
14319
15205
|
const container = document.createElement("div");
|
|
@@ -14342,14 +15228,16 @@ function start(applicationToken, callbacks, options) {
|
|
|
14342
15228
|
root.render(modalElement);
|
|
14343
15229
|
}).catch((error) => {
|
|
14344
15230
|
cleanup();
|
|
14345
|
-
callbacks.onError(
|
|
15231
|
+
callbacks.onError(
|
|
15232
|
+
error instanceof NeoFaceError ? error : new NeoFaceError(error.message || "Failed to validate token", ErrorType.INVALID_TOKEN)
|
|
15233
|
+
);
|
|
14346
15234
|
});
|
|
14347
15235
|
});
|
|
14348
15236
|
}
|
|
14349
15237
|
function startBiometricRegistration(personData, applicationToken, callbacks, options) {
|
|
14350
15238
|
requestConsent({ appName: options == null ? void 0 : options.appName, flow: "registration" }).then((accepted) => {
|
|
14351
15239
|
if (!accepted) {
|
|
14352
|
-
callbacks.onError(
|
|
15240
|
+
callbacks.onError(new NeoFaceError(CONSENT_DENIED_MESSAGE, ErrorType.CONSENT_DENIED));
|
|
14353
15241
|
return;
|
|
14354
15242
|
}
|
|
14355
15243
|
const container = document.createElement("div");
|
|
@@ -14377,7 +15265,7 @@ function startBiometricRegistration(personData, applicationToken, callbacks, opt
|
|
|
14377
15265
|
},
|
|
14378
15266
|
onError: (error) => {
|
|
14379
15267
|
cleanup();
|
|
14380
|
-
callbacks.onError(
|
|
15268
|
+
callbacks.onError(new NeoFaceError(error, ErrorType.UNKNOWN));
|
|
14381
15269
|
},
|
|
14382
15270
|
onCannotGesture: options == null ? void 0 : options.onCannotGesture
|
|
14383
15271
|
});
|
|
@@ -14385,14 +15273,16 @@ function startBiometricRegistration(personData, applicationToken, callbacks, opt
|
|
|
14385
15273
|
rootRef.render(modalElement);
|
|
14386
15274
|
}).catch((error) => {
|
|
14387
15275
|
cleanup();
|
|
14388
|
-
callbacks.onError(
|
|
15276
|
+
callbacks.onError(
|
|
15277
|
+
error instanceof NeoFaceError ? error : new NeoFaceError(error.message || "Failed to validate token", ErrorType.INVALID_TOKEN)
|
|
15278
|
+
);
|
|
14389
15279
|
});
|
|
14390
15280
|
});
|
|
14391
15281
|
}
|
|
14392
15282
|
function startLivenessCapture(applicationToken, callbacks, options) {
|
|
14393
15283
|
requestConsent({ appName: options == null ? void 0 : options.appName, flow: "verification" }).then((accepted) => {
|
|
14394
15284
|
if (!accepted) {
|
|
14395
|
-
callbacks.onError(
|
|
15285
|
+
callbacks.onError(new NeoFaceError(CONSENT_DENIED_MESSAGE, ErrorType.CONSENT_DENIED));
|
|
14396
15286
|
return;
|
|
14397
15287
|
}
|
|
14398
15288
|
const container = document.createElement("div");
|
|
@@ -14425,7 +15315,7 @@ function startLivenessCapture(applicationToken, callbacks, options) {
|
|
|
14425
15315
|
},
|
|
14426
15316
|
onError: (error) => {
|
|
14427
15317
|
cleanup();
|
|
14428
|
-
callbacks.onError(
|
|
15318
|
+
callbacks.onError(new NeoFaceError(error, ErrorType.UNKNOWN));
|
|
14429
15319
|
},
|
|
14430
15320
|
onCannotGesture: options == null ? void 0 : options.onCannotGesture
|
|
14431
15321
|
});
|
|
@@ -14433,7 +15323,9 @@ function startLivenessCapture(applicationToken, callbacks, options) {
|
|
|
14433
15323
|
rootRef.render(modalElement);
|
|
14434
15324
|
}).catch((error) => {
|
|
14435
15325
|
cleanup();
|
|
14436
|
-
callbacks.onError(
|
|
15326
|
+
callbacks.onError(
|
|
15327
|
+
error instanceof NeoFaceError ? error : new NeoFaceError(error.message || "Failed to validate token", ErrorType.INVALID_TOKEN)
|
|
15328
|
+
);
|
|
14437
15329
|
});
|
|
14438
15330
|
});
|
|
14439
15331
|
}
|
|
@@ -14441,6 +15333,7 @@ export {
|
|
|
14441
15333
|
BiometricCaptureModal,
|
|
14442
15334
|
BiometricRegistrationModal,
|
|
14443
15335
|
BiometricStatusOverlay,
|
|
15336
|
+
CameraOvalGuide,
|
|
14444
15337
|
ConsentModal,
|
|
14445
15338
|
DEFAULT_CONSENT_INFO,
|
|
14446
15339
|
DocumentCaptureModal,
|
|
@@ -14456,15 +15349,18 @@ export {
|
|
|
14456
15349
|
VERSION,
|
|
14457
15350
|
authorize,
|
|
14458
15351
|
authorizeOperation,
|
|
15352
|
+
awaitPushApproval,
|
|
14459
15353
|
checkUserExistence,
|
|
14460
15354
|
clearConsentTrail,
|
|
14461
15355
|
completeOnboarding,
|
|
14462
15356
|
completeOnboardingWithData,
|
|
14463
15357
|
confirmPasswordReset,
|
|
15358
|
+
consumeSseStream,
|
|
14464
15359
|
detectBiometricType,
|
|
14465
15360
|
getAccent,
|
|
14466
15361
|
getAppName,
|
|
14467
15362
|
getApplicationToken,
|
|
15363
|
+
getAutoFallback,
|
|
14468
15364
|
getBaseUrl,
|
|
14469
15365
|
getCachedCaptureSession,
|
|
14470
15366
|
getConfig,
|
|
@@ -14489,10 +15385,12 @@ export {
|
|
|
14489
15385
|
recognizeBiometric,
|
|
14490
15386
|
recognizeByPurpose,
|
|
14491
15387
|
recordConsent,
|
|
15388
|
+
refreshSession,
|
|
14492
15389
|
registerBiometric,
|
|
14493
15390
|
registerPersonWithBiometric,
|
|
14494
15391
|
registerPersonWithoutFace,
|
|
14495
15392
|
requestConsent,
|
|
15393
|
+
requestDeviceEnrollmentToken,
|
|
14496
15394
|
requestLivenessChallenge,
|
|
14497
15395
|
requestLivenessChallengeWithSession,
|
|
14498
15396
|
requestPasswordReset,
|