@keeper-security/keeper-sdk-javascript 0.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 (173) hide show
  1. package/dist/auth/ConsoleAuthUI.d.ts +10 -0
  2. package/dist/auth/ConsoleAuthUI.js +152 -0
  3. package/dist/auth/ConsoleAuthUI.js.map +1 -0
  4. package/dist/auth/ConsoleLogin.d.ts +8 -0
  5. package/dist/auth/ConsoleLogin.js +266 -0
  6. package/dist/auth/ConsoleLogin.js.map +1 -0
  7. package/dist/auth/SessionManager.d.ts +66 -0
  8. package/dist/auth/SessionManager.js +211 -0
  9. package/dist/auth/SessionManager.js.map +1 -0
  10. package/dist/index.d.ts +17 -0
  11. package/dist/index.js +61 -0
  12. package/dist/index.js.map +1 -0
  13. package/dist/records/RecordOperations.d.ts +79 -0
  14. package/dist/records/RecordOperations.js +346 -0
  15. package/dist/records/RecordOperations.js.map +1 -0
  16. package/dist/records/RecordUtils.d.ts +36 -0
  17. package/dist/records/RecordUtils.js +224 -0
  18. package/dist/records/RecordUtils.js.map +1 -0
  19. package/dist/sharing/Sharing.d.ts +27 -0
  20. package/dist/sharing/Sharing.js +125 -0
  21. package/dist/sharing/Sharing.js.map +1 -0
  22. package/dist/src/auth/ConsoleAuthUI.d.ts +10 -0
  23. package/dist/src/auth/ConsoleAuthUI.js +161 -0
  24. package/dist/src/auth/ConsoleAuthUI.js.map +1 -0
  25. package/dist/src/auth/ConsoleLogin.d.ts +8 -0
  26. package/dist/src/auth/ConsoleLogin.js +311 -0
  27. package/dist/src/auth/ConsoleLogin.js.map +1 -0
  28. package/dist/src/auth/SessionManager.d.ts +67 -0
  29. package/dist/src/auth/SessionManager.js +212 -0
  30. package/dist/src/auth/SessionManager.js.map +1 -0
  31. package/dist/src/folders/FolderManager.d.ts +57 -0
  32. package/dist/src/folders/FolderManager.js +108 -0
  33. package/dist/src/folders/FolderManager.js.map +1 -0
  34. package/dist/src/folders/addFolder.d.ts +32 -0
  35. package/dist/src/folders/addFolder.js +207 -0
  36. package/dist/src/folders/addFolder.js.map +1 -0
  37. package/dist/src/folders/changeDirectory.d.ts +19 -0
  38. package/dist/src/folders/changeDirectory.js +171 -0
  39. package/dist/src/folders/changeDirectory.js.map +1 -0
  40. package/dist/src/folders/deleteFolder.d.ts +17 -0
  41. package/dist/src/folders/deleteFolder.js +237 -0
  42. package/dist/src/folders/deleteFolder.js.map +1 -0
  43. package/dist/src/folders/folderHelpers.d.ts +48 -0
  44. package/dist/src/folders/folderHelpers.js +100 -0
  45. package/dist/src/folders/folderHelpers.js.map +1 -0
  46. package/dist/src/folders/folderTree.d.ts +29 -0
  47. package/dist/src/folders/folderTree.js +250 -0
  48. package/dist/src/folders/folderTree.js.map +1 -0
  49. package/dist/src/folders/getFolder.d.ts +56 -0
  50. package/dist/src/folders/getFolder.js +143 -0
  51. package/dist/src/folders/getFolder.js.map +1 -0
  52. package/dist/src/folders/listFolder.d.ts +48 -0
  53. package/dist/src/folders/listFolder.js +276 -0
  54. package/dist/src/folders/listFolder.js.map +1 -0
  55. package/dist/src/folders/updateFolder.d.ts +31 -0
  56. package/dist/src/folders/updateFolder.js +137 -0
  57. package/dist/src/folders/updateFolder.js.map +1 -0
  58. package/dist/src/index.d.ts +49 -0
  59. package/dist/src/index.js +151 -0
  60. package/dist/src/index.js.map +1 -0
  61. package/dist/src/records/RecordOperations.d.ts +80 -0
  62. package/dist/src/records/RecordOperations.js +356 -0
  63. package/dist/src/records/RecordOperations.js.map +1 -0
  64. package/dist/src/records/RecordUtils.d.ts +37 -0
  65. package/dist/src/records/RecordUtils.js +263 -0
  66. package/dist/src/records/RecordUtils.js.map +1 -0
  67. package/dist/src/records/Totp.d.ts +14 -0
  68. package/dist/src/records/Totp.js +111 -0
  69. package/dist/src/records/Totp.js.map +1 -0
  70. package/dist/src/sharedFolders/SharedFolderManager.d.ts +20 -0
  71. package/dist/src/sharedFolders/SharedFolderManager.js +33 -0
  72. package/dist/src/sharedFolders/SharedFolderManager.js.map +1 -0
  73. package/dist/src/sharedFolders/listSharedFolders.d.ts +29 -0
  74. package/dist/src/sharedFolders/listSharedFolders.js +127 -0
  75. package/dist/src/sharedFolders/listSharedFolders.js.map +1 -0
  76. package/dist/src/sharedFolders/shareFolder.d.ts +36 -0
  77. package/dist/src/sharedFolders/shareFolder.js +352 -0
  78. package/dist/src/sharedFolders/shareFolder.js.map +1 -0
  79. package/dist/src/sharing/Sharing.d.ts +50 -0
  80. package/dist/src/sharing/Sharing.js +195 -0
  81. package/dist/src/sharing/Sharing.js.map +1 -0
  82. package/dist/src/storage/InMemoryStorage.d.ts +24 -0
  83. package/dist/src/storage/InMemoryStorage.js +139 -0
  84. package/dist/src/storage/InMemoryStorage.js.map +1 -0
  85. package/dist/src/teams/TeamManager.d.ts +17 -0
  86. package/dist/src/teams/TeamManager.js +38 -0
  87. package/dist/src/teams/TeamManager.js.map +1 -0
  88. package/dist/src/teams/enterpriseData.d.ts +106 -0
  89. package/dist/src/teams/enterpriseData.js +319 -0
  90. package/dist/src/teams/enterpriseData.js.map +1 -0
  91. package/dist/src/teams/listTeams.d.ts +42 -0
  92. package/dist/src/teams/listTeams.js +308 -0
  93. package/dist/src/teams/listTeams.js.map +1 -0
  94. package/dist/src/teams/viewTeam.d.ts +35 -0
  95. package/dist/src/teams/viewTeam.js +177 -0
  96. package/dist/src/teams/viewTeam.js.map +1 -0
  97. package/dist/src/utils/Logger.d.ts +28 -0
  98. package/dist/src/utils/Logger.js +62 -0
  99. package/dist/src/utils/Logger.js.map +1 -0
  100. package/dist/src/utils/constants.d.ts +50 -0
  101. package/dist/src/utils/constants.js +64 -0
  102. package/dist/src/utils/constants.js.map +1 -0
  103. package/dist/src/utils/errors.d.ts +10 -0
  104. package/dist/src/utils/errors.js +117 -0
  105. package/dist/src/utils/errors.js.map +1 -0
  106. package/dist/src/utils/guards.d.ts +7 -0
  107. package/dist/src/utils/guards.js +29 -0
  108. package/dist/src/utils/guards.js.map +1 -0
  109. package/dist/src/utils/index.d.ts +7 -0
  110. package/dist/src/utils/index.js +39 -0
  111. package/dist/src/utils/index.js.map +1 -0
  112. package/dist/src/utils/patterns.d.ts +9 -0
  113. package/dist/src/utils/patterns.js +20 -0
  114. package/dist/src/utils/patterns.js.map +1 -0
  115. package/dist/src/utils/types.d.ts +12 -0
  116. package/dist/src/utils/types.js +3 -0
  117. package/dist/src/utils/types.js.map +1 -0
  118. package/dist/src/vault/KeeperVault.d.ts +116 -0
  119. package/dist/src/vault/KeeperVault.js +443 -0
  120. package/dist/src/vault/KeeperVault.js.map +1 -0
  121. package/dist/storage/InMemoryStorage.d.ts +24 -0
  122. package/dist/storage/InMemoryStorage.js +132 -0
  123. package/dist/storage/InMemoryStorage.js.map +1 -0
  124. package/dist/utils/Logger.d.ts +28 -0
  125. package/dist/utils/Logger.js +62 -0
  126. package/dist/utils/Logger.js.map +1 -0
  127. package/dist/utils/constants.d.ts +26 -0
  128. package/dist/utils/constants.js +37 -0
  129. package/dist/utils/constants.js.map +1 -0
  130. package/dist/utils/errors.d.ts +10 -0
  131. package/dist/utils/errors.js +117 -0
  132. package/dist/utils/errors.js.map +1 -0
  133. package/dist/utils/index.d.ts +4 -0
  134. package/dist/utils/index.js +22 -0
  135. package/dist/utils/index.js.map +1 -0
  136. package/dist/vault/KeeperVault.d.ts +72 -0
  137. package/dist/vault/KeeperVault.js +338 -0
  138. package/dist/vault/KeeperVault.js.map +1 -0
  139. package/package.json +32 -0
  140. package/src/auth/ConsoleAuthUI.ts +169 -0
  141. package/src/auth/ConsoleLogin.ts +351 -0
  142. package/src/auth/SessionManager.ts +293 -0
  143. package/src/folders/FolderManager.ts +174 -0
  144. package/src/folders/addFolder.ts +294 -0
  145. package/src/folders/changeDirectory.ts +217 -0
  146. package/src/folders/deleteFolder.ts +293 -0
  147. package/src/folders/folderHelpers.ts +99 -0
  148. package/src/folders/folderTree.ts +321 -0
  149. package/src/folders/getFolder.ts +234 -0
  150. package/src/folders/listFolder.ts +358 -0
  151. package/src/folders/updateFolder.ts +210 -0
  152. package/src/index.ts +242 -0
  153. package/src/records/RecordOperations.ts +549 -0
  154. package/src/records/RecordUtils.ts +282 -0
  155. package/src/records/Totp.ts +119 -0
  156. package/src/sharedFolders/SharedFolderManager.ts +57 -0
  157. package/src/sharedFolders/listSharedFolders.ts +173 -0
  158. package/src/sharedFolders/shareFolder.ts +457 -0
  159. package/src/sharing/Sharing.ts +282 -0
  160. package/src/storage/InMemoryStorage.ts +163 -0
  161. package/src/teams/TeamManager.ts +61 -0
  162. package/src/teams/enterpriseData.ts +453 -0
  163. package/src/teams/listTeams.ts +373 -0
  164. package/src/teams/viewTeam.ts +248 -0
  165. package/src/utils/Logger.ts +71 -0
  166. package/src/utils/constants.ts +63 -0
  167. package/src/utils/errors.ts +108 -0
  168. package/src/utils/guards.ts +24 -0
  169. package/src/utils/index.ts +22 -0
  170. package/src/utils/patterns.ts +20 -0
  171. package/src/utils/types.ts +11 -0
  172. package/src/vault/KeeperVault.ts +612 -0
  173. package/tsconfig.json +16 -0
@@ -0,0 +1,174 @@
1
+ import type { Auth } from '@keeper-security/keeperapi'
2
+ import { InMemoryStorage } from '../storage/InMemoryStorage'
3
+ import { KeeperSdkError, ResultCodes } from '../utils'
4
+ import { addFolder, mkdir } from './addFolder'
5
+ import type { AddFolderInput, AddFolderResult, MkdirOptions } from './addFolder'
6
+ import {
7
+ changeDirectory,
8
+ createVaultFolderSession,
9
+ findParentFolderUid,
10
+ getWorkingFolderDisplayName,
11
+ resolveSingleFolder,
12
+ splitPathComponents,
13
+ tryResolvePath,
14
+ } from './changeDirectory'
15
+ import type { ChangeDirectoryResult, TryResolvePathResult, VaultFolderSession } from './changeDirectory'
16
+ import { buildFolderDeleteObject, deleteFolder, resolveRmdirPatternsToFolderUids, rmdir } from './deleteFolder'
17
+ import type { DeleteFolderResult, RmdirOptions } from './deleteFolder'
18
+ import { buildFolderTree, folderTreeAscii, renderFolderTreeAscii } from './folderTree'
19
+ import type { FolderTreeBuildOptions, FolderTreeResult } from './folderTree'
20
+ import { findFolder, getFolder } from './getFolder'
21
+ import type { FoundFolder, GetFolderOptions, GetFolderResult } from './getFolder'
22
+ import { findFolderUidByNameOrUid, listFolder, listRootUserFolders, listVaultRootFolders } from './listFolder'
23
+ import type { ListFolderFolderSimple, ListFolderOptions, ListFolderResult } from './listFolder'
24
+ import { renameFolder, updateFolder, updateSharedFolderPermissions } from './updateFolder'
25
+ import type { RenameFolderResult, UpdateFolderInput, UpdateFolderResult } from './updateFolder'
26
+
27
+ export type AuthProvider = () => Auth
28
+
29
+ export type SharedFolderPermissionsInput = {
30
+ manageUsers?: boolean | null
31
+ manageRecords?: boolean | null
32
+ canShare?: boolean | null
33
+ canEdit?: boolean | null
34
+ }
35
+
36
+ export class FolderManager {
37
+ private readonly storage: InMemoryStorage
38
+ private readonly session: VaultFolderSession
39
+ private readonly authProvider: AuthProvider
40
+
41
+ constructor(storage: InMemoryStorage, session: VaultFolderSession, authProvider: AuthProvider) {
42
+ this.storage = storage
43
+ this.session = session
44
+ this.authProvider = authProvider
45
+ }
46
+
47
+ public static createSession(): VaultFolderSession {
48
+ return createVaultFolderSession()
49
+ }
50
+
51
+ public static splitPathComponents(path: string): string[] {
52
+ return splitPathComponents(path)
53
+ }
54
+
55
+ public getSession(): VaultFolderSession {
56
+ return this.session
57
+ }
58
+
59
+ public getCurrentFolderUid(): string | null {
60
+ return this.session.currentFolderUid
61
+ }
62
+
63
+ public getWorkingFolderDisplayName(): string {
64
+ return getWorkingFolderDisplayName(this.storage, this.session.currentFolderUid)
65
+ }
66
+
67
+ private requireAuth(): Auth {
68
+ const auth = this.authProvider()
69
+ if (!auth) {
70
+ throw new KeeperSdkError('Not logged in. Call login() first.', ResultCodes.NOT_LOGGED_IN)
71
+ }
72
+ return auth
73
+ }
74
+
75
+ public async listFolder(options: ListFolderOptions = {}): Promise<ListFolderResult> {
76
+ return listFolder(this.storage, options)
77
+ }
78
+
79
+ public listRootUserFolders() {
80
+ return listRootUserFolders(this.storage)
81
+ }
82
+
83
+ public async listVaultRootFolders(): Promise<{
84
+ rows: ListFolderFolderSimple[]
85
+ promotedRootSharedUids: Set<string>
86
+ }> {
87
+ return listVaultRootFolders(this.storage)
88
+ }
89
+
90
+ public findFolderUidByNameOrUid(nameOrUid: string): string | undefined {
91
+ return findFolderUidByNameOrUid(this.storage, nameOrUid)
92
+ }
93
+
94
+ public findFolder(nameOrUid: string): FoundFolder | undefined {
95
+ return findFolder(this.storage, nameOrUid)
96
+ }
97
+
98
+ public async findParentFolderUid(folderUid: string): Promise<string | null> {
99
+ return findParentFolderUid(this.storage, folderUid)
100
+ }
101
+
102
+ public async getFolder(uidOrName: string, options: GetFolderOptions = {}): Promise<GetFolderResult> {
103
+ return getFolder(this.storage, uidOrName, options)
104
+ }
105
+
106
+ public async changeDirectory(path: string): Promise<ChangeDirectoryResult> {
107
+ return changeDirectory(this.storage, this.session, path)
108
+ }
109
+
110
+ public async tryResolvePath(path: string): Promise<TryResolvePathResult> {
111
+ return tryResolvePath(this.storage, this.session, path)
112
+ }
113
+
114
+ public async resolveSingleFolder(folderName: string): Promise<ChangeDirectoryResult> {
115
+ return resolveSingleFolder(this.storage, this.session, folderName)
116
+ }
117
+
118
+ public async addFolder(input: AddFolderInput): Promise<AddFolderResult> {
119
+ return addFolder(this.requireAuth(), this.storage, input)
120
+ }
121
+
122
+ public async mkdir(
123
+ path: string,
124
+ options: MkdirOptions = {}
125
+ ): Promise<{ folderUid: string; success: boolean; message?: string }> {
126
+ return mkdir(this.requireAuth(), this.storage, this.session, path, options)
127
+ }
128
+
129
+ public async updateFolder(input: UpdateFolderInput): Promise<UpdateFolderResult> {
130
+ return updateFolder(this.requireAuth(), this.storage, input)
131
+ }
132
+
133
+ public async renameFolder(folderPath: string, newName: string): Promise<RenameFolderResult> {
134
+ return renameFolder(this.requireAuth(), this.storage, this.session, folderPath, newName)
135
+ }
136
+
137
+ public async updateSharedFolderPermissions(
138
+ sharedFolderUid: string,
139
+ permissions: SharedFolderPermissionsInput
140
+ ): Promise<UpdateFolderResult> {
141
+ return updateSharedFolderPermissions(this.requireAuth(), this.storage, sharedFolderUid, permissions)
142
+ }
143
+
144
+ public async deleteFolder(
145
+ folderRefs: string[],
146
+ confirm?: (summary: string) => boolean | Promise<boolean>
147
+ ): Promise<DeleteFolderResult> {
148
+ return deleteFolder(this.requireAuth(), this.storage, folderRefs, confirm)
149
+ }
150
+
151
+ public async rmdir(patterns: string[], options: RmdirOptions = {}): Promise<DeleteFolderResult> {
152
+ return rmdir(this.requireAuth(), this.storage, this.session, patterns, options)
153
+ }
154
+
155
+ public async resolveRmdirPatternsToFolderUids(pattern: string): Promise<Set<string>> {
156
+ return resolveRmdirPatternsToFolderUids(this.storage, this.session, pattern)
157
+ }
158
+
159
+ public async buildFolderDeleteObject(folderUid: string) {
160
+ return buildFolderDeleteObject(this.storage, folderUid)
161
+ }
162
+
163
+ public async buildFolderTree(options: FolderTreeBuildOptions = {}): Promise<FolderTreeResult> {
164
+ return buildFolderTree(this.storage, this.session, options)
165
+ }
166
+
167
+ public async folderTreeAscii(options: FolderTreeBuildOptions = {}): Promise<string> {
168
+ return folderTreeAscii(this.storage, this.session, options)
169
+ }
170
+
171
+ public renderFolderTreeAscii(tree: FolderTreeResult): string {
172
+ return renderFolderTreeAscii(tree)
173
+ }
174
+ }
@@ -0,0 +1,294 @@
1
+ import type { Auth, FolderAddRequest } from '@keeper-security/keeperapi'
2
+ import {
3
+ encryptForStorage,
4
+ encryptObjectForStorage,
5
+ folderAddCommand,
6
+ generateEncryptionKey,
7
+ generateUid,
8
+ platform,
9
+ } from '@keeper-security/keeperapi'
10
+ import type { DSharedFolder, DSharedFolderFolder, DUserFolder } from '@keeper-security/keeperapi'
11
+ import { InMemoryStorage } from '../storage/InMemoryStorage'
12
+ import { isBoolean, KeeperSdkError, extractErrorMessage } from '../utils'
13
+ import { listFolder } from './listFolder'
14
+ import { tryResolvePath, splitPathComponents, type VaultFolderSession } from './changeDirectory'
15
+ import { FolderKind, FolderResultStatus, ParentFolderKind } from './folderHelpers'
16
+
17
+ type NewFolderKind = FolderKind
18
+
19
+ export type AddFolderInput = {
20
+ folderName: string
21
+ isSharedFolder?: boolean
22
+ parentUid?: string | null
23
+ manageUsers?: boolean
24
+ manageRecords?: boolean
25
+ canShare?: boolean
26
+ canEdit?: boolean
27
+ }
28
+
29
+ export type AddFolderResult = {
30
+ folderUid: string
31
+ success: boolean
32
+ message?: string
33
+ }
34
+
35
+ export type MkdirOptions = {
36
+ sharedFolder?: boolean
37
+ userFolder?: boolean
38
+ grantAll?: boolean
39
+ manageUsers?: boolean
40
+ manageRecords?: boolean
41
+ canShare?: boolean
42
+ canEdit?: boolean
43
+ }
44
+
45
+ type ParentContext = {
46
+ kind: ParentFolderKind
47
+ sharedScopeUid: string | null
48
+ }
49
+
50
+ function resolveParentContext(storage: InMemoryStorage, parentUid: string | null): ParentContext {
51
+ if (parentUid === null || parentUid === '') {
52
+ return { kind: ParentFolderKind.VirtualRoot, sharedScopeUid: null }
53
+ }
54
+ if (storage.getByUid<DUserFolder>(FolderKind.UserFolder, parentUid)) {
55
+ return { kind: ParentFolderKind.UserFolder, sharedScopeUid: null }
56
+ }
57
+ const sharedFolder = storage.getByUid<DSharedFolder>(FolderKind.SharedFolder, parentUid)
58
+ if (sharedFolder) {
59
+ return { kind: ParentFolderKind.SharedFolder, sharedScopeUid: sharedFolder.uid }
60
+ }
61
+ const sharedFolderFolder = storage.getByUid<DSharedFolderFolder>(FolderKind.SharedFolderFolder, parentUid)
62
+ if (sharedFolderFolder) {
63
+ return {
64
+ kind: ParentFolderKind.SharedFolderFolder,
65
+ sharedScopeUid: sharedFolderFolder.sharedFolderUid,
66
+ }
67
+ }
68
+ throw new KeeperSdkError(`Parent folder "${parentUid}" not found`, 'folder_not_found')
69
+ }
70
+
71
+ function decideNewFolderType(parent: ParentContext, isSharedFolder: boolean): NewFolderKind {
72
+ if (isSharedFolder) {
73
+ if (parent.kind !== ParentFolderKind.UserFolder && parent.kind !== ParentFolderKind.VirtualRoot) {
74
+ throw new KeeperSdkError(
75
+ 'Shared folders cannot be nested inside other shared folders.',
76
+ 'shared_folder_nested'
77
+ )
78
+ }
79
+ return FolderKind.SharedFolder
80
+ }
81
+ if (parent.kind === ParentFolderKind.VirtualRoot || parent.kind === ParentFolderKind.UserFolder) {
82
+ return FolderKind.UserFolder
83
+ }
84
+ return FolderKind.SharedFolderFolder
85
+ }
86
+
87
+ async function getEncryptionKeyForNewFolder(
88
+ auth: Auth,
89
+ storage: InMemoryStorage,
90
+ folderType: NewFolderKind,
91
+ sharedScopeUid: string | null
92
+ ): Promise<Uint8Array> {
93
+ if (folderType === FolderKind.SharedFolderFolder) {
94
+ if (!sharedScopeUid) {
95
+ throw new KeeperSdkError('Shared folder scope could not be resolved.', 'shared_folder_scope_missing')
96
+ }
97
+ const sharedFolderKey = await storage.getKeyBytes(sharedScopeUid)
98
+ if (!sharedFolderKey) {
99
+ throw new KeeperSdkError(
100
+ 'Shared folder encryption key not available. Sync the vault and try again.',
101
+ 'shared_folder_key_missing'
102
+ )
103
+ }
104
+ return sharedFolderKey
105
+ }
106
+ if (!auth.dataKey) {
107
+ throw new KeeperSdkError('Data key not available. Ensure you are logged in.', 'data_key_missing')
108
+ }
109
+ return auth.dataKey
110
+ }
111
+
112
+ async function findChildFolderUidByName(
113
+ storage: InMemoryStorage,
114
+ parentUid: string | null,
115
+ name: string
116
+ ): Promise<string | undefined> {
117
+ const result = await listFolder(storage, {
118
+ folderUid: parentUid === null ? undefined : parentUid,
119
+ showFolders: true,
120
+ showRecords: false,
121
+ })
122
+ const trimmedName = name.trim()
123
+ const lowerName = trimmedName.toLowerCase()
124
+ for (const folder of result.folders) {
125
+ if (folder.uid === trimmedName) return folder.uid
126
+ if (folder.name.trim() === trimmedName) return folder.uid
127
+ if (folder.name.trim().toLowerCase() === lowerName) return folder.uid
128
+ }
129
+ return undefined
130
+ }
131
+
132
+ export async function addFolder(auth: Auth, storage: InMemoryStorage, input: AddFolderInput): Promise<AddFolderResult> {
133
+ const name = input.folderName?.trim()
134
+ if (!name) {
135
+ throw new KeeperSdkError('Folder name cannot be empty.', 'folder_name_required')
136
+ }
137
+
138
+ const parentUid = input.parentUid === undefined || input.parentUid === '' ? null : input.parentUid
139
+ const parent = resolveParentContext(storage, parentUid)
140
+
141
+ const isShared = input.isSharedFolder === true
142
+ const folderType = decideNewFolderType(parent, isShared)
143
+
144
+ const folderUid = generateUid()
145
+ const folderKey = generateEncryptionKey()
146
+
147
+ const sharedScope = folderType === FolderKind.SharedFolderFolder ? parent.sharedScopeUid : null
148
+
149
+ const encryptionKey = await getEncryptionKeyForNewFolder(auth, storage, folderType, sharedScope)
150
+
151
+ const request: FolderAddRequest = {
152
+ folder_uid: folderUid,
153
+ folder_type: folderType,
154
+ key: await encryptForStorage(folderKey, encryptionKey),
155
+ data: await encryptObjectForStorage({ name, title: name }, folderKey),
156
+ link: false,
157
+ }
158
+
159
+ if (parentUid) {
160
+ request.parent_uid = parentUid
161
+ }
162
+ if (folderType === FolderKind.SharedFolderFolder && sharedScope) {
163
+ request.shared_folder_uid = sharedScope
164
+ }
165
+
166
+ if (folderType === FolderKind.SharedFolder) {
167
+ request.name = await encryptForStorage(platform.stringToBytes(name), folderKey)
168
+ request.manage_users = isBoolean(input.manageUsers) ? input.manageUsers : false
169
+ request.manage_records = isBoolean(input.manageRecords) ? input.manageRecords : false
170
+ request.can_edit = isBoolean(input.canEdit) ? input.canEdit : false
171
+ request.can_share = isBoolean(input.canShare) ? input.canShare : false
172
+ }
173
+
174
+ try {
175
+ const response = await auth.executeRestCommand(folderAddCommand(request))
176
+ const succeeded =
177
+ response.result === FolderResultStatus.Success || response.result_code === FolderResultStatus.Success
178
+ if (!succeeded) {
179
+ const reason =
180
+ response.message ||
181
+ response.result_code ||
182
+ `folder_add failed for "${name}" (uid=${folderUid}, type=${folderType}): server returned no message or result_code`
183
+ return {
184
+ folderUid,
185
+ success: false,
186
+ message: reason,
187
+ }
188
+ }
189
+ return { folderUid, success: true }
190
+ } catch (err) {
191
+ return {
192
+ folderUid,
193
+ success: false,
194
+ message: `folder_add failed for "${name}" (uid=${folderUid}, type=${folderType}): ${extractErrorMessage(err)}`,
195
+ }
196
+ }
197
+ }
198
+
199
+ export async function mkdir(
200
+ auth: Auth,
201
+ storage: InMemoryStorage,
202
+ session: VaultFolderSession,
203
+ path: string,
204
+ options: MkdirOptions = {}
205
+ ): Promise<{ folderUid: string; success: boolean; message?: string }> {
206
+ const trimmed = path.trim()
207
+ if (!trimmed) {
208
+ throw new KeeperSdkError('Folder path cannot be empty.', 'folder_path_required')
209
+ }
210
+
211
+ if (options.sharedFolder && options.userFolder) {
212
+ throw new KeeperSdkError('Use only one of sharedFolder (-sf) or userFolder (-uf).', 'mkdir_flags_conflict')
213
+ }
214
+
215
+ const { folderUid: baseUid, remaining } = await tryResolvePath(storage, session, trimmed)
216
+ if (!remaining.trim()) {
217
+ throw new KeeperSdkError(`Folder "${trimmed}" already exists.`, 'folder_already_exists')
218
+ }
219
+
220
+ const grantAll = options.grantAll === true
221
+ let manageUsers = isBoolean(options.manageUsers) ? options.manageUsers : false
222
+ let manageRecords = isBoolean(options.manageRecords) ? options.manageRecords : false
223
+ let canShare = isBoolean(options.canShare) ? options.canShare : false
224
+ let canEdit = isBoolean(options.canEdit) ? options.canEdit : false
225
+ if (grantAll) {
226
+ manageUsers = true
227
+ manageRecords = true
228
+ canShare = true
229
+ canEdit = true
230
+ }
231
+
232
+ const segments = splitPathComponents(remaining)
233
+ .map((segment) => segment.trim())
234
+ .filter((segment) => segment !== '' && segment !== '.')
235
+
236
+ if (segments.length === 0) {
237
+ throw new KeeperSdkError(`Folder "${trimmed}" already exists.`, 'folder_already_exists')
238
+ }
239
+
240
+ let currentParent: string | null = baseUid
241
+ let lastResult: AddFolderResult = {
242
+ folderUid: '',
243
+ success: false,
244
+ message: 'not run',
245
+ }
246
+
247
+ for (let segmentIndex = 0; segmentIndex < segments.length; segmentIndex++) {
248
+ const segment = segments[segmentIndex]
249
+ const isLastSegment = segmentIndex === segments.length - 1
250
+ const existingChildUid = await findChildFolderUidByName(storage, currentParent, segment)
251
+ if (existingChildUid) {
252
+ if (isLastSegment) {
253
+ throw new KeeperSdkError(`Folder "${segment}" already exists.`, 'folder_already_exists')
254
+ }
255
+ currentParent = existingChildUid
256
+ continue
257
+ }
258
+
259
+ const createAsSharedFolder = isLastSegment && options.sharedFolder === true
260
+
261
+ const parentContext = resolveParentContext(storage, currentParent)
262
+ if (
263
+ createAsSharedFolder &&
264
+ parentContext.kind !== ParentFolderKind.UserFolder &&
265
+ parentContext.kind !== ParentFolderKind.VirtualRoot
266
+ ) {
267
+ throw new KeeperSdkError(
268
+ 'Shared folders can only be created under a personal folder.',
269
+ 'shared_folder_invalid_parent'
270
+ )
271
+ }
272
+
273
+ lastResult = await addFolder(auth, storage, {
274
+ folderName: segment,
275
+ parentUid: currentParent,
276
+ isSharedFolder: createAsSharedFolder,
277
+ manageUsers: isLastSegment && createAsSharedFolder ? manageUsers : undefined,
278
+ manageRecords: isLastSegment && createAsSharedFolder ? manageRecords : undefined,
279
+ canShare: isLastSegment && createAsSharedFolder ? canShare : undefined,
280
+ canEdit: isLastSegment && createAsSharedFolder ? canEdit : undefined,
281
+ })
282
+
283
+ if (!lastResult.success) {
284
+ return {
285
+ folderUid: lastResult.folderUid,
286
+ success: false,
287
+ message: lastResult.message,
288
+ }
289
+ }
290
+ currentParent = lastResult.folderUid
291
+ }
292
+
293
+ return { folderUid: lastResult.folderUid, success: true }
294
+ }
@@ -0,0 +1,217 @@
1
+ import type { DSharedFolder, DSharedFolderFolder, DUserFolder } from '@keeper-security/keeperapi'
2
+ import { InMemoryStorage } from '../storage/InMemoryStorage'
3
+ import { KeeperSdkError } from '../utils'
4
+ import { listFolder, listRootUserFolders } from './listFolder'
5
+ import type { ListFolderFolderSimple } from './listFolder'
6
+ import { FolderKind, VaultObjectKind, sharedFolderFolderName, sharedFolderName, userFolderName } from './folderHelpers'
7
+
8
+ const VAULT_ROOT_DISPLAY_NAME = 'My Vault'
9
+
10
+ const ESCAPED_SEPARATOR_PLACEHOLDER = '\x00'
11
+
12
+ export type VaultFolderSession = {
13
+ currentFolderUid: string | null
14
+ }
15
+
16
+ export type ChangeDirectoryResult = {
17
+ folderUid: string | null
18
+ name: string
19
+ }
20
+
21
+ export function createVaultFolderSession(): VaultFolderSession {
22
+ return { currentFolderUid: null }
23
+ }
24
+
25
+ function getFolderEntryByUid(storage: InMemoryStorage, uid: string): ListFolderFolderSimple | undefined {
26
+ const userFolder = storage.getByUid<DUserFolder>(FolderKind.UserFolder, uid)
27
+ if (userFolder) {
28
+ return { uid: userFolder.uid, name: userFolderName(userFolder), folderKind: FolderKind.UserFolder }
29
+ }
30
+ const sharedFolder = storage.getByUid<DSharedFolder>(FolderKind.SharedFolder, uid)
31
+ if (sharedFolder) {
32
+ return {
33
+ uid: sharedFolder.uid,
34
+ name: sharedFolderName(sharedFolder),
35
+ folderKind: FolderKind.SharedFolder,
36
+ }
37
+ }
38
+ const sharedFolderFolder = storage.getByUid<DSharedFolderFolder>(FolderKind.SharedFolderFolder, uid)
39
+ if (sharedFolderFolder) {
40
+ return {
41
+ uid: sharedFolderFolder.uid,
42
+ name: sharedFolderFolderName(sharedFolderFolder),
43
+ folderKind: FolderKind.SharedFolderFolder,
44
+ }
45
+ }
46
+ return undefined
47
+ }
48
+
49
+ export async function findParentFolderUid(storage: InMemoryStorage, folderUid: string): Promise<string | null> {
50
+ const rootFolderUids = new Set((await listRootUserFolders(storage)).map((folder) => folder.uid))
51
+ if (rootFolderUids.has(folderUid)) return null
52
+
53
+ const parentKinds = [
54
+ FolderKind.UserFolder,
55
+ FolderKind.SharedFolder,
56
+ FolderKind.SharedFolderFolder,
57
+ VaultObjectKind.Team,
58
+ ] as const
59
+ for (const kind of parentKinds) {
60
+ for (const candidate of storage.getAll(kind)) {
61
+ const candidateUid = (candidate as { uid: string }).uid
62
+ const dependencies = (await storage.getDependencies(candidateUid)) || []
63
+ for (const dependency of dependencies) {
64
+ if (dependency.uid !== folderUid) continue
65
+ if (
66
+ dependency.kind === FolderKind.UserFolder ||
67
+ dependency.kind === FolderKind.SharedFolder ||
68
+ dependency.kind === FolderKind.SharedFolderFolder
69
+ ) {
70
+ return candidateUid
71
+ }
72
+ }
73
+ }
74
+ }
75
+
76
+ return null
77
+ }
78
+
79
+ async function listFolderChildrenForCd(
80
+ storage: InMemoryStorage,
81
+ parentUid: string | null
82
+ ): Promise<ListFolderFolderSimple[]> {
83
+ const result = await listFolder(storage, {
84
+ folderUid: parentUid === null ? undefined : parentUid,
85
+ showFolders: true,
86
+ showRecords: false,
87
+ })
88
+ return result.folders
89
+ }
90
+
91
+ function findChildFolder(
92
+ children: ListFolderFolderSimple[],
93
+ component: string
94
+ ): ListFolderFolderSimple | undefined {
95
+ const trimmedComponent = component.trim()
96
+ if (!trimmedComponent) return undefined
97
+ const matchByUid = children.find((child) => child.uid === trimmedComponent)
98
+ if (matchByUid) return matchByUid
99
+ const exactNameMatch = children.find((child) => child.name.trim() === trimmedComponent)
100
+ if (exactNameMatch) return exactNameMatch
101
+ const lowerComponent = trimmedComponent.toLowerCase()
102
+ return children.find((child) => child.name.trim().toLowerCase() === lowerComponent)
103
+ }
104
+
105
+ export function splitPathComponents(path: string): string[] {
106
+ const escaped = path.replace(/\/\//g, ESCAPED_SEPARATOR_PLACEHOLDER)
107
+ return escaped.split('/').map((segment) => segment.replace(/\x00/g, '/'))
108
+ }
109
+
110
+ export type TryResolvePathResult = {
111
+ folderUid: string | null
112
+ remaining: string
113
+ }
114
+
115
+ export async function tryResolvePath(
116
+ storage: InMemoryStorage,
117
+ session: VaultFolderSession,
118
+ path: string
119
+ ): Promise<TryResolvePathResult> {
120
+ const trimmed = path.trim()
121
+ if (!trimmed) {
122
+ return { folderUid: session.currentFolderUid, remaining: '' }
123
+ }
124
+
125
+ const direct = getFolderEntryByUid(storage, trimmed)
126
+ if (direct) {
127
+ return { folderUid: direct.uid, remaining: '' }
128
+ }
129
+
130
+ const absolute = trimmed.startsWith('/') && !trimmed.startsWith('//')
131
+ const pathToWalk = absolute ? trimmed.slice(1) : trimmed
132
+ const components = splitPathComponents(pathToWalk)
133
+
134
+ let folderUid: string | null = absolute ? null : session.currentFolderUid || null
135
+
136
+ if (!absolute && folderUid !== null) {
137
+ if (!getFolderEntryByUid(storage, folderUid)) {
138
+ folderUid = null
139
+ }
140
+ }
141
+
142
+ let componentIndex = 0
143
+ while (componentIndex < components.length) {
144
+ const component = components[componentIndex]
145
+ componentIndex++
146
+
147
+ if (component === '' || component === '.') {
148
+ continue
149
+ }
150
+
151
+ if (component === '..') {
152
+ if (folderUid === null) {
153
+ continue
154
+ }
155
+ folderUid = await findParentFolderUid(storage, folderUid)
156
+ continue
157
+ }
158
+
159
+ const children = await listFolderChildrenForCd(storage, folderUid)
160
+ const match = findChildFolder(children, component)
161
+ if (match) {
162
+ folderUid = match.uid
163
+ } else {
164
+ const remaining = [component, ...components.slice(componentIndex)].join('/')
165
+ return { folderUid, remaining }
166
+ }
167
+ }
168
+
169
+ return { folderUid, remaining: '' }
170
+ }
171
+
172
+ export async function resolveSingleFolder(
173
+ storage: InMemoryStorage,
174
+ session: VaultFolderSession,
175
+ folderName: string
176
+ ): Promise<ChangeDirectoryResult> {
177
+ const trimmed = folderName.trim()
178
+ if (!trimmed) {
179
+ throw new KeeperSdkError('Folder cannot be empty.', 'folder_required')
180
+ }
181
+
182
+ const direct = getFolderEntryByUid(storage, trimmed)
183
+ if (direct) {
184
+ return { folderUid: direct.uid, name: direct.name }
185
+ }
186
+
187
+ const { folderUid, remaining } = await tryResolvePath(storage, session, trimmed)
188
+ if (remaining) {
189
+ throw new KeeperSdkError(`Folder "${folderName}" not found`, 'folder_not_found')
190
+ }
191
+
192
+ if (folderUid === null) {
193
+ return { folderUid: null, name: VAULT_ROOT_DISPLAY_NAME }
194
+ }
195
+
196
+ const entry = getFolderEntryByUid(storage, folderUid)
197
+ if (!entry) {
198
+ throw new KeeperSdkError(`Folder "${folderName}" not found`, 'folder_not_found')
199
+ }
200
+ return { folderUid: entry.uid, name: entry.name }
201
+ }
202
+
203
+ export async function changeDirectory(
204
+ storage: InMemoryStorage,
205
+ session: VaultFolderSession,
206
+ path: string
207
+ ): Promise<ChangeDirectoryResult> {
208
+ const resolved = await resolveSingleFolder(storage, session, path)
209
+ session.currentFolderUid = resolved.folderUid
210
+ return resolved
211
+ }
212
+
213
+ export function getWorkingFolderDisplayName(storage: InMemoryStorage, currentFolderUid: string | null): string {
214
+ if (currentFolderUid === null) return VAULT_ROOT_DISPLAY_NAME
215
+ const entry = getFolderEntryByUid(storage, currentFolderUid)
216
+ return entry?.name || VAULT_ROOT_DISPLAY_NAME
217
+ }