sliftutils 1.7.70 → 1.7.72

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 (91) hide show
  1. package/dist/treeSummary.ts.cache +2 -2
  2. package/index.d.ts +505 -257
  3. package/misc/dist/environment.ts.cache +2 -2
  4. package/misc/dist/getSecret.ts.cache +2 -2
  5. package/misc/dist/strings.ts.cache +2 -2
  6. package/misc/dist/zip.ts.cache +2 -2
  7. package/misc/https/dist/certs.ts.cache +2 -2
  8. package/misc/https/dist/hostServer.ts.cache +5 -2
  9. package/misc/https/dist/persistentLocalStorage.ts.cache +2 -2
  10. package/package.json +1 -1
  11. package/render-utils/dist/observer.tsx.cache +2 -2
  12. package/spec.txt +10 -0
  13. package/storage/ArchivesDisk.d.ts +4 -13
  14. package/storage/ArchivesDisk.ts +6 -4
  15. package/storage/BulkDatabase2/dist/BulkDatabaseBase.ts.cache +2 -2
  16. package/storage/BulkDatabase2/dist/BulkDatabaseFormat.ts.cache +2 -2
  17. package/storage/BulkDatabase2/dist/BulkDatabaseMerge.ts.cache +2 -2
  18. package/storage/BulkDatabase2/dist/BulkDatabaseReader.ts.cache +2 -2
  19. package/storage/BulkDatabase2/dist/LoadedIndex.ts.cache +2 -2
  20. package/storage/BulkDatabase2/dist/WriteOverlay.ts.cache +2 -2
  21. package/storage/BulkDatabase2/dist/blockCache.ts.cache +2 -2
  22. package/storage/BulkDatabase2/dist/mergeLock.ts.cache +2 -2
  23. package/storage/BulkDatabase2/dist/mergeMarkers.ts.cache +2 -2
  24. package/storage/BulkDatabase2/dist/streamLog.ts.cache +2 -2
  25. package/storage/BulkDatabase2/dist/syncClient.ts.cache +2 -2
  26. package/storage/IArchives.d.ts +14 -10
  27. package/storage/IArchives.ts +16 -4
  28. package/storage/backblaze.d.ts +3 -9
  29. package/storage/backblaze.ts +22 -19
  30. package/storage/dist/ArchivesDisk.ts.cache +6 -3
  31. package/storage/dist/FileFolderAPI.tsx.cache +2 -2
  32. package/storage/dist/IArchives.ts.cache +18 -4
  33. package/storage/dist/IndexedDBFileFolderAPI.ts.cache +2 -2
  34. package/storage/dist/JSONStorage.ts.cache +2 -2
  35. package/storage/dist/PendingManager.tsx.cache +2 -2
  36. package/storage/dist/TransactionStorage.ts.cache +2 -2
  37. package/storage/dist/backblaze.ts.cache +36 -27
  38. package/storage/dist/fileSystemPointer.ts.cache +2 -2
  39. package/storage/dist/remoteFileStorage.ts.cache +2 -2
  40. package/storage/remoteStorage/ArchivesRemote.d.ts +5 -9
  41. package/storage/remoteStorage/ArchivesRemote.ts +25 -20
  42. package/storage/remoteStorage/ArchivesUrl.d.ts +3 -9
  43. package/storage/remoteStorage/ArchivesUrl.ts +7 -5
  44. package/storage/remoteStorage/accessPage.tsx +4 -4
  45. package/storage/remoteStorage/accessStats.d.ts +2 -0
  46. package/storage/remoteStorage/accessStats.ts +32 -0
  47. package/storage/remoteStorage/blobStore.d.ts +193 -88
  48. package/storage/remoteStorage/blobStore.ts +535 -400
  49. package/storage/remoteStorage/bucketDisk.d.ts +18 -0
  50. package/storage/remoteStorage/bucketDisk.ts +66 -0
  51. package/storage/remoteStorage/createArchives.d.ts +4 -10
  52. package/storage/remoteStorage/createArchives.ts +102 -63
  53. package/storage/remoteStorage/dist/ArchivesRemote.ts.cache +29 -20
  54. package/storage/remoteStorage/dist/ArchivesUrl.ts.cache +8 -5
  55. package/storage/remoteStorage/dist/accessPage.tsx.cache +6 -6
  56. package/storage/remoteStorage/dist/accessStats.ts.cache +39 -4
  57. package/storage/remoteStorage/dist/blobStore.ts.cache +517 -375
  58. package/storage/remoteStorage/dist/bucketDisk.ts.cache +72 -0
  59. package/storage/remoteStorage/dist/cliArgs.ts.cache +2 -2
  60. package/storage/remoteStorage/dist/createArchives.ts.cache +112 -58
  61. package/storage/remoteStorage/dist/deployTakeover.ts.cache +2 -2
  62. package/storage/remoteStorage/dist/intermediateSources.ts.cache +13 -13
  63. package/storage/remoteStorage/dist/remoteConfig.ts.cache +19 -3
  64. package/storage/remoteStorage/dist/serverConfig.ts.cache +116 -0
  65. package/storage/remoteStorage/dist/sourceWrapper.ts.cache +7 -6
  66. package/storage/remoteStorage/dist/sourcesList.ts.cache +6 -3
  67. package/storage/remoteStorage/dist/storageClientController.ts.cache +2 -2
  68. package/storage/remoteStorage/dist/storageController.ts.cache +224 -191
  69. package/storage/remoteStorage/dist/storageServer.ts.cache +9 -9
  70. package/storage/remoteStorage/dist/storageServerState.ts.cache +330 -567
  71. package/storage/remoteStorage/dist/storePlan.ts.cache +149 -0
  72. package/storage/remoteStorage/dist/validation.ts.cache +40 -0
  73. package/storage/remoteStorage/grantAccessCli.ts +1 -1
  74. package/storage/remoteStorage/intermediateSources.d.ts +3 -5
  75. package/storage/remoteStorage/intermediateSources.ts +14 -16
  76. package/storage/remoteStorage/remoteConfig.d.ts +2 -2
  77. package/storage/remoteStorage/remoteConfig.ts +19 -3
  78. package/storage/remoteStorage/serverConfig.d.ts +27 -0
  79. package/storage/remoteStorage/serverConfig.ts +109 -0
  80. package/storage/remoteStorage/sourceWrapper.d.ts +3 -3
  81. package/storage/remoteStorage/sourceWrapper.ts +7 -6
  82. package/storage/remoteStorage/sourcesList.ts +3 -0
  83. package/storage/remoteStorage/storageController.d.ts +118 -36
  84. package/storage/remoteStorage/storageController.ts +166 -168
  85. package/storage/remoteStorage/storageServer.ts +1 -1
  86. package/storage/remoteStorage/storageServerState.d.ts +52 -63
  87. package/storage/remoteStorage/storageServerState.ts +340 -584
  88. package/storage/remoteStorage/storePlan.d.ts +34 -0
  89. package/storage/remoteStorage/storePlan.ts +166 -0
  90. package/storage/remoteStorage/validation.d.ts +4 -0
  91. package/storage/remoteStorage/validation.ts +31 -0
@@ -0,0 +1,149 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true , configurable: true});
3
+ //exports.computeStorePlan = exports.selectEntryAt = exports.findSelfIndexes = void 0;
4
+ const misc_1 = require("socket-function/src/misc");
5
+ const IArchives_1 = require("../IArchives");
6
+ const remoteConfig_1 = require("./remoteConfig");
7
+ const serverConfig_1 = require("./serverConfig");
8
+ // Turns a bucket's routing config into this server's store plan: which of the entries are us, what stores (one per route) we run, and which peers each store synchronizes from.
9
+ function findSelfIndexes(routing, account, bucketName) {
10
+ let indexes = [];
11
+ for (let i = 0; i < routing.sources.length; i++) {
12
+ let source = routing.sources[i];
13
+ if (typeof source === "string" || source.type !== "remote")
14
+ continue;
15
+ let parsed = (0, remoteConfig_1.parseHostedUrl)(source.url);
16
+ if (parsed.account !== account || parsed.bucketName !== bucketName)
17
+ continue;
18
+ if ((0, serverConfig_1.isOwnAddress)(parsed.address, parsed.port)) {
19
+ indexes.push(i);
20
+ }
21
+ }
22
+ return indexes;
23
+ }
24
+ exports.findSelfIndexes = findSelfIndexes;
25
+ function selectEntryAt(entries, time, route) {
26
+ if (route !== undefined) {
27
+ let covering = entries.filter(x => (0, remoteConfig_1.routeContains)(x.route, route));
28
+ if (covering.length) {
29
+ entries = covering;
30
+ }
31
+ }
32
+ let containing = entries.find(x => x.validWindow[0] <= time && time < x.validWindow[1]);
33
+ if (containing)
34
+ return containing;
35
+ let best;
36
+ let bestDistance = Infinity;
37
+ for (let entry of entries) {
38
+ let distance = Math.min(Math.abs(time - entry.validWindow[0]), Math.abs(time - entry.validWindow[1]));
39
+ if (distance < bestDistance) {
40
+ bestDistance = distance;
41
+ best = entry;
42
+ }
43
+ }
44
+ return best;
45
+ }
46
+ exports.selectEntryAt = selectEntryAt;
47
+ function mergeRoutes(routes) {
48
+ let list = routes.map(x => x || IArchives_1.FULL_ROUTE).map(x => [x[0], x[1]]);
49
+ (0, misc_1.sort)(list, x => x[0]);
50
+ let merged = [];
51
+ for (let route of list) {
52
+ let last = merged[merged.length - 1];
53
+ if (last && route[0] <= last[1]) {
54
+ last[1] = Math.max(last[1], route[1]);
55
+ continue;
56
+ }
57
+ merged.push(route);
58
+ }
59
+ return merged;
60
+ }
61
+ function summarizeSelf(selfEntries, now) {
62
+ var _a;
63
+ let current = selfEntries.filter(x => x.validWindow[0] <= now && now < x.validWindow[1]);
64
+ if (!current.length) {
65
+ let nearest = selectEntryAt(selfEntries, now);
66
+ current = nearest && [nearest] || [];
67
+ }
68
+ if (!current.length) {
69
+ return undefined;
70
+ }
71
+ return {
72
+ routes: mergeRoutes(current.map(x => x.route)),
73
+ public: current.some(x => x.public),
74
+ immutable: current.some(x => x.immutable),
75
+ noFullSync: current.some(x => x.noFullSync),
76
+ rawDisk: current.some(x => x.rawDisk),
77
+ readerDiskLimit: (_a = current.find(x => x.readerDiskLimit !== undefined)) === null || _a === void 0 ? void 0 : _a.readerDiskLimit,
78
+ };
79
+ }
80
+ function computeStorePlan(account, bucketName, routing) {
81
+ var _a;
82
+ let selfIndexes = findSelfIndexes(routing, account, bucketName);
83
+ let selfEntries = selfIndexes.map(i => routing.sources[i]);
84
+ let self = summarizeSelf(selfEntries, Date.now());
85
+ let ownIndexes = new Set(selfIndexes);
86
+ // One store per distinct route among ALL our entries - past and future windows included, so historical route folders keep serving their data and upcoming routes sync ahead of their window
87
+ let groups = new Map();
88
+ for (let i of selfIndexes) {
89
+ let entry = routing.sources[i];
90
+ let routeKey = JSON.stringify(entry.route || IArchives_1.FULL_ROUTE);
91
+ let group = groups.get(routeKey);
92
+ if (!group) {
93
+ group = { route: entry.route, entries: [], firstIndex: i };
94
+ groups.set(routeKey, group);
95
+ }
96
+ group.entries.push(entry);
97
+ }
98
+ let stores = [];
99
+ for (let [routeKey, group] of groups) {
100
+ let anchor = selectEntryAt(group.entries, Date.now());
101
+ let diskWindow = [0, 0];
102
+ if (anchor) {
103
+ let [start, end] = anchor.validWindow;
104
+ let merged = true;
105
+ while (merged) {
106
+ merged = false;
107
+ for (let entry of group.entries) {
108
+ let [entryStart, entryEnd] = entry.validWindow;
109
+ if (entryStart > end || entryEnd < start)
110
+ continue;
111
+ if (entryStart < start || entryEnd > end) {
112
+ start = Math.min(start, entryStart);
113
+ end = Math.max(end, entryEnd);
114
+ merged = true;
115
+ }
116
+ }
117
+ }
118
+ diskWindow = [start, end];
119
+ }
120
+ let sourceSpecs = [{ validWindow: diskWindow }];
121
+ let noFullSync = group.entries.some(x => x.noFullSync);
122
+ for (let i = group.firstIndex + 1; i < routing.sources.length; i++) {
123
+ let source = routing.sources[i];
124
+ if (typeof source === "string" || ownIndexes.has(i))
125
+ continue;
126
+ let sharedRoute = (0, remoteConfig_1.routeIntersection)(group.route, source.route);
127
+ if (!sharedRoute)
128
+ continue;
129
+ sourceSpecs.push({ sourceConfig: source, validWindow: source.validWindow, route: sharedRoute, noFullSync: source.noFullSync || noFullSync });
130
+ }
131
+ stores.push({
132
+ routeKey,
133
+ route: group.route,
134
+ entries: group.entries,
135
+ rawDisk: group.entries.some(x => x.rawDisk),
136
+ readerDiskLimit: (_a = group.entries.find(x => x.readerDiskLimit !== undefined)) === null || _a === void 0 ? void 0 : _a.readerDiskLimit,
137
+ sourceSpecs,
138
+ });
139
+ }
140
+ if (!stores.length) {
141
+ // Not in the config at all: still serve whatever the plain folder holds, through one inert full-route store
142
+ stores.push({ routeKey: JSON.stringify(IArchives_1.FULL_ROUTE), route: undefined, entries: [], rawDisk: false, readerDiskLimit: undefined, sourceSpecs: [{ validWindow: [0, 0] }] });
143
+ }
144
+ let structureKey = JSON.stringify(stores.map(s => ({ routeKey: s.routeKey, rawDisk: s.rawDisk, readerDiskLimit: s.readerDiskLimit })));
145
+ return { selfEntries, self, stores, structureKey };
146
+ }
147
+ exports.computeStorePlan = computeStorePlan;
148
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic3RvcmVQbGFuLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsic3RvcmVQbGFuLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7OztBQUFBLG1EQUFnRDtBQUNoRCw0Q0FBb0Y7QUFDcEYsaURBQWtGO0FBQ2xGLGlEQUE4QztBQUU5QyxnTEFBZ0w7QUFFaEwsU0FBZ0IsZUFBZSxDQUFDLE9BQXFCLEVBQUUsT0FBZSxFQUFFLFVBQWtCO0lBQ3RGLElBQUksT0FBTyxHQUFhLEVBQUUsQ0FBQztJQUMzQixLQUFLLElBQUksQ0FBQyxHQUFHLENBQUMsRUFBRSxDQUFDLEdBQUcsT0FBTyxDQUFDLE9BQU8sQ0FBQyxNQUFNLEVBQUUsQ0FBQyxFQUFFLEVBQUUsQ0FBQztRQUM5QyxJQUFJLE1BQU0sR0FBRyxPQUFPLENBQUMsT0FBTyxDQUFDLENBQUMsQ0FBQyxDQUFDO1FBQ2hDLElBQUksT0FBTyxNQUFNLEtBQUssUUFBUSxJQUFJLE1BQU0sQ0FBQyxJQUFJLEtBQUssUUFBUTtZQUFFLFNBQVM7UUFDckUsSUFBSSxNQUFNLEdBQUcsSUFBQSw2QkFBYyxFQUFDLE1BQU0sQ0FBQyxHQUFHLENBQUMsQ0FBQztRQUN4QyxJQUFJLE1BQU0sQ0FBQyxPQUFPLEtBQUssT0FBTyxJQUFJLE1BQU0sQ0FBQyxVQUFVLEtBQUssVUFBVTtZQUFFLFNBQVM7UUFDN0UsSUFBSSxJQUFBLDJCQUFZLEVBQUMsTUFBTSxDQUFDLE9BQU8sRUFBRSxNQUFNLENBQUMsSUFBSSxDQUFDLEVBQUUsQ0FBQztZQUM1QyxPQUFPLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQyxDQUFDO1FBQ3BCLENBQUM7SUFDTCxDQUFDO0lBQ0QsT0FBTyxPQUFPLENBQUM7QUFDbkIsQ0FBQztBQVpELDBDQVlDO0FBRUQsU0FBZ0IsYUFBYSxDQUFDLE9BQXVCLEVBQUUsSUFBWSxFQUFFLEtBQWM7SUFDL0UsSUFBSSxLQUFLLEtBQUssU0FBUyxFQUFFLENBQUM7UUFDdEIsSUFBSSxRQUFRLEdBQUcsT0FBTyxD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QUFLLEVBQUUsZUFBZSxFQUFFLFNBQVMsRUFBRSxXQUFXLEVBQUUsQ0FBQyxFQUFFLFdBQVcsRUFBRSxDQUFDLENBQUMsRUFBRSxDQUFDLENBQUMsRUFBRSxDQUFDLEVBQUUsQ0FBQyxDQUFDO0lBQzdLLENBQUM7SUFDRCxJQUFJLFlBQVksR0FBRyxJQUFJLENBQUMsU0FBUyxDQUFDLE1BQU0sQ0FBQyxHQUFHLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxDQUFDLEVBQUUsUUFBUSxFQUFFLENBQUMsQ0FBQyxRQUFRLEVBQUUsT0FBTyxFQUFFLENBQUMsQ0FBQyxPQUFPLEVBQUUsZUFBZSxFQUFFLENBQUMsQ0FBQyxlQUFlLEVBQUUsQ0FBQyxDQUFDLENBQUMsQ0FBQztJQUN2SSxPQUFPLEVBQUUsV0FBVyxFQUFFLElBQUksRUFBRSxNQUFNLEVBQUUsWUFBWSxFQUFFLENBQUM7QUFDdkQsQ0FBQztBQTlERCw0Q0E4REMiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgeyBzb3J0IH0gZnJvbSBcInNvY2tldC1mdW5jdGlvbi9zcmMvbWlzY1wiO1xuaW1wb3J0IHsgUmVtb3RlQ29uZmlnLCBIb3N0ZWRDb25maWcsIFNvdXJjZUNvbmZpZywgRlVMTF9ST1VURSB9IGZyb20gXCIuLi9JQXJjaGl2ZXNcIjtcbmltcG9ydCB7IHBhcnNlSG9zdGVkVXJsLCByb3V0ZUNvbnRhaW5zLCByb3V0ZUludGVyc2VjdGlvbiB9IGZyb20gXCIuL3JlbW90ZUNvbmZpZ1wiO1xuaW1wb3J0IHsgaXNPd25BZGRyZXNzIH0gZnJvbSBcIi4vc2VydmVyQ29uZmlnXCI7XG5cbi8vIFR1cm5zIGEgYnVja2V0J3Mgcm91dGluZyBjb25maWcgaW50byB0aGlzIHNlcnZlcidzIHN0b3JlIHBsYW46IHdoaWNoIG9mIHRoZSBlbnRyaWVzIGFyZSB1cywgd2hhdCBzdG9yZXMgKG9uZSBwZXIgcm91dGUpIHdlIHJ1biwgYW5kIHdoaWNoIHBlZXJzIGVhY2ggc3RvcmUgc3luY2hyb25pemVzIGZyb20uXG5cbmV4cG9ydCBmdW5jdGlvbiBmaW5kU2VsZkluZGV4ZXMocm91dGluZzogUmVtb3RlQ29uZmlnLCBhY2NvdW50OiBzdHJpbmcsIGJ1Y2tldE5hbWU6IHN0cmluZyk6IG51bWJlcltdIHtcbiAgICBsZXQgaW5kZXhlczogbnVtYmVyW10gPSBbXTtcbiAgICBmb3IgKGxldCBpID0gMDsgaSA8IHJvdXRpbmcuc291cmNlcy5sZW5ndGg7IGkrKykge1xuICAgICAgICBsZXQgc291cmNlID0gcm91dGluZy5zb3VyY2VzW2ldO1xuICAgICAgICBpZiAodHlwZW9mIHNvdXJjZSA9PT0gXCJzdHJpbmdcIiB8fCBzb3VyY2UudHlwZSAhPT0gXCJyZW1vdGVcIikgY29udGludWU7XG4gICAgICAgIGxldCBwYXJzZWQgPSBwYXJzZUhvc3RlZFVybChzb3VyY2UudXJsKTtcbiAgICAgICAgaWYgKHBhcnNlZC5hY2NvdW50ICE9PSBhY2NvdW50IHx8IHBhcnNlZC5idWNrZXROYW1lICE9PSBidWNrZXROYW1lKSBjb250aW51ZTtcbiAgICAgICAgaWYgKGlzT3duQWRkcmVzcyhwYXJzZWQuYWRkcmVzcywgcGFyc2VkLnBvcnQpKSB7XG4gICAgICAgICAgICBpbmRleGVzLnB1c2goaSk7XG4gICAgICAgIH1cbiAgICB9XG4gICAgcmV0dXJuIGluZGV4ZXM7XG59XG5cbmV4cG9ydCBmdW5jdGlvbiBzZWxlY3RFbnRyeUF0KGVudHJpZXM6IEhvc3RlZENvbmZpZ1tdLCB0aW1lOiBudW1iZXIsIHJvdXRlPzogbnVtYmVyKTogSG9zdGVkQ29uZmlnIHwgdW5kZWZpbmVkIHtcbiAgICBpZiAocm91dGUgIT09IHVuZGVmaW5lZCkge1xuICAgICAgICBsZXQgY292ZXJpbmcgPSBlbnRyaWVzLmZpbHRlcih4ID0+IHJvdXRlQ29udGFpbnMoeC5yb3V0ZSwgcm91dGUpKTtcbiAgICAgICAgaWYgKGNvdmVyaW5nLmxlbmd0aCkge1xuICAgICAgICAgICAgZW50cmllcyA9IGNvdmVyaW5nO1xuICAgICAgICB9XG4gICAgfVxuICAgIGxldCBjb250YWluaW5nID0gZW50cmllcy5maW5kKHggPT4geC52YWxpZFdpbmRvd1swXSA8PSB0aW1lICYmIHRpbWUgPCB4LnZhbGlkV2luZG93WzFdKTtcbiAgICBpZiAoY29udGFpbmluZykgcmV0dXJuIGNvbnRhaW5pbmc7XG4gICAgbGV0IGJlc3Q6IEhvc3RlZENvbmZpZyB8IHVuZGVmaW5lZDtcbiAgICBsZXQgYmVzdERpc3RhbmNlID0gSW5maW5pdHk7XG4gICAgZm9yIChsZXQgZW50cnkgb2YgZW50cmllcykge1xuICAgICAgICBsZXQgZGlzdGFuY2UgPSBNYXRoLm1pbihNYXRoLmFicyh0aW1lIC0gZW50cnkudmFsaWRXaW5kb3dbMF0pLCBNYXRoLmFicyh0aW1lIC0gZW50cnkudmFsaWRXaW5kb3dbMV0pKTtcbiAgICAgICAgaWYgKGRpc3RhbmNlIDwgYmVzdERpc3RhbmNlKSB7XG4gICAgICAgICAgICBiZXN0RGlzdGFuY2UgPSBkaXN0YW5jZTtcbiAgICAgICAgICAgIGJlc3QgPSBlbnRyeTtcbiAgICAgICAgfVxuICAgIH1cbiAgICByZXR1cm4gYmVzdDtcbn1cblxuLyoqIE91ciByb2xlIGluIGEgYnVja2V0J3Mgcm91dGluZyBjb25maWcsIHN1bW1hcml6ZWQgYWNyb3NzIEFMTCBjdXJyZW50bHktdmFsaWQgc2VsZiBlbnRyaWVzLiBTdG9yZWQgaW5zdGVhZCBvZiBhIHNpbmdsZSByZXByZXNlbnRhdGl2ZSBIb3N0ZWRDb25maWcsIHNvIG5vdGhpbmcgY2FuIGFjY2lkZW50YWxseSB1c2Ugb25lIGVudHJ5J3Mgcm91dGUgb3IgZmxhZ3Mgd2hlcmUgdGhlIHVuaW9uIGlzIHJlcXVpcmVkIC0gdGhlIHN0YW5kYXJkIGNvbmZpZyBoYXMgdGhlIHNhbWUgVVJMIHR3aWNlOiBhIHJvdXRlZCB3cml0ZS1zaGFyZCBlbnRyeSBwbHVzIGFuIHVucm91dGVkIHJlYWQtZXZlcnl0aGluZyBlbnRyeS4gKi9cbmV4cG9ydCB0eXBlIFNlbGZTdW1tYXJ5ID0ge1xuICAgIC8qKiBUaGUgdW5pb24gb2YgdGhlIGN1cnJlbnQgZW50cmllcycgcm91dGVzLCB3aXRoIG92ZXJsYXBwaW5nL2FkamFjZW50IHJhbmdlcyBjb21iaW5lZCAtIHdoaWNoIGNvbW1vbmx5IGNvbGxhcHNlcyB0byBhIHNpbmdsZSBmdWxsIHJhbmdlLCBtYWtpbmcgbWF0Y2hpbmcgdHJpdmlhbC4gKi9cbiAgICByb3V0ZXM6IFtudW1iZXIsIG51bWJlcl1bXTtcbiAgICBwdWJsaWM6IGJvb2xlYW47XG4gICAgaW1tdXRhYmxlOiBib29sZWFuO1xuICAgIG5vRnVsbFN5bmM6IGJvb2xlYW47XG4gICAgcmF3RGlzazogYm9vbGVhbjtcbiAgICByZWFkZXJEaXNrTGltaXQ/OiBudW1iZXI7XG59O1xuXG5mdW5jdGlvbiBtZXJnZVJvdXRlcyhyb3V0ZXM6IChbbnVtYmVyLCBudW1iZXJdIHwgdW5kZWZpbmVkKVtdKTogW251bWJlciwgbnVtYmVyXVtdIHtcbiAgICBsZXQgbGlzdCA9IHJvdXRlcy5tYXAoeCA9PiB4IHx8IEZVTExfUk9VVEUpLm1hcCh4ID0+IFt4WzBdLCB4WzFdXSBhcyBbbnVtYmVyLCBudW1iZXJdKTtcbiAgICBzb3J0KGxpc3QsIHggPT4geFswXSk7XG4gICAgbGV0IG1lcmdlZDogW251bWJlciwgbnVtYmVyXVtdID0gW107XG4gICAgZm9yIChsZXQgcm91dGUgb2YgbGlzdCkge1xuICAgICAgICBsZXQgbGFzdCA9IG1lcmdlZFttZXJnZWQubGVuZ3RoIC0gMV07XG4gICAgICAgIGlmIChsYXN0ICYmIHJvdXRlWzBdIDw9IGxhc3RbMV0pIHtcbiAgICAgICAgICAgIGxhc3RbMV0gPSBNYXRoLm1heChsYXN0WzFdLCByb3V0ZVsxXSk7XG4gICAgICAgICAgICBjb250aW51ZTtcbiAgICAgICAgfVxuICAgICAgICBtZXJnZWQucHVzaChyb3V0ZSk7XG4gICAgfVxuICAgIHJldHVybiBtZXJnZWQ7XG59XG5cbmZ1bmN0aW9uIHN1bW1hcml6ZVNlbGYoc2VsZkVudHJpZXM6IEhvc3RlZENvbmZpZ1tdLCBub3c6IG51bWJlcik6IFNlbGZTdW1tYXJ5IHwgdW5kZWZpbmVkIHtcbiAgICBsZXQgY3VycmVudCA9IHNlbGZFbnRyaWVzLmZpbHRlcih4ID0+IHgudmFsaWRXaW5kb3dbMF0gPD0gbm93ICYmIG5vdyA8IHgudmFsaWRXaW5kb3dbMV0pO1xuICAgIGlmICghY3VycmVudC5sZW5ndGgpIHtcbiAgICAgICAgbGV0IG5lYXJlc3QgPSBzZWxlY3RFbnRyeUF0KHNlbGZFbnRyaWVzLCBub3cpO1xuICAgICAgICBjdXJyZW50ID0gbmVhcmVzdCAmJiBbbmVhcmVzdF0gfHwgW107XG4gICAgfVxuICAgIGlmICghY3VycmVudC5sZW5ndGgpIHtcbiAgICAgICAgcmV0dXJuIHVuZGVmaW5lZDtcbiAgICB9XG4gICAgcmV0dXJuIHtcbiAgICAgICAgcm91dGVzOiBtZXJnZVJvdXRlcyhjdXJyZW50Lm1hcCh4ID0+IHgucm91dGUpKSxcbiAgICAgICAgcHVibGljOiBjdXJyZW50LnNvbWUoeCA9PiB4LnB1YmxpYyksXG4gICAgICAgIGltbXV0YWJsZTogY3VycmVudC5zb21lKHggPT4geC5pbW11dGFibGUpLFxuICAgICAgICBub0Z1bGxTeW5jOiBjdXJyZW50LnNvbWUoeCA9PiB4Lm5vRnVsbFN5bmMpLFxuICAgICAgICByYXdEaXNrOiBjdXJyZW50LnNvbWUoeCA9PiB4LnJhd0Rpc2spLFxuICAgICAgICByZWFkZXJEaXNrTGltaXQ6IGN1cnJlbnQuZmluZCh4ID0+IHgucmVhZGVyRGlza0xpbWl0ICE9PSB1bmRlZmluZWQpPy5yZWFkZXJEaXNrTGltaXQsXG4gICAgfTtcbn1cblxuZXhwb3J0IHR5cGUgU3RvcmVTb3VyY2VTcGVjID0geyBzb3VyY2VDb25maWc/OiBTb3VyY2VDb25maWc7IHZhbGlkV2luZG93OiBbbnVtYmVyLCBudW1iZXJdOyByb3V0ZT86IFtudW1iZXIsIG51bWJlcl07IG5vRnVsbFN5bmM/OiBib29sZWFuIH07XG5leHBvcnQgdHlwZSBTdG9yZVBsYW5TdG9yZSA9IHtcbiAgICByb3V0ZUtleTogc3RyaW5nO1xuICAgIHJvdXRlPzogW251bWJlciwgbnVtYmVyXTtcbiAgICBlbnRyaWVzOiBIb3N0ZWRDb25maWdbXTtcbiAgICByYXdEaXNrOiBib29sZWFuO1xuICAgIHJlYWRlckRpc2tMaW1pdD86IG51bWJlcjtcbiAgICBzb3VyY2VTcGVjczogU3RvcmVTb3VyY2VTcGVjW107XG59O1xuZXhwb3J0IHR5cGUgU3RvcmVQbGFuID0ge1xuICAgIHNlbGZFbnRyaWVzOiBIb3N0ZWRDb25maWdbXTtcbiAgICBzZWxmOiBTZWxmU3VtbWFyeSB8IHVuZGVmaW5lZDtcbiAgICBzdG9yZXM6IFN0b3JlUGxhblN0b3JlW107XG4gICAgc3RydWN0dXJlS2V5OiBzdHJpbmc7XG59O1xuXG5leHBvcnQgZnVuY3Rpb24gY29tcHV0ZVN0b3JlUGxhbihhY2NvdW50OiBzdHJpbmcsIGJ1Y2tldE5hbWU6IHN0cmluZywgcm91dGluZzogUmVtb3RlQ29uZmlnKTogU3RvcmVQbGFuIHtcbiAgICBsZXQgc2VsZkluZGV4ZXMgPSBmaW5kU2VsZkluZGV4ZXMocm91dGluZywgYWNjb3VudCwgYnVja2V0TmFtZSk7XG4gICAgbGV0IHNlbGZFbnRyaWVzID0gc2VsZkluZGV4ZXMubWFwKGkgPT4gcm91dGluZy5zb3VyY2VzW2ldIGFzIEhvc3RlZENvbmZpZyk7XG4gICAgbGV0IHNlbGYgPSBzdW1tYXJpemVTZWxmKHNlbGZFbnRyaWVzLCBEYXRlLm5vdygpKTtcbiAgICBsZXQgb3duSW5kZXhlcyA9IG5ldyBTZXQoc2VsZkluZGV4ZXMpO1xuICAgIC8vIE9uZSBzdG9yZSBwZXIgZGlzdGluY3Qgcm91dGUgYW1vbmcgQUxMIG91ciBlbnRyaWVzIC0gcGFzdCBhbmQgZnV0dXJlIHdpbmRvd3MgaW5jbHVkZWQsIHNvIGhpc3RvcmljYWwgcm91dGUgZm9sZGVycyBrZWVwIHNlcnZpbmcgdGhlaXIgZGF0YSBhbmQgdXBjb21pbmcgcm91dGVzIHN5bmMgYWhlYWQgb2YgdGhlaXIgd2luZG93XG4gICAgbGV0IGdyb3VwcyA9IG5ldyBNYXA8c3RyaW5nLCB7IHJvdXRlPzogW251bWJlciwgbnVtYmVyXTsgZW50cmllczogSG9zdGVkQ29uZmlnW107IGZpcnN0SW5kZXg6IG51bWJlciB9PigpO1xuICAgIGZvciAobGV0IGkgb2Ygc2VsZkluZGV4ZXMpIHtcbiAgICAgICAgbGV0IGVudHJ5ID0gcm91dGluZy5zb3VyY2VzW2ldIGFzIEhvc3RlZENvbmZpZztcbiAgICAgICAgbGV0IHJvdXRlS2V5ID0gSlNPTi5zdHJpbmdpZnkoZW50cnkucm91dGUgfHwgRlVMTF9ST1VURSk7XG4gICAgICAgIGxldCBncm91cCA9IGdyb3Vwcy5nZXQocm91dGVLZXkpO1xuICAgICAgICBpZiAoIWdyb3VwKSB7XG4gICAgICAgICAgICBncm91cCA9IHsgcm91dGU6IGVudHJ5LnJvdXRlLCBlbnRyaWVzOiBbXSwgZmlyc3RJbmRleDogaSB9O1xuICAgICAgICAgICAgZ3JvdXBzLnNldChyb3V0ZUtleSwgZ3JvdXApO1xuICAgICAgICB9XG4gICAgICAgIGdyb3VwLmVudHJpZXMucHVzaChlbnRyeSk7XG4gICAgfVxuICAgIGxldCBzdG9yZXM6IFN0b3JlUGxhblN0b3JlW10gPSBbXTtcbiAgICBmb3IgKGxldCBbcm91dGVLZXksIGdyb3VwXSBvZiBncm91cHMpIHtcbiAgICAgICAgbGV0IGFuY2hvciA9IHNlbGVjdEVudHJ5QXQoZ3JvdXAuZW50cmllcywgRGF0ZS5ub3coKSk7XG4gICAgICAgIGxldCBkaXNrV2luZG93OiBbbnVtYmVyLCBudW1iZXJdID0gWzAsIDBdO1xuICAgICAgICBpZiAoYW5jaG9yKSB7XG4gICAgICAgICAgICBsZXQgW3N0YXJ0LCBlbmRdID0gYW5jaG9yLnZhbGlkV2luZG93O1xuICAgICAgICAgICAgbGV0IG1lcmdlZCA9IHRydWU7XG4gICAgICAgICAgICB3aGlsZSAobWVyZ2VkKSB7XG4gICAgICAgICAgICAgICAgbWVyZ2VkID0gZmFsc2U7XG4gICAgICAgICAgICAgICAgZm9yIChsZXQgZW50cnkgb2YgZ3JvdXAuZW50cmllcykge1xuICAgICAgICAgICAgICAgICAgICBsZXQgW2VudHJ5U3RhcnQsIGVudHJ5RW5kXSA9IGVudHJ5LnZhbGlkV2luZG93O1xuICAgICAgICAgICAgICAgICAgICBpZiAoZW50cnlTdGFydCA+IGVuZCB8fCBlbnRyeUVuZCA8IHN0YXJ0KSBjb250aW51ZTtcbiAgICAgICAgICAgICAgICAgICAgaWYgKGVudHJ5U3RhcnQgPCBzdGFydCB8fCBlbnRyeUVuZCA+IGVuZCkge1xuICAgICAgICAgICAgICAgICAgICAgICAgc3RhcnQgPSBNYXRoLm1pbihzdGFydCwgZW50cnlTdGFydCk7XG4gICAgICAgICAgICAgICAgICAgICAgICBlbmQgPSBNYXRoLm1heChlbmQsIGVudHJ5RW5kKTtcbiAgICAgICAgICAgICAgICAgICAgICAgIG1lcmdlZCA9IHRydWU7XG4gICAgICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICBkaXNrV2luZG93ID0gW3N0YXJ0LCBlbmRdO1xuICAgICAgICB9XG4gICAgICAgIGxldCBzb3VyY2VTcGVjczogU3RvcmVTb3VyY2VTcGVjW10gPSBbeyB2YWxpZFdpbmRvdzogZGlza1dpbmRvdyB9XTtcbiAgICAgICAgbGV0IG5vRnVsbFN5bmMgPSBncm91cC5lbnRyaWVzLnNvbWUoeCA9PiB4Lm5vRnVsbFN5bmMpO1xuICAgICAgICBmb3IgKGxldCBpID0gZ3JvdXAuZmlyc3RJbmRleCArIDE7IGkgPCByb3V0aW5nLnNvdXJjZXMubGVuZ3RoOyBpKyspIHtcbiAgICAgICAgICAgIGxldCBzb3VyY2UgPSByb3V0aW5nLnNvdXJjZXNbaV07XG4gICAgICAgICAgICBpZiAodHlwZW9mIHNvdXJjZSA9PT0gXCJzdHJpbmdcIiB8fCBvd25JbmRleGVzLmhhcyhpKSkgY29udGludWU7XG4gICAgICAgICAgICBsZXQgc2hhcmVkUm91dGUgPSByb3V0ZUludGVyc2VjdGlvbihncm91cC5yb3V0ZSwgc291cmNlLnJvdXRlKTtcbiAgICAgICAgICAgIGlmICghc2hhcmVkUm91dGUpIGNvbnRpbnVlO1xuICAgICAgICAgICAgc291cmNlU3BlY3MucHVzaCh7IHNvdXJjZUNvbmZpZzogc291cmNlLCB2YWxpZFdpbmRvdzogc291cmNlLnZhbGlkV2luZG93LCByb3V0ZTogc2hhcmVkUm91dGUsIG5vRnVsbFN5bmM6IHNvdXJjZS5ub0Z1bGxTeW5jIHx8IG5vRnVsbFN5bmMgfSk7XG4gICAgICAgIH1cbiAgICAgICAgc3RvcmVzLnB1c2goe1xuICAgICAgICAgICAgcm91dGVLZXksXG4gICAgICAgICAgICByb3V0ZTogZ3JvdXAucm91dGUsXG4gICAgICAgICAgICBlbnRyaWVzOiBncm91cC5lbnRyaWVzLFxuICAgICAgICAgICAgcmF3RGlzazogZ3JvdXAuZW50cmllcy5zb21lKHggPT4geC5yYXdEaXNrKSxcbiAgICAgICAgICAgIHJlYWRlckRpc2tMaW1pdDogZ3JvdXAuZW50cmllcy5maW5kKHggPT4geC5yZWFkZXJEaXNrTGltaXQgIT09IHVuZGVmaW5lZCk/LnJlYWRlckRpc2tMaW1pdCxcbiAgICAgICAgICAgIHNvdXJjZVNwZWNzLFxuICAgICAgICB9KTtcbiAgICB9XG4gICAgaWYgKCFzdG9yZXMubGVuZ3RoKSB7XG4gICAgICAgIC8vIE5vdCBpbiB0aGUgY29uZmlnIGF0IGFsbDogc3RpbGwgc2VydmUgd2hhdGV2ZXIgdGhlIHBsYWluIGZvbGRlciBob2xkcywgdGhyb3VnaCBvbmUgaW5lcnQgZnVsbC1yb3V0ZSBzdG9yZVxuICAgICAgICBzdG9yZXMucHVzaCh7IHJvdXRlS2V5OiBKU09OLnN0cmluZ2lmeShGVUxMX1JPVVRFKSwgcm91dGU6IHVuZGVmaW5lZCwgZW50cmllczogW10sIHJhd0Rpc2s6IGZhbHNlLCByZWFkZXJEaXNrTGltaXQ6IHVuZGVmaW5lZCwgc291cmNlU3BlY3M6IFt7IHZhbGlkV2luZG93OiBbMCwgMF0gfV0gfSk7XG4gICAgfVxuICAgIGxldCBzdHJ1Y3R1cmVLZXkgPSBKU09OLnN0cmluZ2lmeShzdG9yZXMubWFwKHMgPT4gKHsgcm91dGVLZXk6IHMucm91dGVLZXksIHJhd0Rpc2s6IHMucmF3RGlzaywgcmVhZGVyRGlza0xpbWl0OiBzLnJlYWRlckRpc2tMaW1pdCB9KSkpO1xuICAgIHJldHVybiB7IHNlbGZFbnRyaWVzLCBzZWxmLCBzdG9yZXMsIHN0cnVjdHVyZUtleSB9O1xufVxuIl19
149
+ /* _JS_SOURCE_HASH = "7971b00e29b1840ce5aa254d6b5e2b8db571965b03d2363c06aba914748aae66"; */
@@ -0,0 +1,40 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true , configurable: true});
3
+ //exports.assertValidArgs = exports.assertValidPath = exports.assertValidName = void 0;
4
+ function assertValidName(value, kind) {
5
+ if (!/^[\w-]{1,64}$/.test(value)) {
6
+ throw new Error(`Invalid ${kind} ${JSON.stringify(value)}, expected 1-64 characters of letters/numbers/underscore/dash`);
7
+ }
8
+ }
9
+ exports.assertValidName = assertValidName;
10
+ function assertValidPath(path) {
11
+ if (Buffer.from(path, "utf8").length > 1000) {
12
+ throw new Error(`Path too long: ${path.length} characters > 1000. Path: ${path.slice(0, 200)}`);
13
+ }
14
+ if (!path || path.startsWith("/") || path.endsWith("/") || path.includes("//") || path.includes("\\") || path.includes("\x00")) {
15
+ throw new Error(`Invalid path ${JSON.stringify(path.slice(0, 200))}, paths cannot be empty, start or end with /, or contain //, backslashes, or null characters`);
16
+ }
17
+ if (path.split("/").some(part => part === "." || part === "..")) {
18
+ throw new Error(`Invalid path ${JSON.stringify(path.slice(0, 200))}, paths cannot contain . or .. segments`);
19
+ }
20
+ }
21
+ exports.assertValidPath = assertValidPath;
22
+ /** Method decorator: validates the well-known fields of the method's single config-object argument - account/bucketName as names, path as a path - before the method runs. Fields the config doesn't have are skipped, so it applies to every API method uniformly. prefix is deliberately NOT validated: prefixes may be empty or end with "/", both invalid for paths. */
23
+ function assertValidArgs(target, key, descriptor) {
24
+ let original = descriptor.value;
25
+ descriptor.value = function (...args) {
26
+ let config = args[0];
27
+ if (config && typeof config === "object") {
28
+ if ("account" in config)
29
+ assertValidName(config.account, "account");
30
+ if ("bucketName" in config)
31
+ assertValidName(config.bucketName, "bucket name");
32
+ if ("path" in config)
33
+ assertValidPath(config.path);
34
+ }
35
+ return original.apply(this, args);
36
+ };
37
+ }
38
+ exports.assertValidArgs = assertValidArgs;
39
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidmFsaWRhdGlvbi5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbInZhbGlkYXRpb24udHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7O0FBQUEsU0FBZ0IsZUFBZSxDQUFDLEtBQWEsRUFBRSxJQUFZO0lBQ3ZELElBQUksQ0FBQyxlQUFlLENBQUMsSUFBSSxDQUFDLEtBQUssQ0FBQyxFQUFFLENBQUM7UUFDL0IsTUFBTSxJQUFJLEtBQUssQ0FBQyxXQUFXLElBQUksSUFBSSxJQUFJLENBQUMsU0FBUyxDQUFDLEtBQUssQ0FBQywrREFBK0QsQ0FBQyxDQUFDO0lBQzdILENBQUM7QUFDTCxDQUFDO0FBSkQsMENBSUM7QUFFRCxTQUFnQixlQUFlLENBQUMsSUFBWTtJQUN4QyxJQUFJLE1BQU0sQ0FBQyxJQUFJLENBQUMsSUFBSSxFQUFFLE1BQU0sQ0FBQyxDQUFDLE1BQU0sR0FBRyxJQUFJLEVBQUUsQ0FBQztRQUMxQyxNQUFNLElBQUksS0FBSyxDQUFDLGtCQUFrQixJQUFJLENBQUMsTUFBTSw2QkFBNkIsSUFBSSxDQUFDLEtBQUssQ0FBQyxDQUFDLEVBQUUsR0FBRyxDQUFDLEVBQUUsQ0FBQyxDQUFDO0lBQ3BHLENBQUM7SUFDRCxJQUFJLENBQUMsSUFBSSxJQUFJLElBQUksQ0FBQyxVQUFVLENBQUMsR0FBRyxDQUFDLElBQUksSUFBSSxDQUFDLFFBQVEsQ0FBQyxHQUFHLENBQUMsSUFBSSxJQUFJLENBQUMsUUFBUSxDQUFDLElBQUksQ0FBQyxJQUFJLElBQUksQ0FBQyxRQUFRLENBQUMsSUFBSSxDQUFDLElBQUksSUFBSSxDQUFDLFFBQVEsQ0FBQyxNQUFNLENBQUMsRUFBRSxDQUFDO1FBQzdILE1BQU0sSUFBSSxLQUFLLENBQUMsZ0JBQWdCLElBQUksQ0FBQyxTQUFTLENBQUMsSUFBSSxDQUFDLEtBQUssQ0FBQyxDQUFDLEVBQUUsR0FBRyxDQUFDLENBQUMsOEZBQThGLENBQUMsQ0FBQztJQUN0SyxDQUFDO0lBQ0QsSUFBSSxJQUFJLENBQUMsS0FBSyxDQUFDLEdBQUcsQ0FBQyxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsRUFBRSxDQUFDLElBQUksS0FBSyxHQUFHLElBQUksSUFBSSxLQUFLLElBQUksQ0FBQyxFQUFFLENBQUM7UUFDOUQsTUFBTSxJQUFJLEtBQUssQ0FBQyxnQkFBZ0IsSUFBSSxDQUFDLFNBQVMsQ0FBQyxJQUFJLENBQUMsS0FBSyxDQUFDLENBQUMsRUFBRSxHQUFHLENBQUMsQ0FBQyx5Q0FBeUMsQ0FBQyxDQUFDO0lBQ2pILENBQUM7QUFDTCxDQUFDO0FBVkQsMENBVUM7QUFFRCw0V0FBNFc7QUFDNVcsU0FBZ0IsZUFBZSxDQUFDLE1BQWUsRUFBRSxHQUFXLEVBQUUsVUFBOEI7SUFDeEYsSUFBSSxRQUFRLEdBQUcsVUFBVSxDQUFDLEtBQXdDLENBQUM7SUFDbkUsVUFBVSxDQUFDLEtBQUssR0FBRyxVQUFVLEdBQUcsSUFBZTtRQUMzQyxJQUFJLE1BQU0sR0FBRyxJQUFJLENBQUMsQ0FBQyxDQUF5RSxDQUFDO1FBQzdGLElBQUksTUFBTSxJQUFJLE9BQU8sTUFBTSxLQUFLLFFBQVEsRUFBRSxDQUFDO1lBQ3ZDLElBQUksU0FBUyxJQUFJLE1BQU07Z0JBQUUsZUFBZSxDQUFDLE1BQU0sQ0FBQyxPQUFpQixFQUFFLFNBQVMsQ0FBQyxDQUFDO1lBQzlFLElBQUksWUFBWSxJQUFJLE1BQU07Z0JBQUUsZUFBZSxDQUFDLE1BQU0sQ0FBQyxVQUFvQixFQUFFLGFBQWEsQ0FBQyxDQUFDO1lBQ3hGLElBQUksTUFBTSxJQUFJLE1BQU07Z0JBQUUsZUFBZSxDQUFDLE1BQU0sQ0FBQyxJQUFjLENBQUMsQ0FBQztRQUNqRSxDQUFDO1FBQ0QsT0FBTyxRQUFRLENBQUMsS0FBSyxDQUFDLElBQUksRUFBRSxJQUFJLENBQUMsQ0FBQztJQUN0QyxDQUFDLENBQUM7QUFDTixDQUFDO0FBWEQsMENBV0MiLCJzb3VyY2VzQ29udGVudCI6WyJleHBvcnQgZnVuY3Rpb24gYXNzZXJ0VmFsaWROYW1lKHZhbHVlOiBzdHJpbmcsIGtpbmQ6IHN0cmluZyk6IHZvaWQge1xuICAgIGlmICghL15bXFx3LV17MSw2NH0kLy50ZXN0KHZhbHVlKSkge1xuICAgICAgICB0aHJvdyBuZXcgRXJyb3IoYEludmFsaWQgJHtraW5kfSAke0pTT04uc3RyaW5naWZ5KHZhbHVlKX0sIGV4cGVjdGVkIDEtNjQgY2hhcmFjdGVycyBvZiBsZXR0ZXJzL251bWJlcnMvdW5kZXJzY29yZS9kYXNoYCk7XG4gICAgfVxufVxuXG5leHBvcnQgZnVuY3Rpb24gYXNzZXJ0VmFsaWRQYXRoKHBhdGg6IHN0cmluZyk6IHZvaWQge1xuICAgIGlmIChCdWZmZXIuZnJvbShwYXRoLCBcInV0ZjhcIikubGVuZ3RoID4gMTAwMCkge1xuICAgICAgICB0aHJvdyBuZXcgRXJyb3IoYFBhdGggdG9vIGxvbmc6ICR7cGF0aC5sZW5ndGh9IGNoYXJhY3RlcnMgPiAxMDAwLiBQYXRoOiAke3BhdGguc2xpY2UoMCwgMjAwKX1gKTtcbiAgICB9XG4gICAgaWYgKCFwYXRoIHx8IHBhdGguc3RhcnRzV2l0aChcIi9cIikgfHwgcGF0aC5lbmRzV2l0aChcIi9cIikgfHwgcGF0aC5pbmNsdWRlcyhcIi8vXCIpIHx8IHBhdGguaW5jbHVkZXMoXCJcXFxcXCIpIHx8IHBhdGguaW5jbHVkZXMoXCJcXHgwMFwiKSkge1xuICAgICAgICB0aHJvdyBuZXcgRXJyb3IoYEludmFsaWQgcGF0aCAke0pTT04uc3RyaW5naWZ5KHBhdGguc2xpY2UoMCwgMjAwKSl9LCBwYXRocyBjYW5ub3QgYmUgZW1wdHksIHN0YXJ0IG9yIGVuZCB3aXRoIC8sIG9yIGNvbnRhaW4gLy8sIGJhY2tzbGFzaGVzLCBvciBudWxsIGNoYXJhY3RlcnNgKTtcbiAgICB9XG4gICAgaWYgKHBhdGguc3BsaXQoXCIvXCIpLnNvbWUocGFydCA9PiBwYXJ0ID09PSBcIi5cIiB8fCBwYXJ0ID09PSBcIi4uXCIpKSB7XG4gICAgICAgIHRocm93IG5ldyBFcnJvcihgSW52YWxpZCBwYXRoICR7SlNPTi5zdHJpbmdpZnkocGF0aC5zbGljZSgwLCAyMDApKX0sIHBhdGhzIGNhbm5vdCBjb250YWluIC4gb3IgLi4gc2VnbWVudHNgKTtcbiAgICB9XG59XG5cbi8qKiBNZXRob2QgZGVjb3JhdG9yOiB2YWxpZGF0ZXMgdGhlIHdlbGwta25vd24gZmllbGRzIG9mIHRoZSBtZXRob2QncyBzaW5nbGUgY29uZmlnLW9iamVjdCBhcmd1bWVudCAtIGFjY291bnQvYnVja2V0TmFtZSBhcyBuYW1lcywgcGF0aCBhcyBhIHBhdGggLSBiZWZvcmUgdGhlIG1ldGhvZCBydW5zLiBGaWVsZHMgdGhlIGNvbmZpZyBkb2Vzbid0IGhhdmUgYXJlIHNraXBwZWQsIHNvIGl0IGFwcGxpZXMgdG8gZXZlcnkgQVBJIG1ldGhvZCB1bmlmb3JtbHkuIHByZWZpeCBpcyBkZWxpYmVyYXRlbHkgTk9UIHZhbGlkYXRlZDogcHJlZml4ZXMgbWF5IGJlIGVtcHR5IG9yIGVuZCB3aXRoIFwiL1wiLCBib3RoIGludmFsaWQgZm9yIHBhdGhzLiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGFzc2VydFZhbGlkQXJncyh0YXJnZXQ6IHVua25vd24sIGtleTogc3RyaW5nLCBkZXNjcmlwdG9yOiBQcm9wZXJ0eURlc2NyaXB0b3IpOiB2b2lkIHtcbiAgICBsZXQgb3JpZ2luYWwgPSBkZXNjcmlwdG9yLnZhbHVlIGFzICguLi5hcmdzOiB1bmtub3duW10pID0+IHVua25vd247XG4gICAgZGVzY3JpcHRvci52YWx1ZSA9IGZ1bmN0aW9uICguLi5hcmdzOiB1bmtub3duW10pOiB1bmtub3duIHtcbiAgICAgICAgbGV0IGNvbmZpZyA9IGFyZ3NbMF0gYXMgeyBhY2NvdW50Pzogc3RyaW5nOyBidWNrZXROYW1lPzogc3RyaW5nOyBwYXRoPzogc3RyaW5nIH0gfCB1bmRlZmluZWQ7XG4gICAgICAgIGlmIChjb25maWcgJiYgdHlwZW9mIGNvbmZpZyA9PT0gXCJvYmplY3RcIikge1xuICAgICAgICAgICAgaWYgKFwiYWNjb3VudFwiIGluIGNvbmZpZykgYXNzZXJ0VmFsaWROYW1lKGNvbmZpZy5hY2NvdW50IGFzIHN0cmluZywgXCJhY2NvdW50XCIpO1xuICAgICAgICAgICAgaWYgKFwiYnVja2V0TmFtZVwiIGluIGNvbmZpZykgYXNzZXJ0VmFsaWROYW1lKGNvbmZpZy5idWNrZXROYW1lIGFzIHN0cmluZywgXCJidWNrZXQgbmFtZVwiKTtcbiAgICAgICAgICAgIGlmIChcInBhdGhcIiBpbiBjb25maWcpIGFzc2VydFZhbGlkUGF0aChjb25maWcucGF0aCBhcyBzdHJpbmcpO1xuICAgICAgICB9XG4gICAgICAgIHJldHVybiBvcmlnaW5hbC5hcHBseSh0aGlzLCBhcmdzKTtcbiAgICB9O1xufVxuIl19
40
+ /* _JS_SOURCE_HASH = "86f30936cf7059feaf31e421a3bf6a44c9adc86e104f6cbadcc59d1b90114c7f"; */
@@ -14,7 +14,7 @@ async function main() {
14
14
  let { address, port } = parseStorageUrl(url);
15
15
  let nodeId = SocketFunction.connect({ address, port });
16
16
  await authenticateStorage({ address, port, nodeId });
17
- let record = await RemoteStorageController.nodes[nodeId].adminGrantAccess(requestId);
17
+ let record = await RemoteStorageController.nodes[nodeId].adminGrantAccess({ requestId });
18
18
  console.log(`Granted machine ${record.machineId} access to account ${JSON.stringify(record.account)}`);
19
19
  process.exit(0);
20
20
  }
@@ -1,9 +1,8 @@
1
- import { RemoteConfig, HostedConfig, BackblazeConfig } from "../IArchives";
1
+ import { RemoteConfig, SourceConfig } from "../IArchives";
2
2
  export declare const INTERMEDIATE_EXPIRE_GRACE: number;
3
3
  /** Adding or removing intermediates is a real config update, so it takes a real version increment - but a proportional one, so it stays far below whatever the author's next version would be (whether they count 1, 2, 3 or use timestamps), and a million of them still fit under it. */
4
4
  export declare function nextIntermediateVersion(version: number): number;
5
- type ObjectSource = HostedConfig | BackblazeConfig;
6
- export declare function getIntermediateSources(config: RemoteConfig): ObjectSource[];
5
+ export declare function getIntermediateSources(config: RemoteConfig): SourceConfig[];
7
6
  export declare function hasIntermediateSources(config: RemoteConfig): boolean;
8
7
  /** Removes every intermediate entry and rejoins the windows it split, giving back the underlying configuration. Two configs that resolve equal differ only by intermediates. */
9
8
  export declare function resolveIntermediateSources(config: RemoteConfig): RemoteConfig;
@@ -17,5 +16,4 @@ export declare function injectIntermediateSource(config: RemoteConfig, inject: {
17
16
  /** Intermediates whose window ended more than INTERMEDIATE_EXPIRE_GRACE ago are removed, and the windows they split are rejoined. */
18
17
  export declare function expireIntermediateSources(config: RemoteConfig, now: number): RemoteConfig;
19
18
  /** The url of the entry an intermediate was split out of - the neighbour it touches. */
20
- export declare function findSplitUrl(config: RemoteConfig, intermediate: ObjectSource): string | undefined;
21
- export {};
19
+ export declare function findSplitUrl(config: RemoteConfig, intermediate: SourceConfig): string | undefined;
@@ -1,4 +1,4 @@
1
- import { RemoteConfig, RemoteConfigBase, HostedConfig, BackblazeConfig } from "../IArchives";
1
+ import { RemoteConfig, RemoteConfigBase, SourceConfig } from "../IArchives";
2
2
 
3
3
  export const INTERMEDIATE_EXPIRE_GRACE = 15 * 60 * 1000;
4
4
  const INTERMEDIATE_VERSION_FRACTION = 1_000_000;
@@ -12,38 +12,36 @@ export function nextIntermediateVersion(version: number): number {
12
12
  return version + step;
13
13
  }
14
14
 
15
- type ObjectSource = HostedConfig | BackblazeConfig;
16
-
17
- function isObjectSource(source: RemoteConfigBase): source is ObjectSource {
15
+ function isSourceConfig(source: RemoteConfigBase): source is SourceConfig {
18
16
  return typeof source !== "string";
19
17
  }
20
18
 
21
19
  function cloneSource(source: RemoteConfigBase): RemoteConfigBase {
22
- if (!isObjectSource(source)) return source;
20
+ if (!isSourceConfig(source)) return source;
23
21
  return { ...source, validWindow: [source.validWindow[0], source.validWindow[1]] };
24
22
  }
25
23
 
26
- function joinKey(source: ObjectSource): string {
24
+ function joinKey(source: SourceConfig): string {
27
25
  return JSON.stringify({ ...source, validWindow: undefined });
28
26
  }
29
27
 
30
- export function getIntermediateSources(config: RemoteConfig): ObjectSource[] {
31
- return config.sources.filter(x => isObjectSource(x) && x.intermediate) as ObjectSource[];
28
+ export function getIntermediateSources(config: RemoteConfig): SourceConfig[] {
29
+ return config.sources.filter(x => isSourceConfig(x) && x.intermediate) as SourceConfig[];
32
30
  }
33
31
 
34
32
  export function hasIntermediateSources(config: RemoteConfig): boolean {
35
- return config.sources.some(x => isObjectSource(x) && x.intermediate);
33
+ return config.sources.some(x => isSourceConfig(x) && x.intermediate);
36
34
  }
37
35
 
38
36
  /** Removes every intermediate entry and rejoins the windows it split, giving back the underlying configuration. Two configs that resolve equal differ only by intermediates. */
39
37
  export function resolveIntermediateSources(config: RemoteConfig): RemoteConfig {
40
38
  if (!hasIntermediateSources(config)) return config;
41
39
  let removed = getIntermediateSources(config);
42
- let sources = config.sources.filter(x => !isObjectSource(x) || !x.intermediate).map(cloneSource);
40
+ let sources = config.sources.filter(x => !isSourceConfig(x) || !x.intermediate).map(cloneSource);
43
41
  for (let gap of removed) {
44
42
  let [gapStart, gapEnd] = gap.validWindow;
45
- let before = sources.find(x => isObjectSource(x) && x.validWindow[1] === gapStart) as ObjectSource | undefined;
46
- let after = sources.find(x => isObjectSource(x) && x.validWindow[0] === gapEnd) as ObjectSource | undefined;
43
+ let before = sources.find(x => isSourceConfig(x) && x.validWindow[1] === gapStart) as SourceConfig | undefined;
44
+ let after = sources.find(x => isSourceConfig(x) && x.validWindow[0] === gapEnd) as SourceConfig | undefined;
47
45
  if (before && after && joinKey(before) === joinKey(after)) {
48
46
  before.validWindow = [before.validWindow[0], after.validWindow[1]];
49
47
  sources.splice(sources.indexOf(after), 1);
@@ -64,12 +62,12 @@ export function resolveIntermediateSources(config: RemoteConfig): RemoteConfig {
64
62
  export function injectIntermediateSource(config: RemoteConfig, inject: { splitUrl: string; intermediateUrl: string; start: number; end: number }): RemoteConfig {
65
63
  let { splitUrl, intermediateUrl, start, end } = inject;
66
64
  if (start >= end) return config;
67
- if (config.sources.some(x => isObjectSource(x) && x.intermediate && x.url === intermediateUrl && x.validWindow[0] === start && x.validWindow[1] === end)) {
65
+ if (config.sources.some(x => isSourceConfig(x) && x.intermediate && x.url === intermediateUrl && x.validWindow[0] === start && x.validWindow[1] === end)) {
68
66
  return config;
69
67
  }
70
68
  let sources: RemoteConfigBase[] = [];
71
69
  for (let source of config.sources) {
72
- if (!isObjectSource(source) || source.url !== splitUrl || source.intermediate) {
70
+ if (!isSourceConfig(source) || source.url !== splitUrl || source.intermediate) {
73
71
  sources.push(cloneSource(source));
74
72
  continue;
75
73
  }
@@ -110,8 +108,8 @@ export function expireIntermediateSources(config: RemoteConfig, now: number): Re
110
108
  }
111
109
 
112
110
  /** The url of the entry an intermediate was split out of - the neighbour it touches. */
113
- export function findSplitUrl(config: RemoteConfig, intermediate: ObjectSource): string | undefined {
111
+ export function findSplitUrl(config: RemoteConfig, intermediate: SourceConfig): string | undefined {
114
112
  let [start, end] = intermediate.validWindow;
115
- let neighbour = config.sources.find(x => isObjectSource(x) && !x.intermediate && (x.validWindow[1] === start || x.validWindow[0] === end)) as ObjectSource | undefined;
113
+ let neighbour = config.sources.find(x => isSourceConfig(x) && !x.intermediate && (x.validWindow[1] === start || x.validWindow[0] === end)) as SourceConfig | undefined;
116
114
  return neighbour?.url;
117
115
  }
@@ -1,6 +1,6 @@
1
1
  /// <reference types="node" />
2
2
  /// <reference types="node" />
3
- import { RemoteConfig, RemoteConfigBase, HostedConfig, BackblazeConfig, ArchiveFileInfo, ChangesAfterConfig } from "../IArchives";
3
+ import { RemoteConfig, RemoteConfigBase, SourceConfig, ArchiveFileInfo, ChangesAfterConfig } from "../IArchives";
4
4
  export declare const ROUTING_FILE = "storage/storagerouting.json";
5
5
  /** The variable-shard route override embedded in the key ("<sentinel>_<value>", see VARIABLE_SHARD), or undefined when the key has no sentinel or the sentinel has no value yet. */
6
6
  export declare function parseVariableRoute(key: string): number | undefined;
@@ -26,7 +26,7 @@ export declare function parseBackblazeUrl(url: string): {
26
26
  bucketName: string;
27
27
  };
28
28
  export declare function replaceHostedUrlPort(url: string, port: number): string;
29
- export declare function normalizeSource(source: RemoteConfigBase): HostedConfig | BackblazeConfig;
29
+ export declare function normalizeSource(source: RemoteConfigBase): SourceConfig;
30
30
  export declare function normalizeRemoteConfig(config: RemoteConfig | RemoteConfigBase): RemoteConfig;
31
31
  export declare function parseRoutingData(data: Buffer): RemoteConfig;
32
32
  export declare function serializeRemoteConfig(config: RemoteConfig): Buffer;
@@ -2,7 +2,7 @@ import { sort } from "socket-function/src/misc";
2
2
  import { getBufferInt } from "socket-function/src/bits";
3
3
  import { setFlag } from "socket-function/require/compileFlags";
4
4
  import jsSha256 from "js-sha256";
5
- import { RemoteConfig, RemoteConfigBase, HostedConfig, BackblazeConfig, ArchiveFileInfo, ChangesAfterConfig, FULL_VALID_WINDOW, FULL_ROUTE, VARIABLE_SHARD } from "../IArchives";
5
+ import { RemoteConfig, RemoteConfigBase, SourceConfig, ArchiveFileInfo, ChangesAfterConfig, FULL_VALID_WINDOW, FULL_ROUTE, VARIABLE_SHARD } from "../IArchives";
6
6
 
7
7
  setFlag(require, "js-sha256", "allowclient", true);
8
8
 
@@ -109,7 +109,7 @@ export function replaceHostedUrlPort(url: string, port: number): string {
109
109
  return u.toString();
110
110
  }
111
111
 
112
- export function normalizeSource(source: RemoteConfigBase): HostedConfig | BackblazeConfig {
112
+ export function normalizeSource(source: RemoteConfigBase): SourceConfig {
113
113
  if (typeof source !== "string") {
114
114
  let window = source.validWindow;
115
115
  if (!Array.isArray(window) || window.length !== 2 || !window.every(x => typeof x === "number")) {
@@ -144,7 +144,7 @@ export function normalizeSource(source: RemoteConfigBase): HostedConfig | Backbl
144
144
  }
145
145
 
146
146
  /** How far up from 0 the sources' routes reach without a gap (1 means the whole key space). */
147
- function getRouteCoverage(sources: (HostedConfig | BackblazeConfig)[]): number {
147
+ function getRouteCoverage(sources: SourceConfig[]): number {
148
148
  let routes = sources.map(x => x.route || FULL_ROUTE);
149
149
  sort(routes, x => x[0]);
150
150
  let covered = 0;
@@ -204,9 +204,25 @@ export function normalizeRemoteConfig(config: RemoteConfig | RemoteConfigBase):
204
204
  groupEnd = Math.max(groupEnd, source.validWindow[1]);
205
205
  }
206
206
  checkGroup();
207
+ // Two entries with the EXACT same URL and the SAME route may never be valid at the same time: on that server the route maps to exactly one on-disk folder and one store, so simultaneous duplicates would collide on it. A different port is a different server. Non-overlapping windows are fine; that is how transitions between windows work.
208
+ for (let i = 0; i < sources.length; i++) {
209
+ for (let j = i + 1; j < sources.length; j++) {
210
+ let a = sources[i];
211
+ let b = sources[j];
212
+ if (sourceRouteIdentity(a) !== sourceRouteIdentity(b)) continue;
213
+ if (a.validWindow[0] < b.validWindow[1] && b.validWindow[0] < a.validWindow[1]) {
214
+ throw new Error(`Two sources with the same URL have the same route with overlapping valid windows - on that server the route maps to one on-disk folder, so these would collide. Give same-route entries non-overlapping windows instead. Conflicting: ${JSON.stringify(a)} and ${JSON.stringify(b)}`);
215
+ }
216
+ }
217
+ }
207
218
  return result;
208
219
  }
209
220
 
221
+ /** The per-store identity of a source: its exact URL plus its route. Entries sharing this identity map to the same store/folder on the same server, so they may never overlap in time (see normalizeRemoteConfig). */
222
+ function sourceRouteIdentity(source: SourceConfig): string {
223
+ return `${source.url}|${JSON.stringify(source.route || FULL_ROUTE)}`;
224
+ }
225
+
210
226
  export function parseRoutingData(data: Buffer): RemoteConfig {
211
227
  let text = data.toString();
212
228
  let parsed: RemoteConfig;
@@ -0,0 +1,27 @@
1
+ import type { IStorage } from "../IStorage";
2
+ import type { AccessRequest, TrustRecord } from "./storageController";
3
+ export type StorageServerConfig = {
4
+ domain: string;
5
+ port: number;
6
+ rootDomain: string;
7
+ sshTarget: string;
8
+ serverCommand: string;
9
+ folder: string;
10
+ };
11
+ export declare function setStorageServerConfig(value: StorageServerConfig): void;
12
+ export declare function getStorageServerConfig(): StorageServerConfig;
13
+ export declare function getStorageServerConfigOptional(): StorageServerConfig | undefined;
14
+ export declare function setWritesRejectedReason(reason: string | undefined): void;
15
+ export declare function getWritesRejectedReason(): string | undefined;
16
+ export declare function assertWritesAllowed(): void;
17
+ export declare function getStorageFolder(): string;
18
+ export declare function getTrust(): Promise<IStorage<TrustRecord>>;
19
+ export declare function getRequests(): Promise<IStorage<AccessRequest[]>>;
20
+ export declare function setTrustedMachines(config: {
21
+ account: string;
22
+ machineIds: string[];
23
+ }): Promise<void>;
24
+ export declare function addExtraListenPort(port: number): void;
25
+ export declare function removeExtraListenPort(port: number): void;
26
+ /** Whether address:port is this server process. The ONE self test - findSelfIndexes, createApiArchives, and SourceWrapper all consult it, so "is this me" cannot disagree between the routing plan and connection building: a URL that is us on an extra listen port must never become a network client to ourselves, which is how infinite self-request loops form. */
27
+ export declare function isOwnAddress(address: string, port: number): boolean;
@@ -0,0 +1,109 @@
1
+ import path from "path";
2
+ import { getFileStorageNested2 } from "../FileFolderAPI";
3
+ import { TransactionStorage } from "../TransactionStorage";
4
+ import { JSONStorage } from "../JSONStorage";
5
+ import type { IStorage } from "../IStorage";
6
+ import type { AccessRequest, TrustRecord } from "./storageController";
7
+ import { getArg } from "./cliArgs";
8
+
9
+ // The storage server's process-level identity and system state: which server we are (config, extra ports), whether writes are allowed, and the trust/request stores.
10
+
11
+ export type StorageServerConfig = {
12
+ domain: string;
13
+ port: number;
14
+ rootDomain: string;
15
+ sshTarget: string;
16
+ serverCommand: string;
17
+ folder: string;
18
+ };
19
+
20
+ let config: StorageServerConfig | undefined;
21
+ export function setStorageServerConfig(value: StorageServerConfig): void {
22
+ config = value;
23
+ }
24
+ export function getStorageServerConfig(): StorageServerConfig {
25
+ if (!config) {
26
+ throw new Error(`Storage server is not initialized (this API only works on the storage server)`);
27
+ }
28
+ return config;
29
+ }
30
+ export function getStorageServerConfigOptional(): StorageServerConfig | undefined {
31
+ return config;
32
+ }
33
+
34
+ let writesRejectedReason: string | undefined;
35
+ export function setWritesRejectedReason(reason: string | undefined): void {
36
+ writesRejectedReason = reason;
37
+ }
38
+ export function getWritesRejectedReason(): string | undefined {
39
+ return writesRejectedReason;
40
+ }
41
+ export function assertWritesAllowed(): void {
42
+ if (writesRejectedReason) throw new Error(writesRejectedReason);
43
+ }
44
+
45
+ export function getStorageFolder(): string {
46
+ let config = getStorageServerConfigOptional();
47
+ if (config) return config.folder;
48
+ let folder = getArg("folder");
49
+ if (!folder) {
50
+ throw new Error(`Storage server is not initialized and there is no --folder arg, so the storage folder is unknown`);
51
+ }
52
+ return path.resolve(folder);
53
+ }
54
+
55
+ const systemStorages = new Map<string, Promise<IStorage<unknown>>>();
56
+ function getSystemStorage<T>(name: string): Promise<IStorage<T>> {
57
+ let storage = systemStorages.get(name);
58
+ if (!storage) {
59
+ storage = (async () => {
60
+ let root = await getFileStorageNested2(getStorageFolder());
61
+ let system = await root.folder.getStorage("system2");
62
+ let transactionName = "storage" + name[0].toUpperCase() + name.slice(1);
63
+ return new JSONStorage<unknown>(new TransactionStorage(await system.folder.getStorage(name), transactionName));
64
+ })();
65
+ systemStorages.set(name, storage);
66
+ }
67
+ return storage as Promise<IStorage<T>>;
68
+ }
69
+ export function getTrust(): Promise<IStorage<TrustRecord>> {
70
+ return getSystemStorage<TrustRecord>("trust");
71
+ }
72
+ export function getRequests(): Promise<IStorage<AccessRequest[]>> {
73
+ return getSystemStorage<AccessRequest[]>("requests");
74
+ }
75
+
76
+ export async function setTrustedMachines(config: { account: string; machineIds: string[] }): Promise<void> {
77
+ let trust = await getTrust();
78
+ let prefix = `${config.account}|`;
79
+ let desired = new Set(config.machineIds);
80
+ for (let key of await trust.getKeys()) {
81
+ if (!key.startsWith(prefix)) continue;
82
+ let machineId = key.slice(prefix.length);
83
+ if (desired.has(machineId)) {
84
+ desired.delete(machineId);
85
+ continue;
86
+ }
87
+ console.log(`Removing trust for machine ${machineId} on account ${config.account}`);
88
+ await trust.remove(key);
89
+ }
90
+ for (let machineId of desired) {
91
+ console.log(`Adding trust for machine ${machineId} on account ${config.account}`);
92
+ await trust.set(`${prefix}${machineId}`, { account: config.account, machineId, ip: "", time: Date.now() });
93
+ }
94
+ }
95
+
96
+ const extraListenPorts = new Set<number>();
97
+ export function addExtraListenPort(port: number): void {
98
+ extraListenPorts.add(port);
99
+ }
100
+ export function removeExtraListenPort(port: number): void {
101
+ extraListenPorts.delete(port);
102
+ }
103
+ /** Whether address:port is this server process. The ONE self test - findSelfIndexes, createApiArchives, and SourceWrapper all consult it, so "is this me" cannot disagree between the routing plan and connection building: a URL that is us on an extra listen port must never become a network client to ourselves, which is how infinite self-request loops form. */
104
+ export function isOwnAddress(address: string, port: number): boolean {
105
+ let config = getStorageServerConfigOptional();
106
+ if (!config) return false;
107
+ if (address !== config.domain) return false;
108
+ return port === config.port || extraListenPorts.has(port);
109
+ }
@@ -1,10 +1,10 @@
1
- import { IArchives, HostedConfig, BackblazeConfig, RemoteConfig } from "../IArchives";
1
+ import { IArchives, SourceConfig, RemoteConfig } from "../IArchives";
2
2
  import { ArchivesUrl } from "./ArchivesUrl";
3
3
  export declare const RETRY_START_DELAY: number;
4
4
  export declare const RETRY_MAX_DELAY: number;
5
5
  export declare const RETRY_GROWTH = 1.5;
6
6
  export declare class SourceWrapper {
7
- config: HostedConfig | BackblazeConfig;
7
+ config: SourceConfig;
8
8
  private background;
9
9
  api?: IArchives;
10
10
  url?: ArchivesUrl;
@@ -16,7 +16,7 @@ export declare class SourceWrapper {
16
16
  private constructor();
17
17
  /** Config updates routinely just move a source's valid window (the last window extends forever, then gets reduced when a new entry is appended). The wrapper survives that: only the window changes, keeping the connection, pings, and latency history. */
18
18
  updateValidWindow(validWindow: [number, number]): void;
19
- static create(config: HostedConfig | BackblazeConfig, options?: {
19
+ static create(config: SourceConfig, options?: {
20
20
  background?: boolean;
21
21
  readOnly?: boolean;
22
22
  }): Promise<SourceWrapper>;
@@ -1,12 +1,13 @@
1
1
  import { isNode, sort } from "socket-function/src/misc";
2
2
  import { delay } from "socket-function/src/batching";
3
3
  import { getSecret } from "../../misc/getSecret";
4
- import { IArchives, HostedConfig, BackblazeConfig, RemoteConfig } from "../IArchives";
4
+ import { IArchives, SourceConfig, RemoteConfig } from "../IArchives";
5
5
  import { ROUTING_FILE, getBucketBaseUrl, parseHostedUrl, parseBackblazeUrl, parseRoutingData } from "./remoteConfig";
6
6
  import { ArchivesRemote } from "./ArchivesRemote";
7
7
  import { ArchivesUrl } from "./ArchivesUrl";
8
8
  import { ArchivesBackblaze } from "../backblaze";
9
- import { getLocalArchives, isOwnAddress } from "./storageServerState";
9
+ import { isOwnAddress } from "./serverConfig";
10
+ import { getLocalArchives } from "./storageServerState";
10
11
 
11
12
  export const RETRY_START_DELAY = 2 * 1000;
12
13
  export const RETRY_MAX_DELAY = 5 * 60 * 1000;
@@ -40,7 +41,7 @@ export class SourceWrapper {
40
41
  private accessCache?: { hasAccess: boolean; time: number };
41
42
 
42
43
  private constructor(
43
- public config: HostedConfig | BackblazeConfig,
44
+ public config: SourceConfig,
44
45
  // false for short-lived probe instances, which must never leave a retry loop behind
45
46
  private background: boolean,
46
47
  ) { }
@@ -53,7 +54,7 @@ export class SourceWrapper {
53
54
  this.config.validWindow = validWindow;
54
55
  }
55
56
 
56
- public static async create(config: HostedConfig | BackblazeConfig, options?: { background?: boolean; readOnly?: boolean }): Promise<SourceWrapper> {
57
+ public static async create(config: SourceConfig, options?: { background?: boolean; readOnly?: boolean }): Promise<SourceWrapper> {
57
58
  let wrapper = new SourceWrapper(config, options?.background !== false);
58
59
  // A public bucket serves everything we can read over plain HTTPS GETs, so there is no reason to open an API connection (which costs a WebSocket, and an access grant we likely don't have) just to read it
59
60
  if (options?.readOnly && config.public !== false) {
@@ -77,10 +78,10 @@ export class SourceWrapper {
77
78
  let parsed = parseHostedUrl(config.url);
78
79
  if (isNode() && isOwnAddress(parsed.address, parsed.port)) {
79
80
  // A bucket hosted by our own process - use it directly instead of calling ourselves
80
- wrapper.api = getLocalArchives(parsed.account, parsed.bucketName);
81
+ wrapper.api = getLocalArchives(parsed.account, parsed.bucketName, config);
81
82
  return wrapper;
82
83
  }
83
- wrapper.remote = new ArchivesRemote({ url: config.url, waitForAccess: false });
84
+ wrapper.remote = new ArchivesRemote({ url: config.url, waitForAccess: false, sourceConfig: config });
84
85
  wrapper.api = wrapper.remote;
85
86
  if (config.public !== false) {
86
87
  wrapper.url = new ArchivesUrl(getBucketBaseUrl(config.url));