@the-bearded-bear/claude-craft 8.8.1 → 8.8.2
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/Dev/i18n/de/Angular/commands/check-compliance.md +218 -168
- package/Dev/i18n/de/Common/agents/refactoring-specialist.md +146 -69
- package/Dev/i18n/de/Common/commands/add-technology.md +166 -68
- package/Dev/i18n/de/Common/commands/setup-ci.md +216 -33
- package/Dev/i18n/de/Laravel/commands/check-compliance.md +223 -115
- package/Dev/i18n/de/Python/checklists/new-feature.md +319 -44
- package/Dev/i18n/de/React/rules/02-architecture.md +483 -322
- package/Dev/i18n/de/React/rules/03-coding-standards.md +584 -368
- package/Dev/i18n/de/Symfony/commands/check-compliance.md +186 -114
- package/Dev/i18n/de/Symfony/rules/07-testing-symfony.md +679 -10
- package/Dev/i18n/de/UIUX/commands/a11y-component.md +284 -26
- package/Dev/i18n/de/UIUX/commands/design-tokens.md +199 -35
- package/Dev/i18n/de/UIUX/commands/user-flow.md +179 -16
- package/Dev/i18n/de/VueJS/commands/check-compliance.md +227 -102
- package/Dev/i18n/es/Angular/commands/check-compliance.md +217 -167
- package/Dev/i18n/es/Common/commands/add-technology.md +158 -60
- package/Dev/i18n/es/Laravel/commands/check-compliance.md +223 -115
- package/Dev/i18n/es/React/commands/check-testing.md +263 -169
- package/Dev/i18n/es/ReactNative/commands/check-testing.md +256 -198
- package/Dev/i18n/es/Symfony/commands/check-compliance.md +189 -117
- package/Dev/i18n/es/Symfony/commands/check-testing.md +301 -102
- package/Dev/i18n/es/Symfony/commands/migration-plan.md +267 -34
- package/Dev/i18n/es/UIUX/commands/a11y-component.md +274 -16
- package/Dev/i18n/es/UIUX/commands/design-tokens.md +186 -22
- package/Dev/i18n/es/UIUX/commands/user-flow.md +173 -10
- package/Dev/i18n/es/VueJS/commands/check-compliance.md +227 -102
- package/Dev/i18n/fr/Angular/commands/check-compliance.md +217 -167
- package/Dev/i18n/fr/Common/commands/ralph-run.md +138 -60
- package/Dev/i18n/fr/Laravel/commands/check-compliance.md +223 -115
- package/Dev/i18n/fr/React/commands/check-architecture.md +370 -53
- package/Dev/i18n/fr/React/commands/check-code-quality.md +454 -64
- package/Dev/i18n/fr/React/commands/check-testing.md +476 -102
- package/Dev/i18n/fr/Symfony/commands/check-compliance.md +188 -116
- package/Dev/i18n/fr/VueJS/commands/check-compliance.md +227 -102
- package/Dev/i18n/pt/Angular/commands/check-compliance.md +225 -175
- package/Dev/i18n/pt/Common/agents/ralph-conductor.md +211 -30
- package/Dev/i18n/pt/Common/commands/add-technology.md +149 -51
- package/Dev/i18n/pt/Common/commands/ralph-run.md +239 -35
- package/Dev/i18n/pt/Flutter/commands/analyze-performance.md +331 -46
- package/Dev/i18n/pt/Flutter/commands/check-compliance.md +234 -29
- package/Dev/i18n/pt/Flutter/commands/generate-feature.md +526 -51
- package/Dev/i18n/pt/Flutter/commands/generate-widget.md +515 -27
- package/Dev/i18n/pt/Flutter/commands/golden-update.md +322 -46
- package/Dev/i18n/pt/Flutter/commands/localization-check.md +278 -37
- package/Dev/i18n/pt/Laravel/commands/check-compliance.md +212 -104
- package/Dev/i18n/pt/React/commands/accessibility-check.md +151 -42
- package/Dev/i18n/pt/React/commands/bundle-analyze.md +300 -25
- package/Dev/i18n/pt/React/commands/check-architecture.md +369 -34
- package/Dev/i18n/pt/React/commands/check-code-quality.md +440 -34
- package/Dev/i18n/pt/React/commands/check-compliance.md +232 -62
- package/Dev/i18n/pt/React/commands/check-security.md +370 -31
- package/Dev/i18n/pt/React/commands/check-testing.md +473 -43
- package/Dev/i18n/pt/React/commands/generate-component.md +454 -19
- package/Dev/i18n/pt/React/commands/generate-hook.md +425 -24
- package/Dev/i18n/pt/React/commands/storybook-story.md +520 -35
- package/Dev/i18n/pt/ReactNative/commands/app-size.md +358 -387
- package/Dev/i18n/pt/ReactNative/commands/check-code-quality.md +246 -75
- package/Dev/i18n/pt/ReactNative/commands/check-compliance.md +191 -85
- package/Dev/i18n/pt/ReactNative/commands/check-security.md +363 -115
- package/Dev/i18n/pt/ReactNative/commands/check-testing.md +269 -79
- package/Dev/i18n/pt/ReactNative/commands/generate-screen.md +491 -324
- package/Dev/i18n/pt/ReactNative/commands/native-module.md +539 -82
- package/Dev/i18n/pt/ReactNative/commands/store-prepare.md +429 -185
- package/Dev/i18n/pt/ReactNative/rules/02-architecture.md +1084 -31
- package/Dev/i18n/pt/Symfony/commands/check-compliance.md +195 -123
- package/Dev/i18n/pt/UIUX/commands/a11y-audit.md +208 -24
- package/Dev/i18n/pt/UIUX/commands/a11y-component.md +281 -23
- package/Dev/i18n/pt/UIUX/commands/design-tokens.md +197 -33
- package/Dev/i18n/pt/UIUX/commands/user-flow.md +183 -20
- package/Dev/i18n/pt/VueJS/commands/check-compliance.md +228 -103
- package/Dev/i18n/pt/Workflow/commands/analyze.md +147 -146
- package/Dev/i18n/pt/Workflow/commands/design.md +205 -204
- package/Dev/i18n/pt/Workflow/commands/implement.md +184 -184
- package/Project/i18n/de/Sprint/commands/dev.md +339 -86
- package/Project/i18n/es/Sprint/commands/dev.md +342 -91
- package/Project/i18n/pt/Sprint/commands/dev.md +343 -92
- package/bundles/cursor/.cursorrules +1 -1
- package/bundles/windsurf/.windsurfrules +1 -1
- package/package.json +1 -1
|
@@ -1,143 +1,600 @@
|
|
|
1
1
|
---
|
|
2
|
-
description:
|
|
2
|
+
description: Guia de Criação de Módulo Nativo React Native
|
|
3
|
+
argument-hint: [arguments]
|
|
3
4
|
---
|
|
4
5
|
|
|
5
|
-
#
|
|
6
|
+
# Guia de Criação de Módulo Nativo React Native
|
|
6
7
|
|
|
7
|
-
|
|
8
|
+
Você é um desenvolvedor React Native sênior. Você deve orientar a criação de um módulo nativo (bridge) para iOS e Android.
|
|
8
9
|
|
|
9
|
-
|
|
10
|
+
## Argumentos
|
|
11
|
+
$ARGUMENTS
|
|
10
12
|
|
|
11
|
-
|
|
13
|
+
Argumentos:
|
|
14
|
+
- Nome do módulo (ex.: `DeviceInfo`, `Biometrics`, `FileManager`)
|
|
15
|
+
- (Opcional) Funcionalidades: sync, async, events
|
|
12
16
|
|
|
13
|
-
|
|
17
|
+
Exemplo: `/reactnative:native-module Biometrics async,events`
|
|
14
18
|
|
|
15
|
-
|
|
19
|
+
## Modo de Planejamento
|
|
16
20
|
|
|
17
|
-
|
|
21
|
+
> O modo de planejamento é ativado automaticamente quando o escopo abrange múltiplos módulos ou requer investigação transversal.
|
|
18
22
|
|
|
19
|
-
|
|
23
|
+
## MISSÃO
|
|
20
24
|
|
|
21
|
-
|
|
25
|
+
### Etapa 1: Estrutura do Módulo
|
|
22
26
|
|
|
23
27
|
```
|
|
24
|
-
src/
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
├── {ModuleName}.
|
|
35
|
-
└── {ModuleName}
|
|
28
|
+
src/
|
|
29
|
+
└── native/
|
|
30
|
+
└── {ModuleName}/
|
|
31
|
+
├── index.ts # API JavaScript
|
|
32
|
+
├── types.ts # Tipos TypeScript
|
|
33
|
+
└── NativeModule.ts # Bridge com TurboModule
|
|
34
|
+
|
|
35
|
+
android/
|
|
36
|
+
└── app/src/main/java/com/{package}/
|
|
37
|
+
└── {modulename}/
|
|
38
|
+
├── {ModuleName}Module.kt # Módulo principal
|
|
39
|
+
└── {ModuleName}Package.kt # Registro do pacote
|
|
40
|
+
|
|
41
|
+
ios/
|
|
42
|
+
└── {AppName}/
|
|
43
|
+
├── {ModuleName}.swift # Módulo Swift
|
|
44
|
+
└── {ModuleName}-Bridging-Header.h # Header para bridge Obj-C
|
|
36
45
|
```
|
|
37
46
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
## Implementação TypeScript
|
|
47
|
+
### Etapa 2: API JavaScript/TypeScript
|
|
41
48
|
|
|
42
49
|
```typescript
|
|
43
|
-
// types.ts
|
|
44
|
-
export interface
|
|
50
|
+
// src/native/{ModuleName}/types.ts
|
|
51
|
+
export interface {ModuleName}Result {
|
|
45
52
|
success: boolean;
|
|
46
53
|
data?: unknown;
|
|
47
54
|
error?: string;
|
|
48
55
|
}
|
|
49
56
|
|
|
50
|
-
|
|
51
|
-
|
|
57
|
+
export interface {ModuleName}Options {
|
|
58
|
+
timeout?: number;
|
|
59
|
+
// ...outras opções
|
|
60
|
+
}
|
|
52
61
|
|
|
53
|
-
|
|
62
|
+
export type {ModuleName}EventType = 'onProgress' | 'onComplete' | 'onError';
|
|
54
63
|
|
|
55
|
-
export
|
|
56
|
-
|
|
57
|
-
|
|
64
|
+
export interface {ModuleName}Event {
|
|
65
|
+
type: {ModuleName}EventType;
|
|
66
|
+
payload: unknown;
|
|
67
|
+
}
|
|
58
68
|
```
|
|
59
69
|
|
|
60
|
-
|
|
70
|
+
```typescript
|
|
71
|
+
// src/native/{ModuleName}/NativeModule.ts
|
|
72
|
+
import { NativeModules, NativeEventEmitter, Platform } from 'react-native';
|
|
73
|
+
|
|
74
|
+
const LINKING_ERROR =
|
|
75
|
+
`O pacote '{ModuleName}' não parece estar vinculado. Certifique-se de: \n\n` +
|
|
76
|
+
Platform.select({ ios: "- Você executou 'pod install'\n", default: '' }) +
|
|
77
|
+
'- Você reconstruiu o app após instalar o pacote\n' +
|
|
78
|
+
'- Você não está usando Expo Go\n';
|
|
79
|
+
|
|
80
|
+
// Tipo do módulo nativo
|
|
81
|
+
interface {ModuleName}NativeModule {
|
|
82
|
+
// Métodos síncronos
|
|
83
|
+
getConstant(): string;
|
|
84
|
+
|
|
85
|
+
// Métodos assíncronos
|
|
86
|
+
authenticate(options: {ModuleName}Options): Promise<{ModuleName}Result>;
|
|
87
|
+
isSupported(): Promise<boolean>;
|
|
88
|
+
|
|
89
|
+
// Para eventos
|
|
90
|
+
addListener(eventType: string): void;
|
|
91
|
+
removeListeners(count: number): void;
|
|
92
|
+
}
|
|
61
93
|
|
|
62
|
-
|
|
94
|
+
const Native{ModuleName} = NativeModules.{ModuleName}
|
|
95
|
+
? (NativeModules.{ModuleName} as {ModuleName}NativeModule)
|
|
96
|
+
: new Proxy(
|
|
97
|
+
{} as {ModuleName}NativeModule,
|
|
98
|
+
{
|
|
99
|
+
get() {
|
|
100
|
+
throw new Error(LINKING_ERROR);
|
|
101
|
+
},
|
|
102
|
+
}
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
export { Native{ModuleName} };
|
|
106
|
+
export const {ModuleName}EventEmitter = new NativeEventEmitter(
|
|
107
|
+
NativeModules.{ModuleName}
|
|
108
|
+
);
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
```typescript
|
|
112
|
+
// src/native/{ModuleName}/index.ts
|
|
113
|
+
import { useEffect, useCallback } from 'react';
|
|
114
|
+
import { Native{ModuleName}, {ModuleName}EventEmitter } from './NativeModule';
|
|
115
|
+
import type {
|
|
116
|
+
{ModuleName}Result,
|
|
117
|
+
{ModuleName}Options,
|
|
118
|
+
{ModuleName}Event,
|
|
119
|
+
} from './types';
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Verifica se o recurso é suportado no dispositivo.
|
|
123
|
+
*/
|
|
124
|
+
export async function isSupported(): Promise<boolean> {
|
|
125
|
+
try {
|
|
126
|
+
return await Native{ModuleName}.isSupported();
|
|
127
|
+
} catch {
|
|
128
|
+
return false;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Inicia a autenticação biométrica.
|
|
134
|
+
*/
|
|
135
|
+
export async function authenticate(
|
|
136
|
+
options: {ModuleName}Options = {}
|
|
137
|
+
): Promise<{ModuleName}Result> {
|
|
138
|
+
return Native{ModuleName}.authenticate(options);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Hook para escutar eventos do módulo.
|
|
143
|
+
*/
|
|
144
|
+
export function use{ModuleName}Events(
|
|
145
|
+
onEvent: (event: {ModuleName}Event) => void
|
|
146
|
+
) {
|
|
147
|
+
useEffect(() => {
|
|
148
|
+
const subscription = {ModuleName}EventEmitter.addListener(
|
|
149
|
+
'{moduleName}Event',
|
|
150
|
+
onEvent
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
return () => {
|
|
154
|
+
subscription.remove();
|
|
155
|
+
};
|
|
156
|
+
}, [onEvent]);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Hook completo para usar o módulo.
|
|
161
|
+
*/
|
|
162
|
+
export function use{ModuleName}() {
|
|
163
|
+
const [isAvailable, setIsAvailable] = useState<boolean | null>(null);
|
|
164
|
+
const [isAuthenticating, setIsAuthenticating] = useState(false);
|
|
165
|
+
const [error, setError] = useState<string | null>(null);
|
|
166
|
+
|
|
167
|
+
useEffect(() => {
|
|
168
|
+
isSupported().then(setIsAvailable);
|
|
169
|
+
}, []);
|
|
170
|
+
|
|
171
|
+
const authenticate = useCallback(async (options?: {ModuleName}Options) => {
|
|
172
|
+
if (!isAvailable) {
|
|
173
|
+
setError('{ModuleName} não disponível');
|
|
174
|
+
return { success: false, error: '{ModuleName} não disponível' };
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
setIsAuthenticating(true);
|
|
178
|
+
setError(null);
|
|
179
|
+
|
|
180
|
+
try {
|
|
181
|
+
const result = await Native{ModuleName}.authenticate(options || {});
|
|
182
|
+
return result;
|
|
183
|
+
} catch (err) {
|
|
184
|
+
const errorMessage = err instanceof Error ? err.message : 'Erro desconhecido';
|
|
185
|
+
setError(errorMessage);
|
|
186
|
+
return { success: false, error: errorMessage };
|
|
187
|
+
} finally {
|
|
188
|
+
setIsAuthenticating(false);
|
|
189
|
+
}
|
|
190
|
+
}, [isAvailable]);
|
|
191
|
+
|
|
192
|
+
return {
|
|
193
|
+
isAvailable,
|
|
194
|
+
isAuthenticating,
|
|
195
|
+
error,
|
|
196
|
+
authenticate,
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Exportar tudo
|
|
201
|
+
export * from './types';
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
### Etapa 3: Módulo Android (Kotlin)
|
|
63
205
|
|
|
64
206
|
```kotlin
|
|
65
|
-
|
|
207
|
+
// android/app/src/main/java/com/{package}/{modulename}/{ModuleName}Module.kt
|
|
208
|
+
package com.{package}.{modulename}
|
|
66
209
|
|
|
67
210
|
import com.facebook.react.bridge.*
|
|
211
|
+
import com.facebook.react.modules.core.DeviceEventManagerModule
|
|
212
|
+
import kotlinx.coroutines.*
|
|
68
213
|
|
|
69
|
-
class
|
|
70
|
-
ReactContextBaseJavaModule(reactContext)
|
|
214
|
+
class {ModuleName}Module(reactContext: ReactApplicationContext) :
|
|
215
|
+
ReactContextBaseJavaModule(reactContext),
|
|
216
|
+
CoroutineScope by MainScope() {
|
|
71
217
|
|
|
72
|
-
|
|
218
|
+
companion object {
|
|
219
|
+
const val NAME = "{ModuleName}"
|
|
220
|
+
private const val EVENT_NAME = "{moduleName}Event"
|
|
221
|
+
}
|
|
73
222
|
|
|
223
|
+
override fun getName(): String = NAME
|
|
224
|
+
|
|
225
|
+
// Constantes expostas ao JS
|
|
226
|
+
override fun getConstants(): MutableMap<String, Any> {
|
|
227
|
+
return hashMapOf(
|
|
228
|
+
"SUPPORTED" to isBiometricSupported()
|
|
229
|
+
)
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Método síncrono
|
|
233
|
+
@ReactMethod(isBlockingSynchronousMethod = true)
|
|
234
|
+
fun getConstant(): String {
|
|
235
|
+
return "some_value"
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Método assíncrono com Promise
|
|
239
|
+
@ReactMethod
|
|
240
|
+
fun isSupported(promise: Promise) {
|
|
241
|
+
launch {
|
|
242
|
+
try {
|
|
243
|
+
val supported = isBiometricSupported()
|
|
244
|
+
promise.resolve(supported)
|
|
245
|
+
} catch (e: Exception) {
|
|
246
|
+
promise.reject("ERROR", e.message, e)
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
@ReactMethod
|
|
252
|
+
fun authenticate(options: ReadableMap, promise: Promise) {
|
|
253
|
+
val timeout = if (options.hasKey("timeout")) options.getInt("timeout") else 30000
|
|
254
|
+
|
|
255
|
+
launch {
|
|
256
|
+
try {
|
|
257
|
+
// Implementar lógica nativa aqui
|
|
258
|
+
val result = performAuthentication(timeout)
|
|
259
|
+
|
|
260
|
+
val response = Arguments.createMap().apply {
|
|
261
|
+
putBoolean("success", result.success)
|
|
262
|
+
result.data?.let { putString("data", it) }
|
|
263
|
+
result.error?.let { putString("error", it) }
|
|
264
|
+
}
|
|
265
|
+
promise.resolve(response)
|
|
266
|
+
} catch (e: Exception) {
|
|
267
|
+
promise.reject("AUTH_ERROR", e.message, e)
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// Enviar eventos ao JS
|
|
273
|
+
private fun sendEvent(eventName: String, params: WritableMap?) {
|
|
274
|
+
reactApplicationContext
|
|
275
|
+
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
|
|
276
|
+
.emit(eventName, params)
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
private fun emitProgress(progress: Int) {
|
|
280
|
+
val params = Arguments.createMap().apply {
|
|
281
|
+
putString("type", "onProgress")
|
|
282
|
+
putInt("progress", progress)
|
|
283
|
+
}
|
|
284
|
+
sendEvent(EVENT_NAME, params)
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// Necessário para eventos
|
|
74
288
|
@ReactMethod
|
|
75
|
-
fun
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
289
|
+
fun addListener(eventName: String) {
|
|
290
|
+
// Necessário para NativeEventEmitter
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
@ReactMethod
|
|
294
|
+
fun removeListeners(count: Int) {
|
|
295
|
+
// Necessário para NativeEventEmitter
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// Lógica de negócio nativa
|
|
299
|
+
private suspend fun performAuthentication(timeout: Int): AuthResult {
|
|
300
|
+
return withContext(Dispatchers.IO) {
|
|
301
|
+
// Implementar aqui
|
|
302
|
+
AuthResult(success = true, data = "authenticated")
|
|
83
303
|
}
|
|
84
304
|
}
|
|
305
|
+
|
|
306
|
+
private fun isBiometricSupported(): Boolean {
|
|
307
|
+
// Implementar verificação
|
|
308
|
+
return true
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
override fun onCatalystInstanceDestroy() {
|
|
312
|
+
super.onCatalystInstanceDestroy()
|
|
313
|
+
cancel() // Cancelar coroutines
|
|
314
|
+
}
|
|
85
315
|
}
|
|
316
|
+
|
|
317
|
+
data class AuthResult(
|
|
318
|
+
val success: Boolean,
|
|
319
|
+
val data: String? = null,
|
|
320
|
+
val error: String? = null
|
|
321
|
+
)
|
|
86
322
|
```
|
|
87
323
|
|
|
88
|
-
|
|
324
|
+
```kotlin
|
|
325
|
+
// android/app/src/main/java/com/{package}/{modulename}/{ModuleName}Package.kt
|
|
326
|
+
package com.{package}.{modulename}
|
|
327
|
+
|
|
328
|
+
import com.facebook.react.ReactPackage
|
|
329
|
+
import com.facebook.react.bridge.NativeModule
|
|
330
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
331
|
+
import com.facebook.react.uimanager.ViewManager
|
|
332
|
+
|
|
333
|
+
class {ModuleName}Package : ReactPackage {
|
|
334
|
+
override fun createNativeModules(
|
|
335
|
+
reactContext: ReactApplicationContext
|
|
336
|
+
): List<NativeModule> {
|
|
337
|
+
return listOf({ModuleName}Module(reactContext))
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
override fun createViewManagers(
|
|
341
|
+
reactContext: ReactApplicationContext
|
|
342
|
+
): List<ViewManager<*, *>> {
|
|
343
|
+
return emptyList()
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
```kotlin
|
|
349
|
+
// Adicionar ao MainApplication.kt
|
|
350
|
+
override fun getPackages(): List<ReactPackage> =
|
|
351
|
+
PackageList(this).packages.apply {
|
|
352
|
+
add({ModuleName}Package())
|
|
353
|
+
}
|
|
354
|
+
```
|
|
89
355
|
|
|
90
|
-
|
|
356
|
+
### Etapa 4: Módulo iOS (Swift)
|
|
91
357
|
|
|
92
358
|
```swift
|
|
93
|
-
|
|
94
|
-
|
|
359
|
+
// ios/{AppName}/{ModuleName}.swift
|
|
360
|
+
import Foundation
|
|
361
|
+
import React
|
|
362
|
+
import LocalAuthentication // ou outro framework necessário
|
|
363
|
+
|
|
364
|
+
@objc({ModuleName})
|
|
365
|
+
class {ModuleName}: RCTEventEmitter {
|
|
366
|
+
|
|
367
|
+
private let eventName = "{moduleName}Event"
|
|
368
|
+
|
|
369
|
+
// MARK: - Configuração do Módulo
|
|
370
|
+
|
|
371
|
+
@objc override static func moduleName() -> String! {
|
|
372
|
+
return "{ModuleName}"
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
@objc override static func requiresMainQueueSetup() -> Bool {
|
|
376
|
+
return false
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
// Eventos suportados
|
|
380
|
+
override func supportedEvents() -> [String]! {
|
|
381
|
+
return [eventName]
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
// Constantes expostas ao JS
|
|
385
|
+
@objc override func constantsToExport() -> [AnyHashable : Any]! {
|
|
386
|
+
return [
|
|
387
|
+
"SUPPORTED": isBiometricSupported()
|
|
388
|
+
]
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// MARK: - Métodos
|
|
95
392
|
|
|
96
393
|
@objc
|
|
97
|
-
func
|
|
98
|
-
|
|
394
|
+
func getConstant() -> String {
|
|
395
|
+
return "some_value"
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
@objc(isSupported:rejecter:)
|
|
399
|
+
func isSupported(
|
|
400
|
+
resolver resolve: @escaping RCTPromiseResolveBlock,
|
|
99
401
|
rejecter reject: @escaping RCTPromiseRejectBlock
|
|
100
402
|
) {
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
403
|
+
resolve(isBiometricSupported())
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
@objc(authenticate:resolver:rejecter:)
|
|
407
|
+
func authenticate(
|
|
408
|
+
options: NSDictionary,
|
|
409
|
+
resolver resolve: @escaping RCTPromiseResolveBlock,
|
|
410
|
+
rejecter reject: @escaping RCTPromiseRejectBlock
|
|
411
|
+
) {
|
|
412
|
+
let timeout = options["timeout"] as? Int ?? 30000
|
|
413
|
+
|
|
414
|
+
DispatchQueue.global(qos: .userInitiated).async { [weak self] in
|
|
415
|
+
self?.performAuthentication(timeout: timeout) { result in
|
|
416
|
+
switch result {
|
|
417
|
+
case .success(let data):
|
|
418
|
+
resolve([
|
|
419
|
+
"success": true,
|
|
420
|
+
"data": data
|
|
421
|
+
])
|
|
422
|
+
case .failure(let error):
|
|
423
|
+
resolve([
|
|
424
|
+
"success": false,
|
|
425
|
+
"error": error.localizedDescription
|
|
426
|
+
])
|
|
427
|
+
}
|
|
428
|
+
}
|
|
107
429
|
}
|
|
108
430
|
}
|
|
109
431
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
432
|
+
// MARK: - Métodos Privados
|
|
433
|
+
|
|
434
|
+
private func isBiometricSupported() -> Bool {
|
|
435
|
+
let context = LAContext()
|
|
436
|
+
var error: NSError?
|
|
437
|
+
return context.canEvaluatePolicy(
|
|
438
|
+
.deviceOwnerAuthenticationWithBiometrics,
|
|
439
|
+
error: &error
|
|
440
|
+
)
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
private func performAuthentication(
|
|
444
|
+
timeout: Int,
|
|
445
|
+
completion: @escaping (Result<String, Error>) -> Void
|
|
446
|
+
) {
|
|
447
|
+
let context = LAContext()
|
|
448
|
+
context.localizedFallbackTitle = "Usar senha"
|
|
449
|
+
|
|
450
|
+
context.evaluatePolicy(
|
|
451
|
+
.deviceOwnerAuthenticationWithBiometrics,
|
|
452
|
+
localizedReason: "Autenticação necessária"
|
|
453
|
+
) { success, error in
|
|
454
|
+
if success {
|
|
455
|
+
completion(.success("authenticated"))
|
|
456
|
+
} else if let error = error {
|
|
457
|
+
completion(.failure(error))
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
// Emitir evento ao JS
|
|
463
|
+
private func emitProgress(_ progress: Int) {
|
|
464
|
+
sendEvent(withName: eventName, body: [
|
|
465
|
+
"type": "onProgress",
|
|
466
|
+
"progress": progress
|
|
467
|
+
])
|
|
113
468
|
}
|
|
114
469
|
}
|
|
115
470
|
```
|
|
116
471
|
|
|
117
|
-
|
|
472
|
+
```objc
|
|
473
|
+
// ios/{AppName}/{ModuleName}-Bridging-Header.h
|
|
474
|
+
#import <React/RCTBridgeModule.h>
|
|
475
|
+
#import <React/RCTEventEmitter.h>
|
|
118
476
|
|
|
119
|
-
|
|
477
|
+
// Expor métodos ao Objective-C
|
|
478
|
+
@interface RCT_EXTERN_MODULE({ModuleName}, RCTEventEmitter)
|
|
120
479
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
- Acesso a hardware específico
|
|
124
|
-
- Funcionalidades de segurança avançadas
|
|
125
|
-
- Integração com SDKs nativos
|
|
126
|
-
- Otimizações de performance críticas
|
|
480
|
+
RCT_EXTERN_METHOD(isSupported:(RCTPromiseResolveBlock)resolve
|
|
481
|
+
rejecter:(RCTPromiseRejectBlock)reject)
|
|
127
482
|
|
|
128
|
-
|
|
483
|
+
RCT_EXTERN_METHOD(authenticate:(NSDictionary *)options
|
|
484
|
+
resolver:(RCTPromiseResolveBlock)resolve
|
|
485
|
+
rejecter:(RCTPromiseRejectBlock)reject)
|
|
129
486
|
|
|
130
|
-
|
|
487
|
+
RCT_EXTERN__BLOCKING_SYNCHRONOUS_METHOD(getConstant)
|
|
131
488
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
- [ ] Implementação iOS completa
|
|
135
|
-
- [ ] Tipagem TypeScript definida
|
|
136
|
-
- [ ] Testes unitários criados
|
|
137
|
-
- [ ] Documentação escrita
|
|
138
|
-
- [ ] Tratamento de erros implementado
|
|
139
|
-
- [ ] Testado em ambas plataformas
|
|
489
|
+
@end
|
|
490
|
+
```
|
|
140
491
|
|
|
141
|
-
|
|
492
|
+
### Etapa 5: Testes do Módulo
|
|
142
493
|
|
|
143
|
-
|
|
494
|
+
```typescript
|
|
495
|
+
// src/native/{ModuleName}/__tests__/{ModuleName}.test.ts
|
|
496
|
+
import { NativeModules } from 'react-native';
|
|
497
|
+
import { authenticate, isSupported } from '../index';
|
|
498
|
+
|
|
499
|
+
// Mock do módulo nativo
|
|
500
|
+
jest.mock('react-native', () => ({
|
|
501
|
+
NativeModules: {
|
|
502
|
+
{ModuleName}: {
|
|
503
|
+
isSupported: jest.fn(),
|
|
504
|
+
authenticate: jest.fn(),
|
|
505
|
+
},
|
|
506
|
+
},
|
|
507
|
+
NativeEventEmitter: jest.fn(() => ({
|
|
508
|
+
addListener: jest.fn(() => ({ remove: jest.fn() })),
|
|
509
|
+
})),
|
|
510
|
+
Platform: {
|
|
511
|
+
OS: 'ios',
|
|
512
|
+
select: jest.fn((obj) => obj.ios),
|
|
513
|
+
},
|
|
514
|
+
}));
|
|
515
|
+
|
|
516
|
+
describe('{ModuleName}', () => {
|
|
517
|
+
beforeEach(() => {
|
|
518
|
+
jest.clearAllMocks();
|
|
519
|
+
});
|
|
520
|
+
|
|
521
|
+
describe('isSupported', () => {
|
|
522
|
+
it('retorna true quando suportado', async () => {
|
|
523
|
+
(NativeModules.{ModuleName}.isSupported as jest.Mock).mockResolvedValue(true);
|
|
524
|
+
|
|
525
|
+
const result = await isSupported();
|
|
526
|
+
|
|
527
|
+
expect(result).toBe(true);
|
|
528
|
+
});
|
|
529
|
+
|
|
530
|
+
it('retorna false em caso de erro', async () => {
|
|
531
|
+
(NativeModules.{ModuleName}.isSupported as jest.Mock).mockRejectedValue(
|
|
532
|
+
new Error('Não disponível')
|
|
533
|
+
);
|
|
534
|
+
|
|
535
|
+
const result = await isSupported();
|
|
536
|
+
|
|
537
|
+
expect(result).toBe(false);
|
|
538
|
+
});
|
|
539
|
+
});
|
|
540
|
+
|
|
541
|
+
describe('authenticate', () => {
|
|
542
|
+
it('retorna resultado de sucesso', async () => {
|
|
543
|
+
const mockResult = { success: true, data: 'authenticated' };
|
|
544
|
+
(NativeModules.{ModuleName}.authenticate as jest.Mock).mockResolvedValue(mockResult);
|
|
545
|
+
|
|
546
|
+
const result = await authenticate({ timeout: 5000 });
|
|
547
|
+
|
|
548
|
+
expect(result).toEqual(mockResult);
|
|
549
|
+
expect(NativeModules.{ModuleName}.authenticate).toHaveBeenCalledWith({
|
|
550
|
+
timeout: 5000,
|
|
551
|
+
});
|
|
552
|
+
});
|
|
553
|
+
|
|
554
|
+
it('trata falha de autenticação', async () => {
|
|
555
|
+
const mockResult = { success: false, error: 'Usuário cancelou' };
|
|
556
|
+
(NativeModules.{ModuleName}.authenticate as jest.Mock).mockResolvedValue(mockResult);
|
|
557
|
+
|
|
558
|
+
const result = await authenticate();
|
|
559
|
+
|
|
560
|
+
expect(result.success).toBe(false);
|
|
561
|
+
expect(result.error).toBe('Usuário cancelou');
|
|
562
|
+
});
|
|
563
|
+
});
|
|
564
|
+
});
|
|
565
|
+
```
|
|
566
|
+
|
|
567
|
+
### Resumo
|
|
568
|
+
|
|
569
|
+
```
|
|
570
|
+
══════════════════════════════════════════════════════════════
|
|
571
|
+
✅ MÓDULO NATIVO - {ModuleName}
|
|
572
|
+
══════════════════════════════════════════════════════════════
|
|
573
|
+
|
|
574
|
+
📁 Arquivos criados:
|
|
575
|
+
|
|
576
|
+
JavaScript/TypeScript:
|
|
577
|
+
- src/native/{ModuleName}/index.ts
|
|
578
|
+
- src/native/{ModuleName}/types.ts
|
|
579
|
+
- src/native/{ModuleName}/NativeModule.ts
|
|
580
|
+
|
|
581
|
+
Android (Kotlin):
|
|
582
|
+
- android/.../{ ModuleName}Module.kt
|
|
583
|
+
- android/.../{ ModuleName}Package.kt
|
|
584
|
+
|
|
585
|
+
iOS (Swift):
|
|
586
|
+
- ios/{AppName}/{ModuleName}.swift
|
|
587
|
+
- ios/{AppName}/{ModuleName}-Bridging-Header.h
|
|
588
|
+
|
|
589
|
+
📝 API exposta:
|
|
590
|
+
- isSupported(): Promise<boolean>
|
|
591
|
+
- authenticate(options): Promise<Result>
|
|
592
|
+
- use{ModuleName}(): Hook completo
|
|
593
|
+
- use{ModuleName}Events(): Listener de eventos
|
|
594
|
+
|
|
595
|
+
🔧 Próximos passos:
|
|
596
|
+
1. Android: Adicionar {ModuleName}Package ao MainApplication.kt
|
|
597
|
+
2. iOS: pod install
|
|
598
|
+
3. Reconstruir ambos os apps
|
|
599
|
+
4. Testar em dispositivo físico
|
|
600
|
+
```
|