@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.
- package/dist/auth/ConsoleAuthUI.d.ts +10 -0
- package/dist/auth/ConsoleAuthUI.js +152 -0
- package/dist/auth/ConsoleAuthUI.js.map +1 -0
- package/dist/auth/ConsoleLogin.d.ts +8 -0
- package/dist/auth/ConsoleLogin.js +266 -0
- package/dist/auth/ConsoleLogin.js.map +1 -0
- package/dist/auth/SessionManager.d.ts +66 -0
- package/dist/auth/SessionManager.js +211 -0
- package/dist/auth/SessionManager.js.map +1 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.js +61 -0
- package/dist/index.js.map +1 -0
- package/dist/records/RecordOperations.d.ts +79 -0
- package/dist/records/RecordOperations.js +346 -0
- package/dist/records/RecordOperations.js.map +1 -0
- package/dist/records/RecordUtils.d.ts +36 -0
- package/dist/records/RecordUtils.js +224 -0
- package/dist/records/RecordUtils.js.map +1 -0
- package/dist/sharing/Sharing.d.ts +27 -0
- package/dist/sharing/Sharing.js +125 -0
- package/dist/sharing/Sharing.js.map +1 -0
- package/dist/src/auth/ConsoleAuthUI.d.ts +10 -0
- package/dist/src/auth/ConsoleAuthUI.js +161 -0
- package/dist/src/auth/ConsoleAuthUI.js.map +1 -0
- package/dist/src/auth/ConsoleLogin.d.ts +8 -0
- package/dist/src/auth/ConsoleLogin.js +311 -0
- package/dist/src/auth/ConsoleLogin.js.map +1 -0
- package/dist/src/auth/SessionManager.d.ts +67 -0
- package/dist/src/auth/SessionManager.js +212 -0
- package/dist/src/auth/SessionManager.js.map +1 -0
- package/dist/src/folders/FolderManager.d.ts +57 -0
- package/dist/src/folders/FolderManager.js +108 -0
- package/dist/src/folders/FolderManager.js.map +1 -0
- package/dist/src/folders/addFolder.d.ts +32 -0
- package/dist/src/folders/addFolder.js +207 -0
- package/dist/src/folders/addFolder.js.map +1 -0
- package/dist/src/folders/changeDirectory.d.ts +19 -0
- package/dist/src/folders/changeDirectory.js +171 -0
- package/dist/src/folders/changeDirectory.js.map +1 -0
- package/dist/src/folders/deleteFolder.d.ts +17 -0
- package/dist/src/folders/deleteFolder.js +237 -0
- package/dist/src/folders/deleteFolder.js.map +1 -0
- package/dist/src/folders/folderHelpers.d.ts +48 -0
- package/dist/src/folders/folderHelpers.js +100 -0
- package/dist/src/folders/folderHelpers.js.map +1 -0
- package/dist/src/folders/folderTree.d.ts +29 -0
- package/dist/src/folders/folderTree.js +250 -0
- package/dist/src/folders/folderTree.js.map +1 -0
- package/dist/src/folders/getFolder.d.ts +56 -0
- package/dist/src/folders/getFolder.js +143 -0
- package/dist/src/folders/getFolder.js.map +1 -0
- package/dist/src/folders/listFolder.d.ts +48 -0
- package/dist/src/folders/listFolder.js +276 -0
- package/dist/src/folders/listFolder.js.map +1 -0
- package/dist/src/folders/updateFolder.d.ts +31 -0
- package/dist/src/folders/updateFolder.js +137 -0
- package/dist/src/folders/updateFolder.js.map +1 -0
- package/dist/src/index.d.ts +49 -0
- package/dist/src/index.js +151 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/records/RecordOperations.d.ts +80 -0
- package/dist/src/records/RecordOperations.js +356 -0
- package/dist/src/records/RecordOperations.js.map +1 -0
- package/dist/src/records/RecordUtils.d.ts +37 -0
- package/dist/src/records/RecordUtils.js +263 -0
- package/dist/src/records/RecordUtils.js.map +1 -0
- package/dist/src/records/Totp.d.ts +14 -0
- package/dist/src/records/Totp.js +111 -0
- package/dist/src/records/Totp.js.map +1 -0
- package/dist/src/sharedFolders/SharedFolderManager.d.ts +20 -0
- package/dist/src/sharedFolders/SharedFolderManager.js +33 -0
- package/dist/src/sharedFolders/SharedFolderManager.js.map +1 -0
- package/dist/src/sharedFolders/listSharedFolders.d.ts +29 -0
- package/dist/src/sharedFolders/listSharedFolders.js +127 -0
- package/dist/src/sharedFolders/listSharedFolders.js.map +1 -0
- package/dist/src/sharedFolders/shareFolder.d.ts +36 -0
- package/dist/src/sharedFolders/shareFolder.js +352 -0
- package/dist/src/sharedFolders/shareFolder.js.map +1 -0
- package/dist/src/sharing/Sharing.d.ts +50 -0
- package/dist/src/sharing/Sharing.js +195 -0
- package/dist/src/sharing/Sharing.js.map +1 -0
- package/dist/src/storage/InMemoryStorage.d.ts +24 -0
- package/dist/src/storage/InMemoryStorage.js +139 -0
- package/dist/src/storage/InMemoryStorage.js.map +1 -0
- package/dist/src/teams/TeamManager.d.ts +17 -0
- package/dist/src/teams/TeamManager.js +38 -0
- package/dist/src/teams/TeamManager.js.map +1 -0
- package/dist/src/teams/enterpriseData.d.ts +106 -0
- package/dist/src/teams/enterpriseData.js +319 -0
- package/dist/src/teams/enterpriseData.js.map +1 -0
- package/dist/src/teams/listTeams.d.ts +42 -0
- package/dist/src/teams/listTeams.js +308 -0
- package/dist/src/teams/listTeams.js.map +1 -0
- package/dist/src/teams/viewTeam.d.ts +35 -0
- package/dist/src/teams/viewTeam.js +177 -0
- package/dist/src/teams/viewTeam.js.map +1 -0
- package/dist/src/utils/Logger.d.ts +28 -0
- package/dist/src/utils/Logger.js +62 -0
- package/dist/src/utils/Logger.js.map +1 -0
- package/dist/src/utils/constants.d.ts +50 -0
- package/dist/src/utils/constants.js +64 -0
- package/dist/src/utils/constants.js.map +1 -0
- package/dist/src/utils/errors.d.ts +10 -0
- package/dist/src/utils/errors.js +117 -0
- package/dist/src/utils/errors.js.map +1 -0
- package/dist/src/utils/guards.d.ts +7 -0
- package/dist/src/utils/guards.js +29 -0
- package/dist/src/utils/guards.js.map +1 -0
- package/dist/src/utils/index.d.ts +7 -0
- package/dist/src/utils/index.js +39 -0
- package/dist/src/utils/index.js.map +1 -0
- package/dist/src/utils/patterns.d.ts +9 -0
- package/dist/src/utils/patterns.js +20 -0
- package/dist/src/utils/patterns.js.map +1 -0
- package/dist/src/utils/types.d.ts +12 -0
- package/dist/src/utils/types.js +3 -0
- package/dist/src/utils/types.js.map +1 -0
- package/dist/src/vault/KeeperVault.d.ts +116 -0
- package/dist/src/vault/KeeperVault.js +443 -0
- package/dist/src/vault/KeeperVault.js.map +1 -0
- package/dist/storage/InMemoryStorage.d.ts +24 -0
- package/dist/storage/InMemoryStorage.js +132 -0
- package/dist/storage/InMemoryStorage.js.map +1 -0
- package/dist/utils/Logger.d.ts +28 -0
- package/dist/utils/Logger.js +62 -0
- package/dist/utils/Logger.js.map +1 -0
- package/dist/utils/constants.d.ts +26 -0
- package/dist/utils/constants.js +37 -0
- package/dist/utils/constants.js.map +1 -0
- package/dist/utils/errors.d.ts +10 -0
- package/dist/utils/errors.js +117 -0
- package/dist/utils/errors.js.map +1 -0
- package/dist/utils/index.d.ts +4 -0
- package/dist/utils/index.js +22 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/vault/KeeperVault.d.ts +72 -0
- package/dist/vault/KeeperVault.js +338 -0
- package/dist/vault/KeeperVault.js.map +1 -0
- package/package.json +32 -0
- package/src/auth/ConsoleAuthUI.ts +169 -0
- package/src/auth/ConsoleLogin.ts +351 -0
- package/src/auth/SessionManager.ts +293 -0
- package/src/folders/FolderManager.ts +174 -0
- package/src/folders/addFolder.ts +294 -0
- package/src/folders/changeDirectory.ts +217 -0
- package/src/folders/deleteFolder.ts +293 -0
- package/src/folders/folderHelpers.ts +99 -0
- package/src/folders/folderTree.ts +321 -0
- package/src/folders/getFolder.ts +234 -0
- package/src/folders/listFolder.ts +358 -0
- package/src/folders/updateFolder.ts +210 -0
- package/src/index.ts +242 -0
- package/src/records/RecordOperations.ts +549 -0
- package/src/records/RecordUtils.ts +282 -0
- package/src/records/Totp.ts +119 -0
- package/src/sharedFolders/SharedFolderManager.ts +57 -0
- package/src/sharedFolders/listSharedFolders.ts +173 -0
- package/src/sharedFolders/shareFolder.ts +457 -0
- package/src/sharing/Sharing.ts +282 -0
- package/src/storage/InMemoryStorage.ts +163 -0
- package/src/teams/TeamManager.ts +61 -0
- package/src/teams/enterpriseData.ts +453 -0
- package/src/teams/listTeams.ts +373 -0
- package/src/teams/viewTeam.ts +248 -0
- package/src/utils/Logger.ts +71 -0
- package/src/utils/constants.ts +63 -0
- package/src/utils/errors.ts +108 -0
- package/src/utils/guards.ts +24 -0
- package/src/utils/index.ts +22 -0
- package/src/utils/patterns.ts +20 -0
- package/src/utils/types.ts +11 -0
- package/src/vault/KeeperVault.ts +612 -0
- 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
|
+
}
|