Skip to content

Commit f878027

Browse files
♻️ refactor: replace enum for union
1 parent 75b996c commit f878027

File tree

9 files changed

+101
-56
lines changed

9 files changed

+101
-56
lines changed

README.md

Lines changed: 14 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -32,15 +32,16 @@ npx expo install expo-totp
3232
# Usage
3333

3434
```typescript
35-
import { HmacAlgorithm, useExpoTotp } from "expo-totp";
35+
import { useExpoTotp } from "expo-totp";
3636
import { Button, SafeAreaView, StyleSheet, Text } from "react-native";
3737

3838
export default function App() {
3939
const totp = useExpoTotp();
4040

4141
const start = () => {
42+
// Supports only ASCII secret keys
4243
totp.start("MY_SUPER_SECRET_KEY", {
43-
algorithm: HmacAlgorithm.SHA512,
44+
algorithm: "SHA512",
4445
digits: 6,
4546
interval: 30,
4647
});
@@ -108,15 +109,15 @@ const { code, progress, remainingTime, start, stop } = useExpoTotp();
108109

109110
## Methods
110111

111-
### `startUpdates()`
112+
### `getTotp()`
112113

113114
Compute TOTP once
114115

115116
```typescript
116117
const totp = await ExpoTotp.getTotp("MY_SUPER_SECRET_KEY",
117118
interval: 30,
118119
digits: 6,
119-
algorithm: HmacAlgorithm.SHA512
120+
algorithm: "SHA512"
120121
)
121122
```
122123

@@ -128,7 +129,7 @@ Start TOTP generation
128129
ExpoTotp.startUpdates("YOUR_SUPER_SECRET_KEY", {
129130
interval: 30,
130131
digits: 6,
131-
algorithm: HmacAlgorithm.SHA512,
132+
algorithm: "SHA512",
132133
});
133134
```
134135

@@ -174,6 +175,14 @@ type TotpOptions = {
174175
};
175176
```
176177

178+
### `HmacAlgorithm`
179+
180+
Defines options for TOTP generation
181+
182+
```typescript
183+
type HmacAlgorithm = "SHA512" | "SHA384" | "SHA256" | "SHA1" | "MD5";
184+
```
185+
177186
| Property | Type | Default | Description |
178187
| ----------- | --------------- | -------- | --------------------- |
179188
| `interval` | `number ` | `30` | Interval in seconds |
@@ -192,20 +201,6 @@ export type TotpPayload = {
192201
};
193202
```
194203

195-
## Enums
196-
197-
Defines the enum for algorithm.
198-
199-
```typescript
200-
enum HmacAlgorithm {
201-
SHA512 = "SHA512",
202-
SHA384 = "SHA384",
203-
SHA256 = "SHA256",
204-
SHA1 = "SHA1",
205-
MD5 = "MD5",
206-
}
207-
```
208-
209204
# Contributing
210205

211206
Contributions are very welcome! Please refer to guidelines described in the [contributing guide](https://github.com/expo/expo#contributing).

example/App.tsx

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
import { HmacAlgorithm, useExpoTotp } from "expo-totp";
1+
import { useExpoTotp } from "expo-totp";
22
import ExpoTotpModule from "expo-totp/ExpoTotpModule";
33
import { useCallback, useEffect, useRef } from "react";
44
import {
5+
Alert,
56
Animated,
67
Button,
78
SafeAreaView,
@@ -23,13 +24,25 @@ export default function App() {
2324
outputRange: ["0%", "100%"],
2425
});
2526

26-
const start = useCallback(() => {
27-
ExpoTotpModule.getTotp("MY_SUPER_SECRET_KEY").then(console.log);
28-
totp.start("MY_SUPER_SECRET_KEY", {
29-
algorithm: HmacAlgorithm.SHA512,
30-
digits: 6,
31-
interval: 30,
32-
});
27+
const getSingleTotp = useCallback(async () => {
28+
try {
29+
const totpInfo = await ExpoTotpModule.getTotp("MY_SUPER_SECRET_KEY");
30+
Alert.alert("TOTP", JSON.stringify(totpInfo, null, 2));
31+
} catch (error) {
32+
console.error(error);
33+
}
34+
}, []);
35+
36+
const start = useCallback(async () => {
37+
try {
38+
await totp.start("MY_SUPER_SECRET_KEY", {
39+
algorithm: "SHA512",
40+
digits: 6,
41+
interval: 30,
42+
});
43+
} catch (error) {
44+
console.error(error);
45+
}
3346
}, []);
3447

3548
const stop = useCallback(() => {
@@ -61,8 +74,9 @@ export default function App() {
6174
<ScrollView style={styles.container}>
6275
<Text style={styles.header}>Module API Example</Text>
6376
<Group name="Controls">
64-
<Button title="Start" onPress={start} />
65-
<Button title="Stop" onPress={stop} />
77+
<Button title="Start Updates" onPress={start} />
78+
<Button title="Stop Updates" onPress={stop} />
79+
<Button title="Get Single Totp" onPress={getSingleTotp} />
6680
</Group>
6781
<Group name="Totp">
6882
{!!totp.code && (

ios/ExpoTotpModule.swift

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@ import CommonCrypto
44
fileprivate let CHANGE_EVENT_NAME = "onTotpUpdate"
55

66
public class ExpoTotpModule: Module {
7+
78
private var timer: Timer?
89

10+
911
public func definition() -> ModuleDefinition {
1012
Name("ExpoTotp")
1113

@@ -23,19 +25,30 @@ public class ExpoTotpModule: Module {
2325

2426
}
2527

26-
private func getTotp(secretKey: String, options: TotpOptions?) -> [String: Any]? {
27-
let secretBase64 = Data(secretKey.utf8).base64EncodedString()
28+
29+
private func getTotp(secretKey: String, options: TotpOptions?) throws -> [String: Any] {
2830
let finalOptions = options ?? TotpOptions()
31+
let secretBase64 = Data(secretKey.utf8).base64EncodedString()
2932

30-
return computeTotp(secretBase64: secretBase64, options: finalOptions)
33+
guard let totpInfo = try? computeTotp(secretBase64: secretBase64, options: finalOptions) else {
34+
throw Exceptions.InvalidSecretKey()
35+
}
36+
return totpInfo
3137
}
3238

33-
private func startUpdates(secretKey: String, options: TotpOptions?){
39+
40+
private func startUpdates(secretKey: String, options: TotpOptions?) throws {
41+
3442
stopUpdates()
3543

44+
let finalOptions = options ?? TotpOptions()
45+
3646
let secretBase64 = Data(secretKey.utf8).base64EncodedString()
3747

38-
let finalOptions = options ?? TotpOptions()
48+
guard let totpInfo = computeTotp(secretBase64: secretBase64, options: finalOptions) else {
49+
throw Exceptions.InvalidSecretKey()
50+
}
51+
sendEvent(CHANGE_EVENT_NAME, totpInfo)
3952

4053
DispatchQueue.main.async { [weak self] in
4154
guard let self else { return }
@@ -47,21 +60,26 @@ public class ExpoTotpModule: Module {
4760
sendEvent(CHANGE_EVENT_NAME, totpInfo)
4861
}
4962
}
63+
5064
}
5165

66+
5267
private func stopUpdates(){
68+
5369
timer?.invalidate()
5470
timer = nil
71+
5572
}
5673

74+
5775
private func computeTotp(secretBase64: String, options: TotpOptions) -> [String: Any]? {
76+
5877
let currentTime = Int(Date().timeIntervalSince1970)
5978
let remainingTime = options.interval - currentTime % options.interval
6079
let currentInterval = currentTime / options.interval
6180

6281
guard let keyData = Data(base64Encoded: secretBase64) else {
63-
NSLog("Invalid secret provided")
64-
return nil
82+
return nil
6583
}
6684

6785
// Create message for HMAC
@@ -92,5 +110,7 @@ public class ExpoTotpModule: Module {
92110
"remainingTime": remainingTime,
93111
"progress": (Double(remainingTime) / Double(options.interval)) * 100
94112
]
113+
95114
}
115+
96116
}

ios/TotpExceptions.swift

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
//
2+
// TotpExceptions.swift
3+
// ExpoTotp
4+
//
5+
// Created by Edgar Jonas Mesquita da Silva on 08/02/25.
6+
//
7+
8+
import ExpoModulesCore
9+
10+
11+
extension Exceptions {
12+
13+
internal final class InvalidSecretKey: Exception {
14+
override var reason: String {
15+
"Invalid secret key."
16+
}
17+
}
18+
19+
}

ios/TotpOptions.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ enum HmacAlgorithm: String, Enumerable {
1717
case MD5
1818

1919
func getAlgorithm() -> Int {
20+
2021
switch self {
2122
case .SHA512:
2223
return kCCHmacAlgSHA512
@@ -29,6 +30,7 @@ enum HmacAlgorithm: String, Enumerable {
2930
case .MD5:
3031
return kCCHmacAlgMD5
3132
}
33+
3234
}
3335

3436

@@ -50,6 +52,7 @@ enum HmacAlgorithm: String, Enumerable {
5052
}
5153

5254
struct TotpOptions: Record {
55+
5356
@Field
5457
var interval: Int = 30
5558

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "expo-totp",
3-
"version": "0.1.2",
3+
"version": "2.0.0",
44
"description": "Allow TOTP(Time-based One-Time Password) code generation for react native expo",
55
"main": "build/index.js",
66
"types": "build/index.d.ts",

src/ExpoTotp.types.ts

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,30 +8,24 @@ export type TotpPayload = {
88
progress: number;
99
};
1010

11-
export enum HmacAlgorithm {
12-
SHA512 = "SHA512",
13-
SHA384 = "SHA384",
14-
SHA256 = "SHA256",
15-
SHA1 = "SHA1",
16-
MD5 = "MD5",
17-
}
11+
export type HmacAlgorithm = "SHA512" | "SHA384" | "SHA256" | "SHA1" | "MD5";
1812

1913
export type TotpOptions = {
2014
/**
2115
* @default
2216
* 30
2317
*/
24-
interval: number;
18+
interval?: number;
2519

2620
/**
2721
* @default
2822
* 6
2923
*/
30-
digits: number;
24+
digits?: number;
3125

3226
/**
3327
* @default
3428
* SHA512
3529
*/
36-
algorithm: HmacAlgorithm;
30+
algorithm?: HmacAlgorithm;
3731
};

src/ExpoTotpModule.ts

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,8 @@ import { EventSubscription } from "expo-modules-core";
44
import { ExpoTotpModuleEvents, TotpOptions } from "./ExpoTotp.types";
55

66
declare class ExpoTotpModule extends NativeModule<ExpoTotpModuleEvents> {
7-
getTotp(secretKey: string, options?: Partial<TotpOptions>): Promise<void>;
8-
startUpdates(
9-
secretKey: string,
10-
options?: Partial<TotpOptions>
11-
): Promise<void>;
7+
getTotp(secretKey: string, options?: TotpOptions): Promise<void>;
8+
startUpdates(secretKey: string, options?: TotpOptions): Promise<void>;
129
stopUpdates(): Promise<void>;
1310
addListener<EventName extends "onTotpUpdate">(
1411
eventName: EventName,
@@ -17,7 +14,7 @@ declare class ExpoTotpModule extends NativeModule<ExpoTotpModuleEvents> {
1714
/**
1815
* @deprecated
1916
*/
20-
start(secretKey: string, options?: Partial<TotpOptions>): Promise<void>;
17+
start(secretKey: string, options?: TotpOptions): Promise<void>;
2118
/**
2219
* @deprecated
2320
*/

src/hooks/useExpoTotp.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,12 @@ import { TotpOptions, TotpPayload } from "../ExpoTotp.types";
66
export function useExpoTotp() {
77
const [totp, setTotp] = useState<TotpPayload | null>(null);
88

9-
const start = useCallback((secretKey: string, options?: TotpOptions) => {
10-
ExpoTotp.startUpdates(secretKey, options);
11-
}, []);
9+
const start = useCallback(
10+
async (secretKey: string, options?: TotpOptions) => {
11+
return ExpoTotp.startUpdates(secretKey, options);
12+
},
13+
[]
14+
);
1215

1316
const stop = useCallback(() => {
1417
ExpoTotp.stopUpdates();

0 commit comments

Comments
 (0)