@casperid/react 1.0.0 → 1.1.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.
Files changed (43) hide show
  1. package/README.md +1 -1
  2. package/dist/_commonjsHelpers-DKOUU3wS.cjs +2 -0
  3. package/dist/_commonjsHelpers-DKOUU3wS.cjs.map +1 -0
  4. package/dist/_commonjsHelpers-DaMA6jEr.js +9 -0
  5. package/dist/_commonjsHelpers-DaMA6jEr.js.map +1 -0
  6. package/dist/camera_utils-BQaOSBPu.js +397 -0
  7. package/dist/camera_utils-BQaOSBPu.js.map +1 -0
  8. package/dist/camera_utils-wchtqrQn.cjs +2 -0
  9. package/dist/camera_utils-wchtqrQn.cjs.map +1 -0
  10. package/dist/face_mesh-DYMPc5Ce.js +4212 -0
  11. package/dist/face_mesh-DYMPc5Ce.js.map +1 -0
  12. package/dist/face_mesh-fEvyDoPt.cjs +19 -0
  13. package/dist/face_mesh-fEvyDoPt.cjs.map +1 -0
  14. package/dist/index.cjs +41 -41
  15. package/dist/index.cjs.map +1 -1
  16. package/dist/index.mjs +1786 -1501
  17. package/dist/index.mjs.map +1 -1
  18. package/dist/style.css +1 -1
  19. package/package.json +23 -10
  20. package/src/CasperIDModal.tsx +0 -777
  21. package/src/index.css +0 -217
  22. package/src/index.ts +0 -41
  23. package/src/screens/AuthSelection.tsx +0 -221
  24. package/src/screens/DocumentScan.tsx +0 -348
  25. package/src/screens/FaceScan.tsx +0 -368
  26. package/src/screens/IdentityVerified.tsx +0 -51
  27. package/src/screens/L1Setup.tsx +0 -335
  28. package/src/screens/Login.tsx +0 -186
  29. package/src/screens/MintingIdentity.tsx +0 -446
  30. package/src/screens/PasskeyAuth.tsx +0 -259
  31. package/src/screens/PasskeyRegister.tsx +0 -281
  32. package/src/screens/PermissionsRequest.tsx +0 -96
  33. package/src/screens/PinVerification.tsx +0 -321
  34. package/src/screens/ReviewData.tsx +0 -315
  35. package/src/screens/SecurityUpgrade.tsx +0 -83
  36. package/src/screens/VerifyIdentityChoice.tsx +0 -59
  37. package/src/shared/Footer.tsx +0 -13
  38. package/src/shared/GlassContainer.tsx +0 -17
  39. package/src/shared/Header.tsx +0 -62
  40. package/src/types.ts +0 -342
  41. package/src/utils/fetchWithTimeout.ts +0 -52
  42. package/src/utils/i18n.ts +0 -640
  43. package/src/utils/webauthn.ts +0 -261
@@ -1,368 +0,0 @@
1
- import React, { useEffect, useRef, useState, useCallback } from 'react';
2
- import { motion, AnimatePresence } from 'framer-motion';
3
- import { Lock, Camera, AlertCircle, Loader2 } from 'lucide-react';
4
- import type { LivenessResponse } from '../types';
5
-
6
- import { t } from '../utils/i18n';
7
-
8
- interface FaceScanProps {
9
- requestId: string;
10
- onNext: (livenessVerified: boolean) => Promise<void>;
11
- onError?: (error: string) => void;
12
- apiBaseUrl?: string;
13
- previewMode?: boolean;
14
- language?: string;
15
- }
16
-
17
- const instructions = [
18
- 'position_face',
19
- 'blink_slowly',
20
- 'turn_head_left',
21
- 'turn_head_right',
22
- 'processing',
23
- ];
24
-
25
- const FaceScan: React.FC<FaceScanProps> = ({
26
- requestId,
27
- onNext,
28
- onError,
29
- apiBaseUrl = 'https://apis.casperid.com',
30
- previewMode = false,
31
- language = 'EN'
32
- }) => {
33
- const videoRef = useRef<HTMLVideoElement>(null);
34
- const canvasRef = useRef<HTMLCanvasElement>(null);
35
- const streamRef = useRef<MediaStream | null>(null);
36
- const framesRef = useRef<Blob[]>([]);
37
-
38
- const [step, setStep] = useState(0);
39
- const [cameraReady, setCameraReady] = useState(false);
40
- const [error, setError] = useState('');
41
- const [isProcessing, setIsProcessing] = useState(false);
42
- const [cameraPermissionDenied, setCameraPermissionDenied] = useState(false);
43
-
44
- // Initialize camera
45
- useEffect(() => {
46
- const initCamera = async () => {
47
- try {
48
- const stream = await navigator.mediaDevices.getUserMedia({
49
- video: { facingMode: 'user', width: 640, height: 480 }
50
- });
51
- streamRef.current = stream;
52
- if (videoRef.current) {
53
- videoRef.current.srcObject = stream;
54
- videoRef.current.onloadedmetadata = () => {
55
- setCameraReady(true);
56
- };
57
- }
58
- } catch (err: any) {
59
- if (err.name === 'NotAllowedError') {
60
- setCameraPermissionDenied(true);
61
- setError(t('camera_permission_denied', language));
62
- } else {
63
- setError(t('unable_access_camera', language));
64
- }
65
- onError?.('Camera access failed');
66
- }
67
- };
68
-
69
- initCamera();
70
-
71
- return () => {
72
- if (streamRef.current) {
73
- streamRef.current.getTracks().forEach(track => track.stop());
74
- }
75
- };
76
- }, [onError]);
77
-
78
- // Capture frame from video
79
- const captureFrame = useCallback((): Blob | null => {
80
- if (!videoRef.current || !canvasRef.current) return null;
81
-
82
- const canvas = canvasRef.current;
83
- const video = videoRef.current;
84
- const ctx = canvas.getContext('2d');
85
-
86
- if (!ctx) return null;
87
-
88
- canvas.width = video.videoWidth;
89
- canvas.height = video.videoHeight;
90
- ctx.drawImage(video, 0, 0);
91
-
92
- return new Promise((resolve) => {
93
- canvas.toBlob((blob) => resolve(blob), 'image/jpeg', 0.8);
94
- }) as unknown as Blob;
95
- }, []);
96
-
97
- // Progress through instructions and capture frames
98
- useEffect(() => {
99
- if (!cameraReady || isProcessing) return;
100
-
101
- const captureAndAdvance = async () => {
102
- // Capture frame at each step
103
- const frame = await new Promise<Blob | null>((resolve) => {
104
- if (!videoRef.current || !canvasRef.current) {
105
- resolve(null);
106
- return;
107
- }
108
- const canvas = canvasRef.current;
109
- const video = videoRef.current;
110
- const ctx = canvas.getContext('2d');
111
- if (!ctx) {
112
- resolve(null);
113
- return;
114
- }
115
- canvas.width = video.videoWidth;
116
- canvas.height = video.videoHeight;
117
- ctx.drawImage(video, 0, 0);
118
- canvas.toBlob((blob) => resolve(blob), 'image/jpeg', 0.8);
119
- });
120
-
121
- if (frame) {
122
- framesRef.current.push(frame);
123
- }
124
-
125
- if (step < instructions.length - 1) {
126
- setStep(prev => prev + 1);
127
- } else {
128
- // Last step - submit for verification
129
- submitLiveness();
130
- }
131
- };
132
-
133
- const timer = setTimeout(captureAndAdvance, 2500);
134
- return () => clearTimeout(timer);
135
- }, [step, cameraReady, isProcessing]);
136
-
137
- // Submit liveness check
138
- const submitLiveness = async () => {
139
- setIsProcessing(true);
140
- setStep(4); // "Processing..."
141
-
142
- if (previewMode) {
143
- setTimeout(() => {
144
- setIsProcessing(false);
145
- onNext(true);
146
- }, 2000);
147
- return;
148
- }
149
-
150
- try {
151
- const formData = new FormData();
152
- formData.append('requestId', requestId);
153
-
154
- // Add captured frames
155
- framesRef.current.forEach((frame, index) => {
156
- formData.append('imageFrames', frame, `frame_${index}.jpg`);
157
- });
158
-
159
- formData.append('metadata', JSON.stringify({
160
- capturedAt: new Date().toISOString(),
161
- frameCount: framesRef.current.length,
162
- instructions: instructions.slice(0, -1)
163
- }));
164
-
165
- const response = await fetch(`${apiBaseUrl}/api/v2/verification/liveness`, {
166
- method: 'POST',
167
- body: formData,
168
- credentials: 'include'
169
- });
170
-
171
- if (!response.ok) {
172
- const errorData = await response.json().catch(() => ({}));
173
- throw new Error(errorData.error || `Liveness check failed with status ${response.status}`);
174
- }
175
-
176
- const data: LivenessResponse = await response.json();
177
-
178
- if (data.success && data.liveness?.passed) {
179
- // Stop camera
180
- if (streamRef.current) {
181
- streamRef.current.getTracks().forEach(track => track.stop());
182
- }
183
- onNext(true);
184
- } else {
185
- setError(data.error || 'Liveness check failed. Please try again.');
186
- // Reset for retry
187
- setStep(0);
188
- framesRef.current = [];
189
- setIsProcessing(false);
190
- }
191
- } catch (err) {
192
- setError('Verification failed. Please try again.');
193
- setStep(0);
194
- framesRef.current = [];
195
- setIsProcessing(false);
196
- onError?.('Liveness submission failed');
197
- }
198
- };
199
-
200
- // Retry handler
201
- const handleRetry = () => {
202
- setError('');
203
- setStep(0);
204
- framesRef.current = [];
205
- setIsProcessing(false);
206
- };
207
-
208
- if (cameraPermissionDenied) {
209
- return (
210
- <motion.div
211
- initial={{ opacity: 0 }}
212
- animate={{ opacity: 1 }}
213
- className="flex-1 flex flex-col items-center justify-center px-8 text-center"
214
- >
215
- <AlertCircle className="w-16 h-16 text-red-500 mb-6" />
216
- <h2 className="text-xl font-bold text-main mb-3">Camera Access Required</h2>
217
- <p className="text-muted text-sm mb-6">
218
- Please enable camera access in your browser settings to continue with face verification.
219
- </p>
220
- <button
221
- onClick={() => window.location.reload()}
222
- className="px-6 py-3 bg-brand text-white rounded-2xl font-bold"
223
- >
224
- Try Again
225
- </button>
226
- </motion.div >
227
- );
228
- }
229
-
230
- return (
231
- <motion.div
232
- initial={{ opacity: 0 }}
233
- animate={{ opacity: 1 }}
234
- exit={{ opacity: 0 }}
235
- className="flex-1 flex flex-col relative dark:bg-[#050a0a] bg-slate-50 overflow-hidden"
236
- >
237
- {/* Hidden canvas for frame capture */}
238
- <canvas ref={canvasRef} className="hidden" />
239
-
240
- {/* Ambient glow */}
241
- <div className="absolute inset-0 z-0">
242
- <div className="w-full h-full opacity-20 bg-[radial-gradient(circle_at_center,_var(--tw-gradient-stops))] from-[#6fe8ec]/30 via-transparent to-transparent" />
243
- </div>
244
-
245
- {/* Top badge */}
246
- <div className="relative z-40 pt-12 pb-4 flex items-center justify-center">
247
- <div className="flex items-center gap-2 px-4 py-1.5 dark:bg-black/40 bg-white/40 backdrop-blur-md rounded-xl border dark:border-white/5 border-black/5">
248
- <div className={`w-2 h-2 rounded-full ${cameraReady ? 'bg-[#6fe8ec] shadow-[0_0_8px_#6fe8ec]' : 'bg-yellow-500 animate-pulse'}`} />
249
- <span className="text-[10px] text-muted uppercase tracking-[0.2em] font-bold">
250
- {cameraReady ? t('live_camera', language) : t('initializing', language)}
251
- </span>
252
- </div>
253
- </div>
254
-
255
- {/* Scan area with video */}
256
- <div className="relative z-20 flex-1 flex items-center justify-center">
257
- {/* Frosted mask with oval hole */}
258
- <div
259
- className="absolute inset-0 z-10 dark:bg-[#050a0a]/80 bg-white/80 backdrop-blur-[2px]"
260
- style={{
261
- mask: 'radial-gradient(ellipse 100px 145px at center, transparent 99%, black 100%)',
262
- WebkitMask: 'radial-gradient(ellipse 100px 145px at center, transparent 99%, black 100%)',
263
- }}
264
- />
265
-
266
- <div className="relative z-30 flex items-center justify-center">
267
- {/* Oval biometric frame with video */}
268
- <div className="w-[200px] h-[290px] flex-shrink-0 rounded-full border-[3px] border-[#6fe8ec] shadow-[0_0_40px_rgba(111,232,236,0.4),inset_0_0_20px_rgba(111,232,236,0.2)] relative overflow-hidden">
269
- <video
270
- ref={videoRef}
271
- autoPlay
272
- playsInline
273
- muted
274
- className="absolute inset-0 w-full h-full object-cover scale-x-[-1]"
275
- />
276
- <motion.div
277
- animate={{ opacity: [0.4, 1, 0.4] }}
278
- transition={{ duration: 2, repeat: Infinity }}
279
- className="absolute inset-0 rounded-full border-[1px] border-white/30"
280
- />
281
- <motion.div
282
- animate={{ top: ['-10%', '110%'] }}
283
- transition={{ duration: 3, repeat: Infinity, ease: 'easeInOut' }}
284
- className="absolute left-0 w-full h-[3px] bg-gradient-to-r from-transparent via-[#6fe8ec] to-transparent shadow-[0_0_20px_#6fe8ec] z-10"
285
- />
286
- </div>
287
-
288
- {/* Corner brackets */}
289
- <div className="absolute w-[280px] h-[380px] pointer-events-none flex items-center justify-center">
290
- <div className="absolute top-0 left-0 w-10 h-10 border-t-2 border-l-2 border-[#6fe8ec]/30 rounded-tl-2xl" />
291
- <div className="absolute top-0 right-0 w-10 h-10 border-t-2 border-r-2 border-[#6fe8ec]/30 rounded-tr-2xl" />
292
- <div className="absolute bottom-0 left-0 w-10 h-10 border-b-2 border-l-2 border-[#6fe8ec]/30 rounded-bl-2xl" />
293
- <div className="absolute bottom-0 right-0 w-10 h-10 border-b-2 border-r-2 border-[#6fe8ec]/30 rounded-br-2xl" />
294
- </div>
295
- </div>
296
- </div>
297
-
298
- {/* Instruction card */}
299
- <footer className="relative z-40 p-8 flex flex-col items-center gap-6">
300
- {error ? (
301
- <div className="dark:bg-red-900/30 bg-red-50 backdrop-blur-2xl border border-red-500/30 w-full p-6 rounded-[2.5rem] text-center">
302
- <p className="text-red-500 font-bold text-lg mb-4">{error}</p>
303
- <button
304
- onClick={handleRetry}
305
- className="px-6 py-2 bg-red-500 text-white rounded-xl font-bold"
306
- >
307
- Try Again
308
- </button>
309
- </div>
310
- ) : (
311
- <div className="dark:bg-[#0a1515]/95 bg-white/90 backdrop-blur-2xl border dark:border-white/10 border-black/5 w-full p-6 rounded-2xl text-center shadow-2xl">
312
- <AnimatePresence mode="wait">
313
- <motion.p
314
- key={step}
315
- initial={{ opacity: 0, y: 10 }}
316
- animate={{ opacity: 1, y: 0 }}
317
- exit={{ opacity: 0, y: -10 }}
318
- className="text-[#6fe8ec] font-bold text-2xl tracking-tight mb-2"
319
- >
320
- {isProcessing ? (
321
- <span className="flex items-center justify-center gap-3">
322
- <Loader2 className="w-6 h-6 animate-spin" />
323
- Verifying...
324
- </span>
325
- ) : (
326
- t(instructions[step], language)
327
- )}
328
- </motion.p>
329
- </AnimatePresence>
330
- <div className="flex items-center justify-center gap-2 text-muted text-[10px] font-bold uppercase tracking-widest">
331
- <Lock className="w-3 h-3" />
332
- {t('secure_face_scan_desc', language)}
333
- </div>
334
- </div>
335
- )}
336
-
337
- {/* Progress dots */}
338
- <div className="flex gap-2 w-full max-w-[140px]">
339
- {[0, 1, 2].map((i) => (
340
- <div
341
- key={i}
342
- className={`h-1.5 flex-1 rounded-full transition-all duration-700 ${i <= Math.floor((step / (instructions.length - 1)) * 2)
343
- ? 'bg-[#6fe8ec] shadow-[0_0_15px_#6fe8ec]'
344
- : 'dark:bg-slate-800 bg-slate-200'
345
- }`}
346
- />
347
- ))}
348
- </div>
349
-
350
- <div className="px-6 py-2.5 dark:bg-black/60 bg-white/60 backdrop-blur-md rounded-xl border dark:border-white/5 border-black/5 flex items-center gap-3">
351
- <div
352
- className={`w-2 h-2 rounded-full ${isProcessing
353
- ? 'bg-yellow-400 animate-pulse'
354
- : step === instructions.length - 1
355
- ? 'bg-emerald-400 shadow-[0_0_10px_#34d399]'
356
- : 'bg-[#6fe8ec] animate-pulse'
357
- }`}
358
- />
359
- <span className="text-[10px] dark:text-slate-400 text-slate-600 uppercase tracking-[0.25em] font-black">
360
- {isProcessing ? t('analyzing_liveness', language) : step === instructions.length - 1 ? t('calibration_complete', language) : t('awaiting_calibration', language)}
361
- </span>
362
- </div>
363
- </footer>
364
- </motion.div>
365
- );
366
- };
367
-
368
- export default FaceScan;
@@ -1,51 +0,0 @@
1
- import React from 'react';
2
- import { motion } from 'framer-motion';
3
- import { CheckCircle2, ArrowRight } from 'lucide-react';
4
- import { t } from '../utils/i18n';
5
-
6
- interface IdentityVerifiedProps {
7
- onNext: () => void;
8
- }
9
-
10
- const IdentityVerified: React.FC<IdentityVerifiedProps & { language?: string }> = ({ onNext, language = 'EN' }) => (
11
- <motion.div
12
- initial={{ opacity: 0, scale: 0.9 }}
13
- animate={{ opacity: 1, scale: 1 }}
14
- exit={{ opacity: 0, scale: 1.1 }}
15
- className="flex-1 flex flex-col items-center justify-center px-8 text-center"
16
- >
17
- {/* Success icon with glow */}
18
- <div className="relative mb-10 group">
19
- <div className="absolute inset-0 bg-[#6fe8ec]/30 rounded-3xl blur-3xl group-hover:blur-2xl transition-all" />
20
- <div className="relative w-32 h-32 rounded-2xl bg-[#6fe8ec] flex items-center justify-center shadow-[0_0_40px_rgba(111,232,236,0.4)] border-4 dark:border-white border-slate-100">
21
- <CheckCircle2 className="text-slate-900 w-16 h-16" />
22
- </div>
23
- </div>
24
-
25
- <h1 className="text-main text-3xl font-bold leading-tight mb-3">{t('identity_verified', language)}</h1>
26
- <p className="text-muted text-base leading-relaxed mb-12">
27
- {t('identity_verified_desc', language)}
28
- </p>
29
-
30
- {/* Data consent toggle */}
31
- <div className="w-full dark:bg-white/5 bg-black/5 rounded-2xl p-5 mb-8 flex items-center justify-between text-left">
32
- <div className="flex flex-col gap-0.5">
33
- <span className="text-main font-bold text-sm">{t('share_data_consent', language)}</span>
34
- <p className="text-muted text-xs leading-normal">{t('authorize_dapps', language)}</p>
35
- </div>
36
- <div className="w-11 h-6 bg-brand rounded-full relative p-1 cursor-pointer">
37
- <div className="absolute right-1 top-1 size-4 bg-white rounded-full" />
38
- </div>
39
- </div>
40
-
41
- <button
42
- onClick={onNext}
43
- className="w-full bg-[#6fe8ec] hover:brightness-105 active:scale-[0.98] transition-all text-slate-900 font-bold py-4 rounded-2xl shadow-lg shadow-[#6fe8ec]/20 flex items-center justify-center gap-2"
44
- >
45
- {t('return_to_app', language)}
46
- <ArrowRight className="w-5 h-5" />
47
- </button>
48
- </motion.div>
49
- );
50
-
51
- export default IdentityVerified;