buckyos 0.7.119 → 0.7.121
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/cli/README.md +24 -3
- package/cli/core/app.ts +28 -0
- package/cli/deno.json +2 -1
- package/cli/dist/cli.mjs +777 -169
- package/cli/dist/cli.mjs.map +1 -1
- package/cli/modules/app.ts +213 -94
- package/cli/modules/provision.ts +690 -0
- package/cli/runtime/host.ts +25 -2
- package/cli/runtime/host_deno.ts +4 -0
- package/cli/runtime/host_node.ts +4 -0
- package/cli/version.ts +1 -1
- package/dist/browser.cjs +1 -1
- package/dist/browser.mjs +124 -124
- package/dist/index.cjs +1 -1
- package/dist/index.mjs +124 -124
- package/dist/{ndm_proxy-c00bc561.mjs → ndm_proxy-8f547002.mjs} +113 -216
- package/dist/ndm_proxy-8f547002.mjs.map +1 -0
- package/dist/ndm_proxy-93a8869b.js +13 -0
- package/dist/ndm_proxy-93a8869b.js.map +1 -0
- package/dist/ndn_types-44c69036.js +2 -0
- package/dist/ndn_types-44c69036.js.map +1 -0
- package/dist/{ndn_types-f6c08d20.mjs → ndn_types-d768245f.mjs} +149 -39
- package/dist/ndn_types-d768245f.mjs.map +1 -0
- package/dist/node.cjs +1 -1
- package/dist/node.mjs +124 -124
- package/dist/provision.cjs +6 -6
- package/dist/provision.cjs.map +1 -1
- package/dist/provision.d.ts +2 -0
- package/dist/provision.d.ts.map +1 -1
- package/dist/provision.mjs +1084 -35
- package/dist/provision.mjs.map +1 -1
- package/dist/provision_activate.d.ts +146 -0
- package/dist/provision_activate.d.ts.map +1 -0
- package/dist/{tus_client-573066b3.mjs → tus_client-b1c35e8c.mjs} +2 -2
- package/dist/{tus_client-573066b3.mjs.map → tus_client-b1c35e8c.mjs.map} +1 -1
- package/dist/{tus_client-83ad0f4f.js → tus_client-d0798623.js} +2 -2
- package/dist/{tus_client-83ad0f4f.js.map → tus_client-d0798623.js.map} +1 -1
- package/package.json +1 -1
- package/dist/ndm_proxy-68354a89.js +0 -13
- package/dist/ndm_proxy-68354a89.js.map +0 -1
- package/dist/ndm_proxy-c00bc561.mjs.map +0 -1
- package/dist/ndn_types-859bbd48.js +0 -2
- package/dist/ndn_types-859bbd48.js.map +0 -1
- package/dist/ndn_types-f6c08d20.mjs.map +0 -1
package/cli/modules/app.ts
CHANGED
|
@@ -36,7 +36,8 @@ type SourceKind = 'catalog' | 'pikg' | 'url'
|
|
|
36
36
|
export interface PikgSnapshot {
|
|
37
37
|
kind: 'pikg' | 'url'
|
|
38
38
|
display: string
|
|
39
|
-
|
|
39
|
+
path: string
|
|
40
|
+
localFileId?: string
|
|
40
41
|
digest: string
|
|
41
42
|
size: number
|
|
42
43
|
}
|
|
@@ -516,7 +517,7 @@ async function installApp(
|
|
|
516
517
|
...sourceRpcParams(source),
|
|
517
518
|
...planScopeAndOptions(inspection.plan),
|
|
518
519
|
options: installOptions(input),
|
|
519
|
-
plan: submittedPlan ??
|
|
520
|
+
plan: submittedPlan ?? inspection.plan,
|
|
520
521
|
approved_plan_fingerprint: expectString(inspection.plan, 'plan_fingerprint'),
|
|
521
522
|
idempotency_key: idempotencyKey(ctx),
|
|
522
523
|
}
|
|
@@ -593,6 +594,7 @@ async function upgradeApps(
|
|
|
593
594
|
...sourceRpcParams(source),
|
|
594
595
|
...planScopeAndOptions(inspection.plan),
|
|
595
596
|
options: installOptions(input),
|
|
597
|
+
plan: inspection.plan,
|
|
596
598
|
approved_plan_fingerprint: expectString(inspection.plan, 'plan_fingerprint'),
|
|
597
599
|
idempotency_key: idempotencyKey(ctx),
|
|
598
600
|
}),
|
|
@@ -675,37 +677,78 @@ async function prepareSource(
|
|
|
675
677
|
const classified = await classifySource(ctx, input)
|
|
676
678
|
if (classified.kind === 'catalog') return classified
|
|
677
679
|
|
|
678
|
-
const
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
'
|
|
689
|
-
|
|
690
|
-
EXIT_OPERATION,
|
|
691
|
-
false,
|
|
692
|
-
{
|
|
693
|
-
expected_digest: snapshot.digest,
|
|
694
|
-
staged_digest: metadata.pikg_digest,
|
|
695
|
-
expected_size: snapshot.size,
|
|
696
|
-
staged_size: metadata.size,
|
|
697
|
-
},
|
|
698
|
-
)
|
|
680
|
+
const host = getHost()
|
|
681
|
+
const localDirectory = localPikgDirectory(ctx)
|
|
682
|
+
let directory: string
|
|
683
|
+
let localFileId: string | undefined
|
|
684
|
+
if (localDirectory) {
|
|
685
|
+
try {
|
|
686
|
+
await host.mkdir(localDirectory, { recursive: true, mode: 0o700 })
|
|
687
|
+
directory = localDirectory
|
|
688
|
+
localFileId = crypto.randomUUID().replaceAll('-', '')
|
|
689
|
+
} catch (error) {
|
|
690
|
+
if (!isHostError(error, 'PermissionDenied') && !isHostError(error, 'NotFound')) throw error
|
|
691
|
+
}
|
|
699
692
|
}
|
|
700
|
-
if (
|
|
701
|
-
|
|
693
|
+
if (!localFileId) {
|
|
694
|
+
await host.mkdir(ctx.configStore.root, { recursive: true, mode: 0o700 })
|
|
695
|
+
directory = await host.makeTempDir({ dir: ctx.configStore.root, prefix: '.pikg-' })
|
|
702
696
|
}
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
697
|
+
let path = host.path.join(directory!, `${localFileId ?? 'source'}.pikg`)
|
|
698
|
+
const snapshotAt = (destination: string) =>
|
|
699
|
+
classified.kind === 'url'
|
|
700
|
+
? downloadPikg(classified.url, destination, dependencies.download, ctx.signal)
|
|
701
|
+
: readPikg(classified.path, destination, ctx.signal)
|
|
702
|
+
try {
|
|
703
|
+
let snapshot: PikgSnapshot
|
|
704
|
+
try {
|
|
705
|
+
snapshot = await snapshotAt(path)
|
|
706
|
+
} catch (error) {
|
|
707
|
+
if (!localFileId || !isHostError(error, 'PermissionDenied')) throw error
|
|
708
|
+
await host.remove(path).catch(() => {})
|
|
709
|
+
localFileId = undefined
|
|
710
|
+
await host.mkdir(ctx.configStore.root, { recursive: true, mode: 0o700 })
|
|
711
|
+
directory = await host.makeTempDir({ dir: ctx.configStore.root, prefix: '.pikg-' })
|
|
712
|
+
path = host.path.join(directory, 'source.pikg')
|
|
713
|
+
snapshot = await snapshotAt(path)
|
|
714
|
+
}
|
|
715
|
+
snapshot.localFileId = localFileId
|
|
716
|
+
const stage = dependencies.stagePikg ?? stagePikg
|
|
717
|
+
const metadata = await stage(ctx, snapshot, purpose)
|
|
718
|
+
if (metadata.schema_version !== PLAN_SCHEMA_VERSION) {
|
|
719
|
+
throw new ToolError('UNSUPPORTED_SCHEMA_VERSION', 'staging returned a non-v4 schema')
|
|
720
|
+
}
|
|
721
|
+
if (metadata.pikg_digest !== snapshot.digest || metadata.size !== snapshot.size) {
|
|
722
|
+
throw new ToolError(
|
|
723
|
+
'PIKG_DIGEST_MISMATCH',
|
|
724
|
+
'the staged PIKG does not match the client byte snapshot',
|
|
725
|
+
EXIT_OPERATION,
|
|
726
|
+
false,
|
|
727
|
+
{
|
|
728
|
+
expected_digest: snapshot.digest,
|
|
729
|
+
staged_digest: metadata.pikg_digest,
|
|
730
|
+
expected_size: snapshot.size,
|
|
731
|
+
staged_size: metadata.size,
|
|
732
|
+
},
|
|
733
|
+
)
|
|
734
|
+
}
|
|
735
|
+
if (metadata.purpose !== purpose) {
|
|
736
|
+
throw new ToolError(
|
|
737
|
+
'INVALID_SERVICE_RESPONSE',
|
|
738
|
+
'staging purpose does not match the request',
|
|
739
|
+
9,
|
|
740
|
+
)
|
|
741
|
+
}
|
|
742
|
+
return {
|
|
743
|
+
kind: snapshot.kind,
|
|
744
|
+
display: snapshot.display,
|
|
745
|
+
snapshot,
|
|
746
|
+
staging: metadata,
|
|
747
|
+
serviceSource: { kind: 'local_pikg', staging_handle: metadata.handle },
|
|
748
|
+
}
|
|
749
|
+
} finally {
|
|
750
|
+
await host.remove(localFileId ? path : directory!, { recursive: !localFileId })
|
|
751
|
+
.catch(() => {})
|
|
709
752
|
}
|
|
710
753
|
}
|
|
711
754
|
|
|
@@ -760,37 +803,82 @@ async function classifySource(
|
|
|
760
803
|
}
|
|
761
804
|
}
|
|
762
805
|
|
|
763
|
-
|
|
806
|
+
function localPikgDirectory(ctx: CommandContext): string | undefined {
|
|
807
|
+
const root = getHost().policy.buckyosRoot
|
|
808
|
+
const hostname = new URL(ctx.connection.endpoint).hostname
|
|
809
|
+
if (!root || !['localhost', '127.0.0.1', '[::1]'].includes(hostname)) return undefined
|
|
810
|
+
return getHost().path.join(root, 'cache', 'control_panel', 'pikg_staging', 'incoming')
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
async function* fileChunks(file: HostFile, size: number): AsyncGenerator<Uint8Array> {
|
|
814
|
+
const buffer = new Uint8Array(size)
|
|
815
|
+
while (true) {
|
|
816
|
+
let used = 0
|
|
817
|
+
while (used < buffer.length) {
|
|
818
|
+
const read = await file.read(buffer.subarray(used))
|
|
819
|
+
if (read === null) break
|
|
820
|
+
if (read === 0) throw new UsageError('PIKG_READ_FAILED', 'PIKG read made no progress')
|
|
821
|
+
used += read
|
|
822
|
+
}
|
|
823
|
+
if (used === 0) break
|
|
824
|
+
yield buffer.subarray(0, used)
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
async function writePikgSnapshot(
|
|
829
|
+
path: string,
|
|
830
|
+
chunks: AsyncIterable<Uint8Array> | Iterable<Uint8Array>,
|
|
831
|
+
signal: AbortSignal,
|
|
832
|
+
): Promise<{ path: string; digest: string; size: number }> {
|
|
833
|
+
const output = await getHost().open(path, { write: true, createNew: true, mode: 0o600 })
|
|
834
|
+
const hash = getHost().createHash('sha256')
|
|
835
|
+
const magic = new Uint8Array(ZIP_LOCAL_MAGIC.length)
|
|
836
|
+
let size = 0
|
|
837
|
+
try {
|
|
838
|
+
for await (const chunk of chunks) {
|
|
839
|
+
signal.throwIfAborted()
|
|
840
|
+
if (size < magic.length) magic.set(chunk.subarray(0, magic.length - size), size)
|
|
841
|
+
hash.update(chunk)
|
|
842
|
+
size += chunk.length
|
|
843
|
+
if (!Number.isSafeInteger(size)) throw new UsageError('PIKG_TOO_LARGE', 'PIKG is too large')
|
|
844
|
+
let offset = 0
|
|
845
|
+
while (offset < chunk.length) {
|
|
846
|
+
const written = await output.write(chunk.subarray(offset))
|
|
847
|
+
if (written === 0) throw new UsageError('PIKG_WRITE_FAILED', 'PIKG write made no progress')
|
|
848
|
+
offset += written
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
validatePikgSnapshot(magic.subarray(0, Math.min(size, magic.length)))
|
|
852
|
+
await output.sync()
|
|
853
|
+
return { path, size, digest: hash.digestHex() }
|
|
854
|
+
} finally {
|
|
855
|
+
await output.close()
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
async function readPikg(
|
|
860
|
+
source: string,
|
|
861
|
+
destination: string,
|
|
862
|
+
signal: AbortSignal,
|
|
863
|
+
): Promise<PikgSnapshot> {
|
|
764
864
|
let file: HostFile
|
|
765
865
|
try {
|
|
766
|
-
file = await getHost().open(
|
|
866
|
+
file = await getHost().open(source, { read: true })
|
|
767
867
|
} catch (error) {
|
|
768
868
|
throw new UsageError('PIKG_READ_FAILED', `failed to open PIKG: ${errorMessage(error)}`)
|
|
769
869
|
}
|
|
770
870
|
try {
|
|
771
|
-
const
|
|
772
|
-
if (!
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
if (read === null) break
|
|
781
|
-
offset += read
|
|
782
|
-
}
|
|
783
|
-
if (offset !== bytes.length) {
|
|
784
|
-
throw new UsageError('PIKG_READ_FAILED', 'PIKG changed size while being read')
|
|
785
|
-
}
|
|
786
|
-
validatePikgSnapshot(bytes)
|
|
787
|
-
return {
|
|
788
|
-
kind,
|
|
789
|
-
display: path,
|
|
790
|
-
bytes,
|
|
791
|
-
digest: await sha256Id(bytes),
|
|
792
|
-
size: bytes.length,
|
|
871
|
+
const before = await file.stat()
|
|
872
|
+
if (!before.isFile) throw new UsageError('INVALID_PIKG_SOURCE', 'PIKG source is not a file')
|
|
873
|
+
const snapshot = await writePikgSnapshot(destination, fileChunks(file, 256 * 1024), signal)
|
|
874
|
+
const after = await file.stat()
|
|
875
|
+
if (
|
|
876
|
+
snapshot.size !== before.size || before.size !== after.size ||
|
|
877
|
+
before.mtime?.getTime() !== after.mtime?.getTime()
|
|
878
|
+
) {
|
|
879
|
+
throw new UsageError('PIKG_READ_FAILED', 'PIKG changed while being read')
|
|
793
880
|
}
|
|
881
|
+
return { kind: 'pikg', display: source, ...snapshot }
|
|
794
882
|
} finally {
|
|
795
883
|
await file.close()
|
|
796
884
|
}
|
|
@@ -798,34 +886,33 @@ async function readPikg(path: string, kind: 'pikg'): Promise<PikgSnapshot> {
|
|
|
798
886
|
|
|
799
887
|
async function downloadPikg(
|
|
800
888
|
url: URL,
|
|
889
|
+
destination: string,
|
|
801
890
|
downloader: AppModuleDependencies['download'],
|
|
802
891
|
signal: AbortSignal,
|
|
803
892
|
): Promise<PikgSnapshot> {
|
|
804
|
-
let
|
|
893
|
+
let chunks: Uint8Array[] | ReadableStream<Uint8Array> | undefined
|
|
805
894
|
try {
|
|
806
|
-
|
|
895
|
+
chunks = downloader ? [await downloader(url, signal)] : await defaultDownload(url, signal)
|
|
896
|
+
const snapshot = await writePikgSnapshot(destination, chunks, signal)
|
|
897
|
+
return { kind: 'url', display: safeUrl(url), ...snapshot }
|
|
807
898
|
} catch (error) {
|
|
808
|
-
if (error instanceof ToolError) throw error
|
|
899
|
+
if (error instanceof ToolError || isHostError(error, 'PermissionDenied')) throw error
|
|
900
|
+
signal.throwIfAborted()
|
|
809
901
|
throw new ToolError(
|
|
810
902
|
'PIKG_DOWNLOAD_FAILED',
|
|
811
903
|
`failed to download PIKG: ${errorMessage(error)}`,
|
|
812
904
|
5,
|
|
813
905
|
true,
|
|
814
906
|
)
|
|
815
|
-
}
|
|
816
|
-
|
|
817
|
-
return {
|
|
818
|
-
kind: 'url',
|
|
819
|
-
display: safeUrl(url),
|
|
820
|
-
bytes,
|
|
821
|
-
digest: await sha256Id(bytes),
|
|
822
|
-
size: bytes.length,
|
|
907
|
+
} finally {
|
|
908
|
+
if (chunks instanceof ReadableStream && !chunks.locked) await chunks.cancel().catch(() => {})
|
|
823
909
|
}
|
|
824
910
|
}
|
|
825
911
|
|
|
826
|
-
async function defaultDownload(url: URL, signal: AbortSignal): Promise<Uint8Array
|
|
912
|
+
async function defaultDownload(url: URL, signal: AbortSignal): Promise<ReadableStream<Uint8Array>> {
|
|
827
913
|
const response = await fetch(url, { method: 'GET', redirect: 'follow', signal })
|
|
828
|
-
if (!response.ok) {
|
|
914
|
+
if (!response.ok || !response.body) {
|
|
915
|
+
await response.body?.cancel()
|
|
829
916
|
throw new ToolError(
|
|
830
917
|
'PIKG_DOWNLOAD_FAILED',
|
|
831
918
|
`PIKG download returned HTTP ${response.status}`,
|
|
@@ -833,7 +920,7 @@ async function defaultDownload(url: URL, signal: AbortSignal): Promise<Uint8Arra
|
|
|
833
920
|
response.status >= 500,
|
|
834
921
|
)
|
|
835
922
|
}
|
|
836
|
-
return
|
|
923
|
+
return response.body
|
|
837
924
|
}
|
|
838
925
|
|
|
839
926
|
async function stagePikg(
|
|
@@ -841,42 +928,80 @@ async function stagePikg(
|
|
|
841
928
|
snapshot: PikgSnapshot,
|
|
842
929
|
purpose: PikgPurpose,
|
|
843
930
|
): Promise<PikgStagingMetadata> {
|
|
931
|
+
const identity = { pikg_digest: snapshot.digest, size: snapshot.size, purpose }
|
|
932
|
+
if (snapshot.localFileId) {
|
|
933
|
+
try {
|
|
934
|
+
const value = expectObject(
|
|
935
|
+
await callControl(ctx, 'apps.staging.finalize', {
|
|
936
|
+
...identity,
|
|
937
|
+
local_file_id: snapshot.localFileId,
|
|
938
|
+
}),
|
|
939
|
+
'apps.staging.finalize response',
|
|
940
|
+
)
|
|
941
|
+
return parseStagingMetadata(value)
|
|
942
|
+
} catch (error) {
|
|
943
|
+
if (!errorMessage(error).includes('LOCAL_PIKG_UNAVAILABLE')) throw error
|
|
944
|
+
}
|
|
945
|
+
}
|
|
844
946
|
const session = ctx.session
|
|
845
947
|
if (!session) throw new ToolError('AUTH_REQUIRED', 'authenticated session is required', 3)
|
|
846
948
|
const token = (await session.ensureValid()).token
|
|
847
|
-
const hash = ndn.sha256Bytes(snapshot.bytes)
|
|
848
|
-
const chunkId = ndn.ChunkId.fromMix256Result(snapshot.size, hash).toString()
|
|
849
949
|
const proxy = ndm_proxy.createNdmProxyClient({
|
|
850
950
|
endpoint: gatewayOrigin(ctx.connection.endpoint),
|
|
851
951
|
sessionToken: token,
|
|
852
952
|
fetcher: (request: RequestInfo | URL, init?: RequestInit) =>
|
|
853
953
|
fetch(normalizeNdmRequestUrl(request), { ...init, signal: ctx.signal }),
|
|
854
954
|
})
|
|
855
|
-
let
|
|
955
|
+
let fileObjectId: string
|
|
856
956
|
try {
|
|
857
|
-
await
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
}
|
|
868
|
-
|
|
869
|
-
|
|
957
|
+
const file = await getHost().open(snapshot.path, { read: true })
|
|
958
|
+
const chunks = new ndn.SimpleChunkList()
|
|
959
|
+
const hash = getHost().createHash('sha256')
|
|
960
|
+
try {
|
|
961
|
+
for await (const bytes of fileChunks(file, 32 * 1024 * 1024)) {
|
|
962
|
+
ctx.signal.throwIfAborted()
|
|
963
|
+
hash.update(bytes)
|
|
964
|
+
const chunk = ndn.ChunkId.fromMix256Result(bytes.length, ndn.sha256Bytes(bytes))
|
|
965
|
+
await proxy.putChunk(chunk.toString(), bytes)
|
|
966
|
+
chunks.appendChunk(chunk)
|
|
967
|
+
}
|
|
968
|
+
} finally {
|
|
969
|
+
await file.close()
|
|
970
|
+
}
|
|
971
|
+
if (chunks.total_size !== snapshot.size || hash.digestHex() !== snapshot.digest) {
|
|
972
|
+
throw new ToolError('PIKG_DIGEST_MISMATCH', 'PIKG snapshot changed during upload')
|
|
973
|
+
}
|
|
974
|
+
let content = chunks.body[0].toString()
|
|
975
|
+
if (chunks.body.length > 1) {
|
|
976
|
+
const [id, body] = chunks.genObjId()
|
|
977
|
+
await proxy.putObject({ obj_id: id.toString(), obj_data: body })
|
|
978
|
+
content = id.toString()
|
|
979
|
+
}
|
|
980
|
+
const fileObject = new ndn.FileObject(`${snapshot.digest}.pikg`, snapshot.size, content)
|
|
981
|
+
const [id, body] = fileObject.genObjId()
|
|
982
|
+
await proxy.putObject({ obj_id: id.toString(), obj_data: body })
|
|
983
|
+
fileObjectId = id.toString()
|
|
870
984
|
} catch (error) {
|
|
871
|
-
if (
|
|
985
|
+
if (error instanceof ToolError) throw error
|
|
986
|
+
ctx.signal.throwIfAborted()
|
|
872
987
|
throw new ToolError(
|
|
873
988
|
'PIKG_UPLOAD_FAILED',
|
|
874
|
-
`failed to upload PIKG
|
|
989
|
+
`failed to upload PIKG: ${errorMessage(error)}`,
|
|
875
990
|
5,
|
|
876
991
|
true,
|
|
877
|
-
{ finalize_error: errorMessage(error) },
|
|
878
992
|
)
|
|
879
993
|
}
|
|
994
|
+
const value = expectObject(
|
|
995
|
+
await callControl(ctx, 'apps.staging.finalize', {
|
|
996
|
+
...identity,
|
|
997
|
+
source_obj_id: fileObjectId,
|
|
998
|
+
}),
|
|
999
|
+
'apps.staging.finalize response',
|
|
1000
|
+
)
|
|
1001
|
+
return parseStagingMetadata(value)
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
function parseStagingMetadata(value: Record<string, unknown>): PikgStagingMetadata {
|
|
880
1005
|
return {
|
|
881
1006
|
schema_version: expectNumber(value, 'schema_version'),
|
|
882
1007
|
handle: expectString(value, 'handle'),
|
|
@@ -1445,12 +1570,6 @@ function validatePikgSnapshot(bytes: Uint8Array): void {
|
|
|
1445
1570
|
}
|
|
1446
1571
|
}
|
|
1447
1572
|
|
|
1448
|
-
async function sha256Id(bytes: Uint8Array): Promise<string> {
|
|
1449
|
-
const digestInput = Uint8Array.from(bytes)
|
|
1450
|
-
const digest = new Uint8Array(await crypto.subtle.digest('SHA-256', digestInput.buffer))
|
|
1451
|
-
return [...digest].map((byte) => byte.toString(16).padStart(2, '0')).join('')
|
|
1452
|
-
}
|
|
1453
|
-
|
|
1454
1573
|
function gatewayOrigin(endpoint: string): string {
|
|
1455
1574
|
const url = new URL(endpoint)
|
|
1456
1575
|
const marker = url.pathname.indexOf('/kapi')
|