Skip to content

Commit 4dbabaa

Browse files
Add Keychain Service
1 parent 6293d6e commit 4dbabaa

35 files changed

+1484
-315
lines changed

Recap.xcodeproj/project.pbxproj

Lines changed: 25 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -23,19 +23,11 @@
2323
remoteGlobalIDString = A72106512E3016590073C515;
2424
remoteInfo = Recap;
2525
};
26-
A721066B2E30165B0073C515 /* PBXContainerItemProxy */ = {
27-
isa = PBXContainerItemProxy;
28-
containerPortal = A721064A2E3016590073C515 /* Project object */;
29-
proxyType = 1;
30-
remoteGlobalIDString = A72106512E3016590073C515;
31-
remoteInfo = Recap;
32-
};
3326
/* End PBXContainerItemProxy section */
3427

3528
/* Begin PBXFileReference section */
3629
A72106522E3016590073C515 /* Recap.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Recap.app; sourceTree = BUILT_PRODUCTS_DIR; };
3730
A72106602E30165B0073C515 /* RecapTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RecapTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
38-
A721066A2E30165B0073C515 /* RecapUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RecapUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
3931
/* End PBXFileReference section */
4032

4133
/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */
@@ -46,12 +38,16 @@
4638
Audio/Models/AudioProcessGroup.swift,
4739
Audio/Processing/Detection/AudioProcessControllerType.swift,
4840
DataModels/RecapDataModel.xcdatamodeld,
41+
Helpers/Availability/AvailabilityHelper.swift,
4942
"Helpers/Colors/Color+Extension.swift",
5043
Helpers/Constants/AppConstants.swift,
5144
Helpers/Constants/UIConstants.swift,
5245
Helpers/MeetingDetection/MeetingPatternMatcher.swift,
5346
Helpers/Permissions/PermissionsHelper.swift,
5447
Helpers/Permissions/PermissionsHelperType.swift,
48+
Repositories/LLMModels/LLMModelRepository.swift,
49+
Repositories/LLMModels/LLMModelRepositoryType.swift,
50+
Repositories/Models/LLMModelInfo.swift,
5551
Repositories/Models/LLMProvider.swift,
5652
Repositories/Models/RecordingInfo.swift,
5753
Repositories/Models/UserPreferencesInfo.swift,
@@ -60,7 +56,24 @@
6056
Repositories/UserPreferences/UserPreferencesRepository.swift,
6157
Repositories/UserPreferences/UserPreferencesRepositoryType.swift,
6258
Services/CoreData/CoreDataManagerType.swift,
59+
Services/Keychain/KeychainAPIValidator.swift,
60+
Services/Keychain/KeychainAPIValidatorType.swift,
61+
Services/Keychain/KeychainService.swift,
62+
"Services/Keychain/KeychainService+Extensions.swift",
63+
Services/Keychain/KeychainServiceType.swift,
6364
Services/LLM/Core/LLMError.swift,
65+
Services/LLM/Core/LLMModelType.swift,
66+
Services/LLM/Core/LLMOptions.swift,
67+
Services/LLM/Core/LLMProviderType.swift,
68+
Services/LLM/Core/LLMTaskManageable.swift,
69+
Services/LLM/LLMService.swift,
70+
Services/LLM/LLMServiceType.swift,
71+
Services/LLM/Providers/Ollama/OllamaAPIClient.swift,
72+
Services/LLM/Providers/Ollama/OllamaModel.swift,
73+
Services/LLM/Providers/Ollama/OllamaProvider.swift,
74+
Services/LLM/Providers/OpenRouter/OpenRouterAPIClient.swift,
75+
Services/LLM/Providers/OpenRouter/OpenRouterModel.swift,
76+
Services/LLM/Providers/OpenRouter/OpenRouterProvider.swift,
6477
Services/MeetingDetection/Core/MeetingDetectionService.swift,
6578
Services/MeetingDetection/Core/MeetingDetectionServiceType.swift,
6679
Services/MeetingDetection/Detectors/GoogleMeetDetector.swift,
@@ -78,6 +91,8 @@
7891
Services/Summarization/Models/SummarizationResult.swift,
7992
Services/Summarization/SummarizationServiceType.swift,
8093
Services/Transcription/TranscriptionServiceType.swift,
94+
Services/Utilities/Warnings/ProviderWarningCoordinator.swift,
95+
Services/Utilities/Warnings/WarningManager.swift,
8196
Services/Utilities/Warnings/WarningManagerType.swift,
8297
UIComponents/Buttons/PillButton.swift,
8398
UIComponents/Cards/ActionableWarningCard.swift,
@@ -86,6 +101,8 @@
86101
UseCases/Settings/Components/MeetingDetection/MeetingDetectionView.swift,
87102
UseCases/Settings/Components/Reusable/CustomToggle.swift,
88103
UseCases/Settings/Components/SettingsCard.swift,
104+
UseCases/Settings/ViewModels/General/GeneralSettingsViewModel.swift,
105+
UseCases/Settings/ViewModels/General/GeneralSettingsViewModelType.swift,
89106
UseCases/Settings/ViewModels/MeetingDetection/MeetingDetectionSettingsViewModel.swift,
90107
UseCases/Settings/ViewModels/MeetingDetection/MeetingDetectionSettingsViewModelType.swift,
91108
UseCases/Summary/Components/ProcessingProgressBar.swift,
@@ -134,13 +151,6 @@
134151
);
135152
runOnlyForDeploymentPostprocessing = 0;
136153
};
137-
A72106672E30165B0073C515 /* Frameworks */ = {
138-
isa = PBXFrameworksBuildPhase;
139-
buildActionMask = 2147483647;
140-
files = (
141-
);
142-
runOnlyForDeploymentPostprocessing = 0;
143-
};
144154
/* End PBXFrameworksBuildPhase section */
145155

146156
/* Begin PBXGroup section */
@@ -158,7 +168,6 @@
158168
children = (
159169
A72106522E3016590073C515 /* Recap.app */,
160170
A72106602E30165B0073C515 /* RecapTests.xctest */,
161-
A721066A2E30165B0073C515 /* RecapUITests.xctest */,
162171
);
163172
name = Products;
164173
sourceTree = "<group>";
@@ -217,26 +226,6 @@
217226
productReference = A72106602E30165B0073C515 /* RecapTests.xctest */;
218227
productType = "com.apple.product-type.bundle.unit-test";
219228
};
220-
A72106692E30165B0073C515 /* RecapUITests */ = {
221-
isa = PBXNativeTarget;
222-
buildConfigurationList = A721067A2E30165B0073C515 /* Build configuration list for PBXNativeTarget "RecapUITests" */;
223-
buildPhases = (
224-
A72106662E30165B0073C515 /* Sources */,
225-
A72106672E30165B0073C515 /* Frameworks */,
226-
A72106682E30165B0073C515 /* Resources */,
227-
);
228-
buildRules = (
229-
);
230-
dependencies = (
231-
A721066C2E30165B0073C515 /* PBXTargetDependency */,
232-
);
233-
name = RecapUITests;
234-
packageProductDependencies = (
235-
);
236-
productName = RecapUITests;
237-
productReference = A721066A2E30165B0073C515 /* RecapUITests.xctest */;
238-
productType = "com.apple.product-type.bundle.ui-testing";
239-
};
240229
/* End PBXNativeTarget section */
241230

242231
/* Begin PBXProject section */
@@ -254,10 +243,6 @@
254243
CreatedOnToolsVersion = 16.4;
255244
TestTargetID = A72106512E3016590073C515;
256245
};
257-
A72106692E30165B0073C515 = {
258-
CreatedOnToolsVersion = 16.4;
259-
TestTargetID = A72106512E3016590073C515;
260-
};
261246
};
262247
};
263248
buildConfigurationList = A721064D2E3016590073C515 /* Build configuration list for PBXProject "Recap" */;
@@ -282,7 +267,6 @@
282267
targets = (
283268
A72106512E3016590073C515 /* Recap */,
284269
A721065F2E30165B0073C515 /* RecapTests */,
285-
A72106692E30165B0073C515 /* RecapUITests */,
286270
);
287271
};
288272
/* End PBXProject section */
@@ -302,13 +286,6 @@
302286
);
303287
runOnlyForDeploymentPostprocessing = 0;
304288
};
305-
A72106682E30165B0073C515 /* Resources */ = {
306-
isa = PBXResourcesBuildPhase;
307-
buildActionMask = 2147483647;
308-
files = (
309-
);
310-
runOnlyForDeploymentPostprocessing = 0;
311-
};
312289
/* End PBXResourcesBuildPhase section */
313290

314291
/* Begin PBXSourcesBuildPhase section */
@@ -326,13 +303,6 @@
326303
);
327304
runOnlyForDeploymentPostprocessing = 0;
328305
};
329-
A72106662E30165B0073C515 /* Sources */ = {
330-
isa = PBXSourcesBuildPhase;
331-
buildActionMask = 2147483647;
332-
files = (
333-
);
334-
runOnlyForDeploymentPostprocessing = 0;
335-
};
336306
/* End PBXSourcesBuildPhase section */
337307

338308
/* Begin PBXTargetDependency section */
@@ -341,11 +311,6 @@
341311
target = A72106512E3016590073C515 /* Recap */;
342312
targetProxy = A72106612E30165B0073C515 /* PBXContainerItemProxy */;
343313
};
344-
A721066C2E30165B0073C515 /* PBXTargetDependency */ = {
345-
isa = PBXTargetDependency;
346-
target = A72106512E3016590073C515 /* Recap */;
347-
targetProxy = A721066B2E30165B0073C515 /* PBXContainerItemProxy */;
348-
};
349314
/* End PBXTargetDependency section */
350315

351316
/* Begin XCBuildConfiguration section */
@@ -571,40 +536,6 @@
571536
};
572537
name = Release;
573538
};
574-
A721067B2E30165B0073C515 /* Debug */ = {
575-
isa = XCBuildConfiguration;
576-
buildSettings = {
577-
CODE_SIGN_STYLE = Automatic;
578-
CURRENT_PROJECT_VERSION = 1;
579-
DEVELOPMENT_TEAM = EY7EQX6JC5;
580-
GENERATE_INFOPLIST_FILE = YES;
581-
MACOSX_DEPLOYMENT_TARGET = 15.0;
582-
MARKETING_VERSION = 1.0;
583-
PRODUCT_BUNDLE_IDENTIFIER = dev.rawa.RecapUITests;
584-
PRODUCT_NAME = "$(TARGET_NAME)";
585-
SWIFT_EMIT_LOC_STRINGS = NO;
586-
SWIFT_VERSION = 5.0;
587-
TEST_TARGET_NAME = Recap;
588-
};
589-
name = Debug;
590-
};
591-
A721067C2E30165B0073C515 /* Release */ = {
592-
isa = XCBuildConfiguration;
593-
buildSettings = {
594-
CODE_SIGN_STYLE = Automatic;
595-
CURRENT_PROJECT_VERSION = 1;
596-
DEVELOPMENT_TEAM = EY7EQX6JC5;
597-
GENERATE_INFOPLIST_FILE = YES;
598-
MACOSX_DEPLOYMENT_TARGET = 15.0;
599-
MARKETING_VERSION = 1.0;
600-
PRODUCT_BUNDLE_IDENTIFIER = dev.rawa.RecapUITests;
601-
PRODUCT_NAME = "$(TARGET_NAME)";
602-
SWIFT_EMIT_LOC_STRINGS = NO;
603-
SWIFT_VERSION = 5.0;
604-
TEST_TARGET_NAME = Recap;
605-
};
606-
name = Release;
607-
};
608539
/* End XCBuildConfiguration section */
609540

610541
/* Begin XCConfigurationList section */
@@ -635,15 +566,6 @@
635566
defaultConfigurationIsVisible = 0;
636567
defaultConfigurationName = Release;
637568
};
638-
A721067A2E30165B0073C515 /* Build configuration list for PBXNativeTarget "RecapUITests" */ = {
639-
isa = XCConfigurationList;
640-
buildConfigurations = (
641-
A721067B2E30165B0073C515 /* Debug */,
642-
A721067C2E30165B0073C515 /* Release */,
643-
);
644-
defaultConfigurationIsVisible = 0;
645-
defaultConfigurationName = Release;
646-
};
647569
/* End XCConfigurationList section */
648570

649571
/* Begin XCRemoteSwiftPackageReference section */

Recap.xctestplan

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
{
2+
"configurations" : [
3+
{
4+
"id" : "16849F79-ACBC-4890-8336-EF7543A98E8A",
5+
"name" : "Test Scheme Action",
6+
"options" : {
7+
8+
}
9+
}
10+
],
11+
"defaultOptions" : {
12+
"targetForVariableExpansion" : {
13+
"containerPath" : "container:Recap.xcodeproj",
14+
"identifier" : "A72106512E3016590073C515",
15+
"name" : "Recap"
16+
}
17+
},
18+
"testTargets" : [
19+
{
20+
"parallelizable" : true,
21+
"target" : {
22+
"containerPath" : "container:Recap.xcodeproj",
23+
"identifier" : "A721065F2E30165B0073C515",
24+
"name" : "RecapTests"
25+
}
26+
},
27+
{
28+
"parallelizable" : true,
29+
"target" : {
30+
"containerPath" : "container:Recap.xcodeproj",
31+
"identifier" : "A72106692E30165B0073C515",
32+
"name" : "RecapUITests"
33+
}
34+
],
35+
"version" : 1
36+
}

Recap/DependencyContainer/DependencyContainer+Services.swift

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ extension DependencyContainer {
1818
}
1919

2020
func makeMeetingDetectionService() -> any MeetingDetectionServiceType {
21-
MeetingDetectionService(audioProcessController: audioProcessController)
21+
MeetingDetectionService(audioProcessController: audioProcessController, permissionsHelper: makePermissionsHelper())
2222
}
2323

2424
func makeMeetingAppDetectionService() -> MeetingAppDetecting {
@@ -37,4 +37,12 @@ extension DependencyContainer {
3737
func makeNotificationService() -> NotificationServiceType {
3838
NotificationService()
3939
}
40+
41+
func makeKeychainService() -> KeychainServiceType {
42+
KeychainService()
43+
}
44+
45+
func makeKeychainAPIValidator() -> KeychainAPIValidatorType {
46+
KeychainAPIValidator(keychainService: keychainService)
47+
}
4048
}

Recap/DependencyContainer/DependencyContainer+ViewModels.swift

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,17 @@ extension DependencyContainer {
1818
GeneralSettingsViewModel(
1919
llmService: llmService,
2020
userPreferencesRepository: userPreferencesRepository,
21-
environmentValidator: EnvironmentValidator(),
21+
keychainAPIValidator: keychainAPIValidator,
22+
keychainService: keychainService,
2223
warningManager: warningManager
2324
)
2425
}
2526

2627
func makeMeetingDetectionSettingsViewModel() -> MeetingDetectionSettingsViewModel {
2728
MeetingDetectionSettingsViewModel(
2829
detectionService: meetingDetectionService,
29-
userPreferencesRepository: userPreferencesRepository
30+
userPreferencesRepository: userPreferencesRepository,
31+
permissionsHelper: makePermissionsHelper()
3032
)
3133
}
3234

Recap/DependencyContainer/DependencyContainer.swift

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ final class DependencyContainer {
3232
lazy var microphoneCapture: MicrophoneCaptureType = makeMicrophoneCapture()
3333
lazy var notificationService: NotificationServiceType = makeNotificationService()
3434
lazy var appSelectionCoordinator: AppSelectionCoordinatorType = makeAppSelectionCoordinator()
35+
lazy var keychainService: KeychainServiceType = makeKeychainService()
36+
lazy var keychainAPIValidator: KeychainAPIValidatorType = makeKeychainAPIValidator()
3537

3638
init(inMemory: Bool = false) {
3739
self.inMemory = inMemory
@@ -69,7 +71,8 @@ final class DependencyContainer {
6971
meetingDetectionService: meetingDetectionService,
7072
userPreferencesRepository: userPreferencesRepository,
7173
notificationService: notificationService,
72-
appSelectionCoordinator: appSelectionCoordinator
74+
appSelectionCoordinator: appSelectionCoordinator,
75+
permissionsHelper: makePermissionsHelper()
7376
)
7477
}
7578

Recap/Helpers/Permissions/PermissionsHelper.swift

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,4 +51,13 @@ final class PermissionsHelper: PermissionsHelperType {
5151
return true
5252
}
5353
}
54+
55+
func checkScreenCapturePermission() async -> Bool {
56+
do {
57+
let _ = try await SCShareableContent.current
58+
return true
59+
} catch {
60+
return false
61+
}
62+
}
5463
}

Recap/Helpers/Permissions/PermissionsHelperType.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,5 @@ protocol PermissionsHelperType: AnyObject {
1515
func checkMicrophonePermissionStatus() -> AVAuthorizationStatus
1616
func checkNotificationPermissionStatus() async -> Bool
1717
func checkScreenRecordingPermission() -> Bool
18+
func checkScreenCapturePermission() async -> Bool
1819
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import Foundation
2+
3+
final class KeychainAPIValidator: KeychainAPIValidatorType {
4+
private let keychainService: KeychainServiceType
5+
6+
init(keychainService: KeychainServiceType = KeychainService()) {
7+
self.keychainService = keychainService
8+
}
9+
10+
func validateOpenRouterAPI() -> APIValidationResult {
11+
do {
12+
guard let apiKey = try keychainService.retrieve(key: KeychainKey.openRouterApiKey.key),
13+
!apiKey.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty else {
14+
return .missingApiKey
15+
}
16+
17+
guard isValidOpenRouterAPIKeyFormat(apiKey) else {
18+
return .invalidApiKey
19+
}
20+
21+
return .valid
22+
} catch {
23+
return .missingApiKey
24+
}
25+
}
26+
27+
private func isValidOpenRouterAPIKeyFormat(_ apiKey: String) -> Bool {
28+
let trimmedKey = apiKey.trimmingCharacters(in: .whitespacesAndNewlines)
29+
return trimmedKey.hasPrefix("sk-or-") && trimmedKey.count > 10
30+
}
31+
}

0 commit comments

Comments
 (0)