@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,321 @@
1
+ import type {
2
+ DRecord,
3
+ DSharedFolder,
4
+ DSharedFolderFolder,
5
+ DSharedFolderTeam,
6
+ DSharedFolderUser,
7
+ DUser,
8
+ DUserFolder,
9
+ } from '@keeper-security/keeperapi'
10
+ import { webSafe64FromBytes } from '@keeper-security/keeperapi'
11
+ import { InMemoryStorage } from '../storage/InMemoryStorage'
12
+ import { getRecordTitle } from '../records/RecordUtils'
13
+ import { listFolder, listVaultRootFolders } from './listFolder'
14
+ import { resolveSingleFolder, type VaultFolderSession } from './changeDirectory'
15
+ import { FolderKind, VaultObjectKind, sharedFolderFolderName, sharedFolderName, userFolderName } from './folderHelpers'
16
+
17
+ enum TreeItemKind {
18
+ Permission = 'perm',
19
+ Record = 'record',
20
+ Folder = 'folder',
21
+ }
22
+
23
+ export type FolderTreeBuildOptions = {
24
+ folderPath?: string | null
25
+ verbose?: boolean
26
+ showRecords?: boolean
27
+ showShares?: boolean
28
+ hideSharesKey?: boolean
29
+ title?: string | null
30
+ }
31
+
32
+ export type FolderTreeNode = {
33
+ displayName: string
34
+ children: FolderTreeNode[]
35
+ permissions?: { display: string }[]
36
+ records?: { display: string }[]
37
+ }
38
+
39
+ export type FolderTreeResult = {
40
+ title?: string | null
41
+ root: FolderTreeNode
42
+ }
43
+
44
+ export function userPermissionToText(manageUsers: boolean, manageRecords: boolean): string {
45
+ if (manageUsers && manageRecords) return 'Can Manage Users & Records'
46
+ if (manageUsers) return 'Can Manage Users'
47
+ if (manageRecords) return 'Can Manage Records'
48
+ return 'No User Permissions'
49
+ }
50
+
51
+ export function recordPermissionToText(canEdit: boolean, canShare: boolean): string {
52
+ if (canEdit && canShare) return 'Can Edit & Share'
53
+ if (canEdit) return 'Can Edit'
54
+ if (canShare) return 'Can Share'
55
+ return 'View Only'
56
+ }
57
+
58
+ function buildAccountUidEmailMap(storage: InMemoryStorage): Map<string, string> {
59
+ const accountUidToEmail = new Map<string, string>()
60
+ for (const user of storage.getAll<DUser>(VaultObjectKind.User)) {
61
+ const uid = user.accountUid ? webSafe64FromBytes(user.accountUid) : ''
62
+ const email = (user.username || '').trim()
63
+ if (uid && email) accountUidToEmail.set(uid, email)
64
+ }
65
+ return accountUidToEmail
66
+ }
67
+
68
+ function resolveUserDisplayName(
69
+ accountUid: string | undefined,
70
+ accountUsername: string | undefined,
71
+ emailMap: Map<string, string>
72
+ ): string {
73
+ const explicit = (accountUsername || '').trim()
74
+ if (explicit) return explicit
75
+ if (accountUid) {
76
+ const fromMap = emailMap.get(accountUid)
77
+ if (fromMap) return fromMap
78
+ return accountUid
79
+ }
80
+ return 'unknown'
81
+ }
82
+
83
+ async function collectSharedFolderPermissions(
84
+ storage: InMemoryStorage,
85
+ sharedFolder: DSharedFolder,
86
+ verbose: boolean,
87
+ hideSharesKey: boolean,
88
+ emailMap: Map<string, string>
89
+ ): Promise<{ display: string }[]> {
90
+ const rows: { display: string; sortKey: string }[] = []
91
+ const seenUserUids = new Set<string>()
92
+
93
+ for (const sharedUser of storage.getAll<DSharedFolderUser>(VaultObjectKind.SharedFolderUser)) {
94
+ if (sharedUser.sharedFolderUid !== sharedFolder.uid) continue
95
+ if (sharedUser.accountUid) seenUserUids.add(sharedUser.accountUid)
96
+ const permissionText = userPermissionToText(sharedUser.manageUsers, sharedUser.manageRecords)
97
+ let name = resolveUserDisplayName(sharedUser.accountUid, sharedUser.accountUsername, emailMap)
98
+ if (verbose && sharedUser.accountUid) name += ` (${sharedUser.accountUid})`
99
+ const suffix = hideSharesKey ? '' : ` [User]`
100
+ rows.push({
101
+ display: `${name}: ${permissionText}${suffix}`,
102
+ sortKey: name.toLowerCase(),
103
+ })
104
+ }
105
+
106
+ if (sharedFolder.ownerAccountUid && !seenUserUids.has(sharedFolder.ownerAccountUid)) {
107
+ const ownerName = resolveUserDisplayName(sharedFolder.ownerAccountUid, sharedFolder.ownerUsername, emailMap)
108
+ let display = ownerName
109
+ if (verbose) display += ` (${sharedFolder.ownerAccountUid})`
110
+ const suffix = hideSharesKey ? '' : ` [User]`
111
+ rows.push({
112
+ display: `${display}: ${userPermissionToText(true, true)}${suffix}`,
113
+ sortKey: ownerName.toLowerCase(),
114
+ })
115
+ }
116
+
117
+ for (const sharedTeam of storage.getAll<DSharedFolderTeam>(VaultObjectKind.SharedFolderTeam)) {
118
+ if (sharedTeam.sharedFolderUid !== sharedFolder.uid) continue
119
+ const permissionText = userPermissionToText(sharedTeam.manageUsers, sharedTeam.manageRecords)
120
+ let name = sharedTeam.name || `(${sharedTeam.teamUid})`
121
+ if (verbose && sharedTeam.teamUid) name += ` (${sharedTeam.teamUid})`
122
+ const suffix = hideSharesKey ? '' : ` [Team]`
123
+ rows.push({
124
+ display: `${name}: ${permissionText}${suffix}`,
125
+ sortKey: name.toLowerCase(),
126
+ })
127
+ }
128
+
129
+ rows.sort((rowA, rowB) => rowA.sortKey.localeCompare(rowB.sortKey))
130
+ return rows.map((row) => ({ display: row.display }))
131
+ }
132
+
133
+ type BuildOpts = Required<Pick<FolderTreeBuildOptions, 'verbose' | 'showRecords' | 'showShares' | 'hideSharesKey'>> & {
134
+ promotedRootSharedUids?: Set<string>
135
+ accountUidEmailMap: Map<string, string>
136
+ }
137
+
138
+ async function buildFolderSubtree(
139
+ storage: InMemoryStorage,
140
+ folderUid: string,
141
+ opts: BuildOpts
142
+ ): Promise<FolderTreeNode> {
143
+ const userFolder = storage.getByUid<DUserFolder>(FolderKind.UserFolder, folderUid)
144
+ const sharedFolder = storage.getByUid<DSharedFolder>(FolderKind.SharedFolder, folderUid)
145
+ const sharedFolderFolder = storage.getByUid<DSharedFolderFolder>(FolderKind.SharedFolderFolder, folderUid)
146
+ if (!userFolder && !sharedFolder && !sharedFolderFolder) {
147
+ return { displayName: `(missing folder ${folderUid})`, children: [] }
148
+ }
149
+
150
+ let baseName: string
151
+ if (userFolder) baseName = userFolderName(userFolder)
152
+ else if (sharedFolder) baseName = sharedFolderName(sharedFolder)
153
+ else baseName = sharedFolderFolderName(sharedFolderFolder!)
154
+
155
+ let displayName = baseName
156
+ if (opts.verbose) {
157
+ displayName = `${baseName} (${folderUid})`
158
+ }
159
+ if (sharedFolder) {
160
+ displayName += ' [Shared]'
161
+ }
162
+
163
+ const node: FolderTreeNode = { displayName, children: [] }
164
+
165
+ if (opts.showShares && sharedFolder) {
166
+ node.permissions = await collectSharedFolderPermissions(
167
+ storage,
168
+ sharedFolder,
169
+ opts.verbose,
170
+ opts.hideSharesKey,
171
+ opts.accountUidEmailMap
172
+ )
173
+ }
174
+
175
+ const listed = await listFolder(storage, {
176
+ folderUid: folderUid,
177
+ showFolders: true,
178
+ showRecords: opts.showRecords,
179
+ })
180
+
181
+ for (const childFolder of listed.folders) {
182
+ if (opts.promotedRootSharedUids?.has(childFolder.uid)) continue
183
+ node.children.push(await buildFolderSubtree(storage, childFolder.uid, opts))
184
+ }
185
+
186
+ if (opts.showRecords && 'records' in listed) {
187
+ const records = listed.records
188
+ node.records = records.map((recordRow) => {
189
+ const record = storage.getByUid<DRecord>(VaultObjectKind.Record, recordRow.uid)
190
+ const title = record ? getRecordTitle(record) : recordRow.name
191
+ const display = opts.verbose && record ? `${title} (${recordRow.uid}) [Record]` : `${title} [Record]`
192
+ return { display }
193
+ })
194
+ }
195
+
196
+ return node
197
+ }
198
+
199
+ async function buildVaultRootTree(storage: InMemoryStorage, opts: BuildOpts): Promise<FolderTreeNode> {
200
+ const node: FolderTreeNode = { displayName: '', children: [] }
201
+ const { rows, promotedRootSharedUids } = await listVaultRootFolders(storage)
202
+ const optsWithPromoted: BuildOpts = { ...opts, promotedRootSharedUids }
203
+ for (const folderRow of rows) {
204
+ node.children.push(await buildFolderSubtree(storage, folderRow.uid, optsWithPromoted))
205
+ }
206
+ const listed = await listFolder(storage, {
207
+ folderUid: undefined,
208
+ showFolders: true,
209
+ showRecords: opts.showRecords,
210
+ })
211
+ if (opts.showRecords && listed.records?.length) {
212
+ node.records = listed.records.map((recordRow) => {
213
+ const record = storage.getByUid<DRecord>(VaultObjectKind.Record, recordRow.uid)
214
+ const title = record ? getRecordTitle(record) : recordRow.name
215
+ const display = opts.verbose && record ? `${title} (${recordRow.uid}) [Record]` : `${title} [Record]`
216
+ return { display }
217
+ })
218
+ }
219
+ return node
220
+ }
221
+
222
+ async function resolveTreeStart(
223
+ storage: InMemoryStorage,
224
+ session: VaultFolderSession,
225
+ folderPath?: string | null
226
+ ): Promise<{ folderUid: string | null }> {
227
+ const trimmedPath = folderPath?.trim()
228
+ if (trimmedPath) {
229
+ const resolved = await resolveSingleFolder(storage, session, trimmedPath)
230
+ return { folderUid: resolved.folderUid }
231
+ }
232
+ return { folderUid: session.currentFolderUid || null }
233
+ }
234
+
235
+ export async function buildFolderTree(
236
+ storage: InMemoryStorage,
237
+ session: VaultFolderSession,
238
+ options: FolderTreeBuildOptions = {}
239
+ ): Promise<FolderTreeResult> {
240
+ const opts: BuildOpts = {
241
+ verbose: options.verbose === true,
242
+ showRecords: options.showRecords === true,
243
+ showShares: options.showShares === true,
244
+ hideSharesKey: options.hideSharesKey === true,
245
+ accountUidEmailMap: buildAccountUidEmailMap(storage),
246
+ }
247
+
248
+ const { folderUid } = await resolveTreeStart(storage, session, options.folderPath)
249
+
250
+ const root =
251
+ folderUid === null
252
+ ? await buildVaultRootTree(storage, opts)
253
+ : await buildFolderSubtree(storage, folderUid, opts)
254
+
255
+ return { title: options.title || undefined, root }
256
+ }
257
+
258
+ type TreeItem =
259
+ | { kind: TreeItemKind.Permission; display: string }
260
+ | { kind: TreeItemKind.Record; display: string }
261
+ | { kind: TreeItemKind.Folder; node: FolderTreeNode }
262
+
263
+ function gatherItems(node: FolderTreeNode): TreeItem[] {
264
+ const items: TreeItem[] = []
265
+ if (node.permissions) {
266
+ for (const permission of node.permissions) {
267
+ items.push({ kind: TreeItemKind.Permission, display: permission.display })
268
+ }
269
+ }
270
+ for (const child of node.children) {
271
+ items.push({ kind: TreeItemKind.Folder, node: child })
272
+ }
273
+ if (node.records) {
274
+ for (const record of node.records) {
275
+ items.push({ kind: TreeItemKind.Record, display: record.display })
276
+ }
277
+ }
278
+ return items
279
+ }
280
+
281
+ export function renderFolderTreeAscii(result: FolderTreeResult): string {
282
+ const lines: string[] = []
283
+ if (result.title) {
284
+ lines.push(result.title)
285
+ }
286
+ renderNode(result.root, lines, true, '', true)
287
+ return lines.join('\n')
288
+ }
289
+
290
+ function renderNode(node: FolderTreeNode, lines: string[], isRoot: boolean, prefix: string, isLast: boolean): void {
291
+ if (isRoot) {
292
+ if (node.displayName) {
293
+ lines.push(node.displayName)
294
+ }
295
+ } else {
296
+ const connector = isLast ? '\\-- ' : '+-- '
297
+ lines.push(prefix + connector + node.displayName)
298
+ }
299
+
300
+ const childBase = isRoot ? ' ' : prefix + (isLast ? ' ' : '| ')
301
+ const items = gatherItems(node)
302
+ for (let itemIndex = 0; itemIndex < items.length; itemIndex++) {
303
+ const isLastItem = itemIndex === items.length - 1
304
+ const item = items[itemIndex]
305
+ if (item.kind === TreeItemKind.Permission || item.kind === TreeItemKind.Record) {
306
+ const connector = isLastItem ? '\\-- ' : '+-- '
307
+ lines.push(childBase + connector + item.display)
308
+ } else {
309
+ renderNode(item.node, lines, false, childBase, isLastItem)
310
+ }
311
+ }
312
+ }
313
+
314
+ export async function folderTreeAscii(
315
+ storage: InMemoryStorage,
316
+ session: VaultFolderSession,
317
+ options?: FolderTreeBuildOptions
318
+ ): Promise<string> {
319
+ const built = await buildFolderTree(storage, session, options || {})
320
+ return renderFolderTreeAscii(built)
321
+ }
@@ -0,0 +1,234 @@
1
+ import type {
2
+ DSharedFolder,
3
+ DSharedFolderFolder,
4
+ DSharedFolderRecord,
5
+ DSharedFolderUser,
6
+ DUserFolder,
7
+ } from '@keeper-security/keeperapi'
8
+ import { InMemoryStorage } from '../storage/InMemoryStorage'
9
+ import { KeeperSdkError } from '../utils'
10
+ import { findParentFolderUid } from './changeDirectory'
11
+ import { FolderKind, FolderObjectType, VaultObjectKind, sharedFolderFolderName, userFolderName } from './folderHelpers'
12
+
13
+ export enum GetFolderFormat {
14
+ Detail = 'detail',
15
+ JSON = 'json',
16
+ }
17
+
18
+ export type GetFolderFormatInput = GetFolderFormat | `${GetFolderFormat}`
19
+
20
+ export type GetFolderOptions = {
21
+ format?: GetFolderFormatInput
22
+ }
23
+
24
+ export type GetFolderResult = GetFolderResultFolder | GetFolderResultSharedFolder
25
+
26
+ export type GetFolderResultFolder = {
27
+ objectType: FolderObjectType.Folder
28
+ format: GetFolderFormat
29
+ folder_uid: string
30
+ folder_type: FolderKind.UserFolder | FolderKind.SharedFolderFolder
31
+ name: string
32
+ parent_uid: string | null
33
+ shared_folder_scope_uid?: string
34
+ json?: Record<string, unknown>
35
+ }
36
+
37
+ export type GetFolderResultSharedFolder = {
38
+ objectType: FolderObjectType.SharedFolder
39
+ format: GetFolderFormat
40
+ shared_folder_uid: string
41
+ name: string
42
+ default_can_edit: boolean
43
+ default_can_share: boolean
44
+ default_manage_records: boolean
45
+ default_manage_users: boolean
46
+ record_permissions?: {
47
+ record_uid: string
48
+ can_edit: boolean
49
+ can_share: boolean
50
+ owner: boolean
51
+ }[]
52
+ user_permissions?: {
53
+ account_username?: string
54
+ manage_records: boolean
55
+ manage_users: boolean
56
+ }[]
57
+ json?: Record<string, unknown>
58
+ }
59
+
60
+ export type FoundFolder =
61
+ | { kind: FolderKind.UserFolder; folder: DUserFolder }
62
+ | { kind: FolderKind.SharedFolder; folder: DSharedFolder }
63
+ | { kind: FolderKind.SharedFolderFolder; folder: DSharedFolderFolder }
64
+
65
+ function findByUidOrName<T>(
66
+ items: Iterable<T>,
67
+ nameOrUid: string,
68
+ getUid: (item: T) => string,
69
+ getName: (item: T) => string
70
+ ): T | undefined {
71
+ const trimmedNameOrUid = nameOrUid.trim()
72
+ if (!trimmedNameOrUid) return undefined
73
+ const lowerNameOrUid = trimmedNameOrUid.toLowerCase()
74
+ let exactNameHit: T | undefined
75
+ let lowerNameHit: T | undefined
76
+ for (const item of items) {
77
+ if (getUid(item) === trimmedNameOrUid) return item
78
+ if (!exactNameHit) {
79
+ const itemName = getName(item)
80
+ if (itemName === trimmedNameOrUid) {
81
+ exactNameHit = item
82
+ } else if (!lowerNameHit && itemName.toLowerCase() === lowerNameOrUid) {
83
+ lowerNameHit = item
84
+ }
85
+ }
86
+ }
87
+ return exactNameHit || lowerNameHit
88
+ }
89
+
90
+ function findSharedFolder(storage: InMemoryStorage, nameOrUid: string): DSharedFolder | undefined {
91
+ return findByUidOrName(
92
+ storage.getAll<DSharedFolder>(FolderKind.SharedFolder),
93
+ nameOrUid,
94
+ (sharedFolder) => sharedFolder.uid,
95
+ (sharedFolder) => (sharedFolder.name || '').trim()
96
+ )
97
+ }
98
+
99
+ function findUserFolder(storage: InMemoryStorage, nameOrUid: string): DUserFolder | undefined {
100
+ return findByUidOrName(
101
+ storage.getAll<DUserFolder>(FolderKind.UserFolder),
102
+ nameOrUid,
103
+ (userFolder) => userFolder.uid,
104
+ (userFolder) => userFolderName(userFolder)
105
+ )
106
+ }
107
+
108
+ function findSharedFolderFolder(storage: InMemoryStorage, nameOrUid: string): DSharedFolderFolder | undefined {
109
+ return findByUidOrName(
110
+ storage.getAll<DSharedFolderFolder>(FolderKind.SharedFolderFolder),
111
+ nameOrUid,
112
+ (sharedFolderFolder) => sharedFolderFolder.uid,
113
+ (sharedFolderFolder) => sharedFolderFolderName(sharedFolderFolder)
114
+ )
115
+ }
116
+
117
+ export function findFolder(storage: InMemoryStorage, nameOrUid: string): FoundFolder | undefined {
118
+ const trimmedNameOrUid = nameOrUid.trim()
119
+ if (!trimmedNameOrUid) return undefined
120
+
121
+ const sharedFolder = findSharedFolder(storage, trimmedNameOrUid)
122
+ if (sharedFolder) return { kind: FolderKind.SharedFolder, folder: sharedFolder }
123
+
124
+ const userFolder = findUserFolder(storage, trimmedNameOrUid)
125
+ if (userFolder) return { kind: FolderKind.UserFolder, folder: userFolder }
126
+
127
+ const sharedFolderFolder = findSharedFolderFolder(storage, trimmedNameOrUid)
128
+ if (sharedFolderFolder) return { kind: FolderKind.SharedFolderFolder, folder: sharedFolderFolder }
129
+
130
+ return undefined
131
+ }
132
+
133
+ async function formatRegularFolder(
134
+ storage: InMemoryStorage,
135
+ folder: DUserFolder | DSharedFolderFolder,
136
+ format: GetFolderFormat
137
+ ): Promise<GetFolderResultFolder> {
138
+ const isUser = folder.kind === FolderKind.UserFolder
139
+ const folder_uid = folder.uid
140
+ const folder_type: GetFolderResultFolder['folder_type'] = isUser
141
+ ? FolderKind.UserFolder
142
+ : FolderKind.SharedFolderFolder
143
+ const name = isUser ? userFolderName(folder) : sharedFolderFolderName(folder)
144
+ const parent_uid = await findParentFolderUid(storage, folder_uid)
145
+
146
+ const base: GetFolderResultFolder = {
147
+ objectType: FolderObjectType.Folder,
148
+ format,
149
+ folder_uid,
150
+ folder_type,
151
+ name,
152
+ parent_uid,
153
+ }
154
+ if (!isUser) {
155
+ base.shared_folder_scope_uid = (folder as DSharedFolderFolder).sharedFolderUid
156
+ }
157
+ if (format === GetFolderFormat.JSON) {
158
+ base.json = { ...base, objectType: FolderObjectType.Folder }
159
+ }
160
+ return base
161
+ }
162
+
163
+ function formatSharedFolder(
164
+ storage: InMemoryStorage,
165
+ sharedFolder: DSharedFolder,
166
+ format: GetFolderFormat
167
+ ): GetFolderResultSharedFolder {
168
+ const sharedFolderUid = sharedFolder.uid
169
+ const recordPermissions = storage
170
+ .getAll<DSharedFolderRecord>(VaultObjectKind.SharedFolderRecord)
171
+ .filter((record) => record.sharedFolderUid === sharedFolderUid)
172
+ .map((record) => ({
173
+ record_uid: record.recordUid,
174
+ can_edit: record.canEdit,
175
+ can_share: record.canShare,
176
+ owner: record.owner,
177
+ }))
178
+ const userPermissions = storage
179
+ .getAll<DSharedFolderUser>(VaultObjectKind.SharedFolderUser)
180
+ .filter((user) => user.sharedFolderUid === sharedFolderUid)
181
+ .map((user) => ({
182
+ account_username: user.accountUsername,
183
+ manage_records: user.manageRecords,
184
+ manage_users: user.manageUsers,
185
+ }))
186
+
187
+ const base: GetFolderResultSharedFolder = {
188
+ objectType: FolderObjectType.SharedFolder,
189
+ format,
190
+ shared_folder_uid: sharedFolder.uid,
191
+ name: (sharedFolder.name || '').trim() || sharedFolder.uid,
192
+ default_can_edit: sharedFolder.defaultCanEdit,
193
+ default_can_share: sharedFolder.defaultCanShare,
194
+ default_manage_records: sharedFolder.defaultManageRecords,
195
+ default_manage_users: sharedFolder.defaultManageUsers,
196
+ record_permissions: recordPermissions.length ? recordPermissions : undefined,
197
+ user_permissions: userPermissions.length ? userPermissions : undefined,
198
+ }
199
+ if (format === GetFolderFormat.JSON) {
200
+ base.json = { ...base, objectType: FolderObjectType.SharedFolder }
201
+ }
202
+ return base
203
+ }
204
+
205
+ function normalizeFormat(format: GetFolderFormatInput | undefined): GetFolderFormat {
206
+ if (format === GetFolderFormat.JSON) return GetFolderFormat.JSON
207
+ return GetFolderFormat.Detail
208
+ }
209
+
210
+ export async function getFolder(
211
+ storage: InMemoryStorage,
212
+ uidOrName: string,
213
+ options: GetFolderOptions = {}
214
+ ): Promise<GetFolderResult> {
215
+ const trimmed = uidOrName.trim()
216
+ if (!trimmed) {
217
+ throw new KeeperSdkError('Folder UID or name is required.', 'missing_folder_ref')
218
+ }
219
+
220
+ const found = findFolder(storage, trimmed)
221
+ if (!found) {
222
+ throw new KeeperSdkError(`"${trimmed}" not found as a folder or shared folder`, 'folder_not_found')
223
+ }
224
+
225
+ const format: GetFolderFormat = normalizeFormat(options.format)
226
+
227
+ switch (found.kind) {
228
+ case FolderKind.SharedFolder:
229
+ return formatSharedFolder(storage, found.folder, format)
230
+ case FolderKind.UserFolder:
231
+ case FolderKind.SharedFolderFolder:
232
+ return formatRegularFolder(storage, found.folder, format)
233
+ }
234
+ }