From d21f1e8ecc42a38e3aeed4e8938b3e4fc51ba44d Mon Sep 17 00:00:00 2001 From: Hunter Miller Date: Thu, 4 May 2023 09:53:06 -0500 Subject: [PATCH 1/3] settings,storage: add new agents for migration --- desk/app/settings.hoon | 225 +++++++++++++++++++++++++++++++++++ desk/app/storage.hoon | 168 ++++++++++++++++++++++++++ desk/desk.bill | 2 + desk/lib/storage-json.hoon | 53 +++++++++ desk/mar/storage/action.hoon | 13 ++ desk/mar/storage/update.hoon | 14 +++ desk/sur/storage-0.hoon | 27 +++++ desk/sur/storage.hoon | 34 ++++++ 8 files changed, 536 insertions(+) create mode 100644 desk/app/settings.hoon create mode 100644 desk/app/storage.hoon create mode 100644 desk/lib/storage-json.hoon create mode 100644 desk/mar/storage/action.hoon create mode 100644 desk/mar/storage/update.hoon create mode 100644 desk/sur/storage-0.hoon create mode 100644 desk/sur/storage.hoon diff --git a/desk/app/settings.hoon b/desk/app/settings.hoon new file mode 100644 index 0000000..b4862d2 --- /dev/null +++ b/desk/app/settings.hoon @@ -0,0 +1,225 @@ +/- *settings +/+ verb, dbug, default-agent, agentio +|% ++$ card card:agent:gall ++$ versioned-state + $% state-0 + state-1 + state-2 + == ++$ state-0 [%0 settings=settings-0] ++$ state-1 [%1 settings=settings-1] ++$ state-2 [%2 =settings] +-- +=| state-2 +=* state - +:: +%- agent:dbug +%+ verb | +^- agent:gall +=< + |_ =bowl:gall + +* this . + do ~(. +> bowl) + def ~(. (default-agent this %|) bowl) + io ~(. agentio bowl) + :: + ++ on-init + :: XX: deprecated; migration code + ^- (quip card _this) + :_ this + :~ :* %pass + /migrate + %agent + [our dap]:bowl + %poke + noun+!>(%migrate) + == == + :: + ++ on-save !>(state) + :: + ++ on-load + |= =old=vase + ^- (quip card _this) + =/ old !<(versioned-state old-vase) + |- + ?- -.old + %0 $(old [%1 +.old]) + %1 $(old [%2 (~(put by *^settings) %landscape settings.old)]) + %2 `this(state old) + == + :: + ++ on-poke + |= [mar=mark vas=vase] + ^- (quip card _this) + ?> (team:title our.bowl src.bowl) + =^ cards state + ?+ mar (on-poke:def mar vas) + %settings-event + =/ evt=event !<(event vas) + ?- -.evt + %put-bucket (put-bucket:do [desk key bucket]:evt) + %del-bucket (del-bucket:do [desk key]:evt) + %put-entry (put-entry:do [desk buc key val]:evt) + %del-entry (del-entry:do [desk buc key]:evt) + == + :: + :: XX: deprecated; migration code + %noun + ?> ?=(%migrate !<(%migrate vas)) + =/ bas /(scot %p our.bowl)/settings-store/(scot %da now.bowl) + :- ~ + ?. .^(? %gu bas) + state + =/ ful .^(data %gx (weld bas /all/noun)) + ?+ -.ful (on-poke:def mar vas) + %all state(settings +.ful) + == + == + [cards this] + :: + ++ on-watch + |= pax=path + ^- (quip card _this) + ?> (team:title our.bowl src.bowl) + ?+ pax (on-watch:def pax) + [%all ~] + [~ this] + :: + [%desk @ ~] + =* desk i.t.pax + [~ this] + :: + [%bucket @ @ ~] + =* desk i.t.pax + =* bucket-key i.t.t.pax + ?> (~(has bi settings) desk bucket-key) + [~ this] + :: + [%entry @ @ @ ~] + =* desk i.t.pax + =* bucket-key i.t.t.pax + =* entry-key i.t.t.t.pax + =/ bucket (~(got bi settings) desk bucket-key) + ?> (~(has by bucket) entry-key) + [~ this] + == + :: + ++ on-peek + |= pax=path + ^- (unit (unit cage)) + ?+ pax (on-peek:def pax) + [%x %all ~] + ``settings-data+!>(`data`all+settings) + :: + [%x %desk @ ~] + =* desk i.t.t.pax + =/ desk-settings (~(gut by settings) desk ~) + ``settings-data+!>(desk+desk-settings) + :: + [%x %bucket @ @ ~] + =* desk i.t.t.pax + =* buc i.t.t.t.pax + =/ bucket=(unit bucket) (~(get bi settings) desk buc) + ?~ bucket [~ ~] + ``settings-data+!>(`data`bucket+u.bucket) + :: + [%x %entry @ @ @ ~] + =* desk i.t.t.pax + =* buc i.t.t.t.pax + =* key i.t.t.t.t.pax + =/ =bucket (~(gut bi settings) desk buc *bucket) + =/ entry=(unit val) (~(get by bucket) key) + ?~ entry [~ ~] + ``settings-data+!>(`data`entry+u.entry) + :: + [%x %has-bucket @ @ ~] + =/ desk i.t.t.pax + =/ buc i.t.t.t.pax + =/ has-bucket=? (~(has bi settings) desk buc) + ``noun+!>(`?`has-bucket) + :: + [%x %has-entry @ @ @ ~] + =* desk i.t.t.pax + =* buc i.t.t.t.pax + =* key i.t.t.t.t.pax + =/ =bucket (~(gut bi settings) desk buc *bucket) + =/ has-entry=? (~(has by bucket) key) + ``noun+!>(`?`has-entry) + == + :: + ++ on-agent on-agent:def + ++ on-leave on-leave:def + ++ on-arvo on-arvo:def + ++ on-fail on-fail:def + -- +:: +|_ bol=bowl:gall +:: +:: +put-bucket: put a bucket in the top level settings map, overwriting if it +:: already exists +:: +++ put-bucket + |= [=desk =key =bucket] + ^- (quip card _state) + =/ pas=(list path) + :~ /all + /desk/[desk] + /bucket/[desk]/[key] + == + :- [(give-event pas %put-bucket desk key bucket)]~ + state(settings (~(put bi settings) desk key bucket)) +:: +:: +del-bucket: delete a bucket from the top level settings map +:: +++ del-bucket + |= [=desk =key] + ^- (quip card _state) + =/ pas=(list path) + :~ /all + /desk/[desk] + /bucket/[key] + == + :- [(give-event pas %del-bucket desk key)]~ + state(settings (~(del bi settings) desk key)) +:: +:: +put-entry: put an entry in a bucket, overwriting if it already exists +:: if bucket does not yet exist, create it +:: +++ put-entry + |= [=desk buc=key =key =val] + ^- (quip card _state) + =/ pas=(list path) + :~ /all + /desk/[desk] + /bucket/[desk]/[buc] + /entry/[desk]/[buc]/[key] + == + =/ =bucket (~(put by (~(gut bi settings) desk buc *bucket)) key val) + :- [(give-event pas %put-entry desk buc key val)]~ + state(settings (~(put bi settings) desk buc bucket)) +:: +:: +del-entry: delete an entry from a bucket, fail quietly if bucket does not +:: exist +:: +++ del-entry + |= [=desk buc=key =key] + ^- (quip card _state) + =/ pas=(list path) + :~ /all + /desk/[desk] + /bucket/[desk]/[buc] + /entry/[desk]/[buc]/[key] + == + =/ bucket=(unit bucket) (~(get bi settings) desk buc) + ?~ bucket + [~ state] + =. u.bucket (~(del by u.bucket) key) + :- [(give-event pas %del-entry desk buc key)]~ + state(settings (~(put bi settings) desk buc u.bucket)) +:: +++ give-event + |= [pas=(list path) evt=event] + ^- card + [%give %fact pas %settings-event !>(evt)] +-- diff --git a/desk/app/storage.hoon b/desk/app/storage.hoon new file mode 100644 index 0000000..7e135cb --- /dev/null +++ b/desk/app/storage.hoon @@ -0,0 +1,168 @@ +:: storage: +:: +:: stores s3 keys for uploading and sharing images and objects +:: +/- *storage +/+ storage-json, default-agent, verb, dbug +~% %s3-top ..part ~ +|% ++$ card card:agent:gall ++$ versioned-state + $% state-zero + state-one + == +:: ++$ state-zero [%0 =credentials:zero:past =configuration:zero:past] ++$ state-one [%1 =credentials =configuration] +-- +:: +=| state-one +=* state - +:: +%- agent:dbug +%+ verb | +^- agent:gall +~% %s3-agent-core ..card ~ +|_ =bowl:gall ++* this . + def ~(. (default-agent this %|) bowl) +:: +++ on-init + :: XX: deprecated; migration code + ^- (quip card _this) + :_ this + :~ :* %pass + /migrate + %agent + [our dap]:bowl + %poke + noun+!>(%migrate) + == == +++ on-save !>(state) +++ on-load + |= =vase + =/ old !<(versioned-state vase) + |^ + ?- -.old + %1 `this(state old) + %0 `this(state (state-0-to-1 old)) + == + ++ state-0-to-1 + |= zer=state-zero + ^- state-one + :* %1 + credentials.zer + (configuration-0-to-1 configuration.zer) + == + ++ configuration-0-to-1 + |= conf=configuration:zero:past + ^- ^configuration + :* buckets.conf + current-bucket.conf + '' + == + -- +:: +++ on-poke + ~/ %s3-poke + |= [=mark =vase] + ^- (quip card _this) + |^ + ?> (team:title our.bowl src.bowl) + =^ cards state + ?+ mark (on-poke:def mark vase) + %storage-action + (poke-action !<(action vase)) + :: + :: XX: deprecated; migration code + %noun + ?> ?=(%migrate !<(%migrate vase)) + =/ bas /(scot %p our.bowl)/s3-store/(scot %da now.bowl) + :- ~ + ?. .^(? %gu bas) + state + =: + credentials + =/ ful .^(update %gx (weld bas /credentials/noun)) + ?+ -.ful (on-poke:def mark vase) + %credentials +.ful + == + :: + configuration + =/ ful .^(update %gx (weld bas /configuration/noun)) + ?+ -.ful (on-poke:def mark vase) + %configuration +.ful + == == + state + == + [cards this] + :: + ++ poke-action + |= act=action + ^- (quip card _state) + :- [%give %fact [/all]~ %storage-update !>(act)]~ + ?- -.act + %set-endpoint + state(endpoint.credentials endpoint.act) + :: + %set-access-key-id + state(access-key-id.credentials access-key-id.act) + :: + %set-secret-access-key + state(secret-access-key.credentials secret-access-key.act) + :: + %set-region + state(region.configuration region.act) + :: + %set-current-bucket + %_ state + current-bucket.configuration bucket.act + buckets.configuration (~(put in buckets.configuration) bucket.act) + == + :: + %add-bucket + state(buckets.configuration (~(put in buckets.configuration) bucket.act)) + :: + %remove-bucket + state(buckets.configuration (~(del in buckets.configuration) bucket.act)) + == + -- +:: +++ on-watch + ~/ %s3-watch + |= =path + ^- (quip card _this) + |^ + ?> (team:title our.bowl src.bowl) + =/ cards=(list card) + ?+ path (on-watch:def path) + [%all ~] + :~ (give %storage-update !>([%credentials credentials])) + (give %storage-update !>([%configuration configuration])) + == + == + [cards this] + :: + ++ give + |= =cage + ^- card + [%give %fact ~ cage] + -- +:: +++ on-leave on-leave:def +++ on-peek + ~/ %s3-peek + |= =path + ^- (unit (unit cage)) + ?. (team:title our.bowl src.bowl) ~ + ?+ path [~ ~] + [%x %credentials ~] + [~ ~ %storage-update !>(`update`[%credentials credentials])] + :: + [%x %configuration ~] + [~ ~ %storage-update !>(`update`[%configuration configuration])] + == +++ on-agent on-agent:def +++ on-arvo on-arvo:def +++ on-fail on-fail:def +-- diff --git a/desk/desk.bill b/desk/desk.bill index 23fa7cf..da1a2a8 100644 --- a/desk/desk.bill +++ b/desk/desk.bill @@ -2,7 +2,9 @@ %treaty %hark-store %hark-system-hook + %settings %settings-store + %storage %reel %bait == diff --git a/desk/lib/storage-json.hoon b/desk/lib/storage-json.hoon new file mode 100644 index 0000000..3afaa6b --- /dev/null +++ b/desk/lib/storage-json.hoon @@ -0,0 +1,53 @@ +/- *storage +|% +++ json-to-action + |= =json + ^- action + =, format + |^ (parse-json json) + ++ parse-json + %- of:dejs + :~ [%set-endpoint so:dejs] + [%set-access-key-id so:dejs] + [%set-secret-access-key so:dejs] + [%set-region so:dejs] + [%add-bucket so:dejs] + [%remove-bucket so:dejs] + [%set-current-bucket so:dejs] + == + -- +:: +++ update-to-json + |= upd=update + ^- json + =, format + %+ frond:enjs %s3-update + %- pairs:enjs + :~ ?- -.upd + %set-current-bucket [%'setCurrentBucket' s+bucket.upd] + %add-bucket [%'addBucket' s+bucket.upd] + %set-region [%'setRegion' s+region.upd] + %remove-bucket [%'removeBucket' s+bucket.upd] + %set-endpoint [%'setEndpoint' s+endpoint.upd] + %set-access-key-id [%'setAccessKeyId' s+access-key-id.upd] + %set-secret-access-key + [%'setSecretAccessKey' s+secret-access-key.upd] + :: + %credentials + :- %credentials + %- pairs:enjs + :~ [%endpoint s+endpoint.credentials.upd] + [%'accessKeyId' s+access-key-id.credentials.upd] + [%'secretAccessKey' s+secret-access-key.credentials.upd] + == + :: + %configuration + :- %configuration + %- pairs:enjs + :~ [%buckets a+(turn ~(tap in buckets.configuration.upd) |=(a=@t s+a))] + [%'currentBucket' s+current-bucket.configuration.upd] + [%'region' s+region.configuration.upd] + == + == + == +-- diff --git a/desk/mar/storage/action.hoon b/desk/mar/storage/action.hoon new file mode 100644 index 0000000..5304998 --- /dev/null +++ b/desk/mar/storage/action.hoon @@ -0,0 +1,13 @@ +/+ *storage-json +|_ act=action +++ grad %noun +++ grow + |% + ++ noun act + -- +++ grab + |% + ++ noun action + ++ json json-to-action + -- +-- diff --git a/desk/mar/storage/update.hoon b/desk/mar/storage/update.hoon new file mode 100644 index 0000000..8349903 --- /dev/null +++ b/desk/mar/storage/update.hoon @@ -0,0 +1,14 @@ +/+ *storage-json +|_ upd=update +++ grad %noun +++ grow + |% + ++ noun upd + ++ json (update-to-json upd) + -- +:: +++ grab + |% + ++ noun update + -- +-- diff --git a/desk/sur/storage-0.hoon b/desk/sur/storage-0.hoon new file mode 100644 index 0000000..4f0ca04 --- /dev/null +++ b/desk/sur/storage-0.hoon @@ -0,0 +1,27 @@ +|% ++$ credentials + $: endpoint=@t + access-key-id=@t + secret-access-key=@t + == +:: ++$ configuration + $: buckets=(set @t) + current-bucket=@t + == +:: ++$ action + $% [%set-endpoint endpoint=@t] + [%set-access-key-id access-key-id=@t] + [%set-secret-access-key secret-access-key=@t] + [%add-bucket bucket=@t] + [%remove-bucket bucket=@t] + [%set-current-bucket bucket=@t] + == +:: ++$ update + $% [%credentials =credentials] + [%configuration =configuration] + action + == +-- diff --git a/desk/sur/storage.hoon b/desk/sur/storage.hoon new file mode 100644 index 0000000..8afea6a --- /dev/null +++ b/desk/sur/storage.hoon @@ -0,0 +1,34 @@ +/- zer=storage-0 +|% +++ past + |% + ++ zero zer + -- ++$ credentials + $: endpoint=@t + access-key-id=@t + secret-access-key=@t + == +:: ++$ configuration + $: buckets=(set @t) + current-bucket=@t + region=@t + == +:: ++$ action + $% [%set-endpoint endpoint=@t] + [%set-access-key-id access-key-id=@t] + [%set-secret-access-key secret-access-key=@t] + [%add-bucket bucket=@t] + [%remove-bucket bucket=@t] + [%set-current-bucket bucket=@t] + [%set-region region=@t] + == +:: ++$ update + $% [%credentials =credentials] + [%configuration =configuration] + action + == +-- From cf17c01bad710028f9d3a4fe84f40723886f6be6 Mon Sep 17 00:00:00 2001 From: Hunter Miller Date: Thu, 4 May 2023 13:27:16 -0500 Subject: [PATCH 2/3] settings/storage: 413 compat --- desk/app/settings.hoon | 2 +- desk/app/storage.hoon | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/desk/app/settings.hoon b/desk/app/settings.hoon index b4862d2..d7497c0 100644 --- a/desk/app/settings.hoon +++ b/desk/app/settings.hoon @@ -69,7 +69,7 @@ ?> ?=(%migrate !<(%migrate vas)) =/ bas /(scot %p our.bowl)/settings-store/(scot %da now.bowl) :- ~ - ?. .^(? %gu bas) + ?. .^(? %gu (weld bas /$)) state =/ ful .^(data %gx (weld bas /all/noun)) ?+ -.ful (on-poke:def mar vas) diff --git a/desk/app/storage.hoon b/desk/app/storage.hoon index 7e135cb..910734f 100644 --- a/desk/app/storage.hoon +++ b/desk/app/storage.hoon @@ -79,7 +79,7 @@ ?> ?=(%migrate !<(%migrate vase)) =/ bas /(scot %p our.bowl)/s3-store/(scot %da now.bowl) :- ~ - ?. .^(? %gu bas) + ?. .^(? %gu (weld bas /$)) state =: credentials From 39cac10d782a4b5c13a7f3c1e9f5e5060bd8197b Mon Sep 17 00:00:00 2001 From: Hunter Miller Date: Thu, 4 May 2023 13:27:37 -0500 Subject: [PATCH 3/3] settings/storage: update frontend --- ui/package.json | 3 + ui/public/mockServiceWorker.js | 339 +++++++++++++++------------- ui/src/preferences/StoragePrefs.tsx | 37 +-- ui/src/state/settings.ts | 31 ++- ui/src/state/storage.ts | 20 +- 5 files changed, 242 insertions(+), 188 deletions(-) diff --git a/ui/package.json b/ui/package.json index 5f5b0b6..bbcb626 100644 --- a/ui/package.json +++ b/ui/package.json @@ -2,6 +2,9 @@ "name": "landscape", "version": "0.0.0", "private": true, + "engines": { + "node": "^16.0.0" + }, "scripts": { "dev": "vite", "mock": "vite --mode mock", diff --git a/ui/public/mockServiceWorker.js b/ui/public/mockServiceWorker.js index 87e0f31..0966a9d 100644 --- a/ui/public/mockServiceWorker.js +++ b/ui/public/mockServiceWorker.js @@ -2,21 +2,22 @@ /* tslint:disable */ /** - * Mock Service Worker (1.2.1). + * Mock Service Worker (0.39.2). * @see https://github.com/mswjs/msw * - Please do NOT modify this file. * - Please do NOT serve this file on production. */ -const INTEGRITY_CHECKSUM = '3d6b9f06410d179a7f7404d4bf4c3c70' +const INTEGRITY_CHECKSUM = '02f4ad4a2797f85668baf196e553d929' +const bypassHeaderName = 'x-msw-bypass' const activeClientIds = new Set() self.addEventListener('install', function () { - self.skipWaiting() + return self.skipWaiting() }) -self.addEventListener('activate', function (event) { - event.waitUntil(self.clients.claim()) +self.addEventListener('activate', async function (event) { + return self.clients.claim() }) self.addEventListener('message', async function (event) { @@ -32,9 +33,7 @@ self.addEventListener('message', async function (event) { return } - const allClients = await self.clients.matchAll({ - type: 'window', - }) + const allClients = await self.clients.matchAll() switch (event.data) { case 'KEEPALIVE_REQUEST': { @@ -84,6 +83,161 @@ self.addEventListener('message', async function (event) { } }) +// Resolve the "main" client for the given event. +// Client that issues a request doesn't necessarily equal the client +// that registered the worker. It's with the latter the worker should +// communicate with during the response resolving phase. +async function resolveMainClient(event) { + const client = await self.clients.get(event.clientId) + + if (client.frameType === 'top-level') { + return client + } + + const allClients = await self.clients.matchAll() + + return allClients + .filter((client) => { + // Get only those clients that are currently visible. + return client.visibilityState === 'visible' + }) + .find((client) => { + // Find the client ID that's recorded in the + // set of clients that have registered the worker. + return activeClientIds.has(client.id) + }) +} + +async function handleRequest(event, requestId) { + const client = await resolveMainClient(event) + const response = await getResponse(event, client, requestId) + + // Send back the response clone for the "response:*" life-cycle events. + // Ensure MSW is active and ready to handle the message, otherwise + // this message will pend indefinitely. + if (client && activeClientIds.has(client.id)) { + ;(async function () { + const clonedResponse = response.clone() + sendToClient(client, { + type: 'RESPONSE', + payload: { + requestId, + type: clonedResponse.type, + ok: clonedResponse.ok, + status: clonedResponse.status, + statusText: clonedResponse.statusText, + body: + clonedResponse.body === null ? null : await clonedResponse.text(), + headers: serializeHeaders(clonedResponse.headers), + redirected: clonedResponse.redirected, + }, + }) + })() + } + + return response +} + +async function getResponse(event, client, requestId) { + const { request } = event + const requestClone = request.clone() + const getOriginalResponse = () => fetch(requestClone) + + // Bypass mocking when the request client is not active. + if (!client) { + return getOriginalResponse() + } + + // Bypass initial page load requests (i.e. static assets). + // The absence of the immediate/parent client in the map of the active clients + // means that MSW hasn't dispatched the "MOCK_ACTIVATE" event yet + // and is not ready to handle requests. + if (!activeClientIds.has(client.id)) { + return await getOriginalResponse() + } + + // Bypass requests with the explicit bypass header + if (requestClone.headers.get(bypassHeaderName) === 'true') { + const cleanRequestHeaders = serializeHeaders(requestClone.headers) + + // Remove the bypass header to comply with the CORS preflight check. + delete cleanRequestHeaders[bypassHeaderName] + + const originalRequest = new Request(requestClone, { + headers: new Headers(cleanRequestHeaders), + }) + + return fetch(originalRequest) + } + + // Send the request to the client-side MSW. + const reqHeaders = serializeHeaders(request.headers) + const body = await request.text() + + const clientMessage = await sendToClient(client, { + type: 'REQUEST', + payload: { + id: requestId, + url: request.url, + method: request.method, + headers: reqHeaders, + cache: request.cache, + mode: request.mode, + credentials: request.credentials, + destination: request.destination, + integrity: request.integrity, + redirect: request.redirect, + referrer: request.referrer, + referrerPolicy: request.referrerPolicy, + body, + bodyUsed: request.bodyUsed, + keepalive: request.keepalive, + }, + }) + + switch (clientMessage.type) { + case 'MOCK_SUCCESS': { + return delayPromise( + () => respondWithMock(clientMessage), + clientMessage.payload.delay, + ) + } + + case 'MOCK_NOT_FOUND': { + return getOriginalResponse() + } + + case 'NETWORK_ERROR': { + const { name, message } = clientMessage.payload + const networkError = new Error(message) + networkError.name = name + + // Rejecting a request Promise emulates a network error. + throw networkError + } + + case 'INTERNAL_ERROR': { + const parsedBody = JSON.parse(clientMessage.payload.body) + + console.error( + `\ +[MSW] Uncaught exception in the request handler for "%s %s": + +${parsedBody.location} + +This exception has been gracefully handled as a 500 response, however, it's strongly recommended to resolve this error, as it indicates a mistake in your code. If you wish to mock an error response, please see this guide: https://mswjs.io/docs/recipes/mocking-error-responses\ +`, + request.method, + request.url, + ) + + return respondWithMock(clientMessage) + } + } + + return getOriginalResponse() +} + self.addEventListener('fetch', function (event) { const { request } = event const accept = request.headers.get('accept') || '' @@ -111,10 +265,9 @@ self.addEventListener('fetch', function (event) { return } - // Generate unique request ID. - const requestId = Math.random().toString(16).slice(2) + const requestId = uuidv4() - event.respondWith( + return event.respondWith( handleRequest(event, requestId).catch((error) => { if (error.name === 'NetworkError') { console.warn( @@ -137,142 +290,14 @@ self.addEventListener('fetch', function (event) { ) }) -async function handleRequest(event, requestId) { - const client = await resolveMainClient(event) - const response = await getResponse(event, client, requestId) - - // Send back the response clone for the "response:*" life-cycle events. - // Ensure MSW is active and ready to handle the message, otherwise - // this message will pend indefinitely. - if (client && activeClientIds.has(client.id)) { - ;(async function () { - const clonedResponse = response.clone() - sendToClient(client, { - type: 'RESPONSE', - payload: { - requestId, - type: clonedResponse.type, - ok: clonedResponse.ok, - status: clonedResponse.status, - statusText: clonedResponse.statusText, - body: - clonedResponse.body === null ? null : await clonedResponse.text(), - headers: Object.fromEntries(clonedResponse.headers.entries()), - redirected: clonedResponse.redirected, - }, - }) - })() - } - - return response -} - -// Resolve the main client for the given event. -// Client that issues a request doesn't necessarily equal the client -// that registered the worker. It's with the latter the worker should -// communicate with during the response resolving phase. -async function resolveMainClient(event) { - const client = await self.clients.get(event.clientId) - - if (client?.frameType === 'top-level') { - return client - } - - const allClients = await self.clients.matchAll({ - type: 'window', +function serializeHeaders(headers) { + const reqHeaders = {} + headers.forEach((value, name) => { + reqHeaders[name] = reqHeaders[name] + ? [].concat(reqHeaders[name]).concat(value) + : value }) - - return allClients - .filter((client) => { - // Get only those clients that are currently visible. - return client.visibilityState === 'visible' - }) - .find((client) => { - // Find the client ID that's recorded in the - // set of clients that have registered the worker. - return activeClientIds.has(client.id) - }) -} - -async function getResponse(event, client, requestId) { - const { request } = event - const clonedRequest = request.clone() - - function passthrough() { - // Clone the request because it might've been already used - // (i.e. its body has been read and sent to the client). - const headers = Object.fromEntries(clonedRequest.headers.entries()) - - // Remove MSW-specific request headers so the bypassed requests - // comply with the server's CORS preflight check. - // Operate with the headers as an object because request "Headers" - // are immutable. - delete headers['x-msw-bypass'] - - return fetch(clonedRequest, { headers }) - } - - // Bypass mocking when the client is not active. - if (!client) { - return passthrough() - } - - // Bypass initial page load requests (i.e. static assets). - // The absence of the immediate/parent client in the map of the active clients - // means that MSW hasn't dispatched the "MOCK_ACTIVATE" event yet - // and is not ready to handle requests. - if (!activeClientIds.has(client.id)) { - return passthrough() - } - - // Bypass requests with the explicit bypass header. - // Such requests can be issued by "ctx.fetch()". - if (request.headers.get('x-msw-bypass') === 'true') { - return passthrough() - } - - // Notify the client that a request has been intercepted. - const clientMessage = await sendToClient(client, { - type: 'REQUEST', - payload: { - id: requestId, - url: request.url, - method: request.method, - headers: Object.fromEntries(request.headers.entries()), - cache: request.cache, - mode: request.mode, - credentials: request.credentials, - destination: request.destination, - integrity: request.integrity, - redirect: request.redirect, - referrer: request.referrer, - referrerPolicy: request.referrerPolicy, - body: await request.text(), - bodyUsed: request.bodyUsed, - keepalive: request.keepalive, - }, - }) - - switch (clientMessage.type) { - case 'MOCK_RESPONSE': { - return respondWithMock(clientMessage.data) - } - - case 'MOCK_NOT_FOUND': { - return passthrough() - } - - case 'NETWORK_ERROR': { - const { name, message } = clientMessage.data - const networkError = new Error(message) - networkError.name = name - - // Rejecting a "respondWith" promise emulates a network error. - throw networkError - } - } - - return passthrough() + return reqHeaders } function sendToClient(client, message) { @@ -287,17 +312,27 @@ function sendToClient(client, message) { resolve(event.data) } - client.postMessage(message, [channel.port2]) + client.postMessage(JSON.stringify(message), [channel.port2]) }) } -function sleep(timeMs) { +function delayPromise(cb, duration) { return new Promise((resolve) => { - setTimeout(resolve, timeMs) + setTimeout(() => resolve(cb()), duration) }) } -async function respondWithMock(response) { - await sleep(response.delay) - return new Response(response.body, response) +function respondWithMock(clientMessage) { + return new Response(clientMessage.payload.body, { + ...clientMessage.payload, + headers: clientMessage.payload.headers, + }) +} + +function uuidv4() { + return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { + const r = (Math.random() * 16) | 0 + const v = c == 'x' ? r : (r & 0x3) | 0x8 + return v.toString(16) + }) } diff --git a/ui/src/preferences/StoragePrefs.tsx b/ui/src/preferences/StoragePrefs.tsx index b7d540e..629dac3 100644 --- a/ui/src/preferences/StoragePrefs.tsx +++ b/ui/src/preferences/StoragePrefs.tsx @@ -1,11 +1,5 @@ import React, { useCallback, useState, FormEvent, useEffect } from 'react'; import api from '../state/api'; -import { - setAccessKeyId, - setCurrentBucket, - setEndpoint, - setSecretAccessKey, -} from '@urbit/api'; import { useForm } from 'react-hook-form'; import cn from 'classnames'; import { useAsyncCall } from '../logic/useAsyncCall'; @@ -21,6 +15,23 @@ interface CredentialsSubmit { bucket: string; } +type S3Update = + | { 'set-region': string } + | { 'set-endpoint': string } + | { 'set-access-key-id': string } + | { 'set-secret-access-key': string } + | { 'set-current-bucket': string } + | { 'add-bucket': string } + | { 'remove-bucket': string }; + +function storagePoke(data: S3Update | { 'set-region': string }) { + return { + app: 'storage', + mark: 'storage-action', + json: data, + }; +} + export const StoragePrefs = () => { const { s3, loaded, ...storageState } = useStorageState(); @@ -35,15 +46,11 @@ export const StoragePrefs = () => { const { call: addS3Credentials, status } = useAsyncCall( useCallback(async (data: CredentialsSubmit) => { - api.poke(setEndpoint(data.endpoint)); - api.poke(setAccessKeyId(data.accessId)); - api.poke(setSecretAccessKey(data.accessSecret)); - api.poke(setCurrentBucket(data.bucket)); - api.poke({ - app: 's3-store', - mark: 's3-action', - json: { 'set-region': data.region }, - }); + api.poke(storagePoke({ 'set-endpoint': data.endpoint })); + api.poke(storagePoke({ 'set-access-key-id': data.accessId })); + api.poke(storagePoke({ 'set-secret-access-key': data.accessSecret })); + api.poke(storagePoke({ 'set-current-bucket': data.bucket })); + api.poke(storagePoke({ 'set-region': data.region })); }, []) ); diff --git a/ui/src/state/settings.ts b/ui/src/state/settings.ts index 309a6d5..c577cf0 100644 --- a/ui/src/state/settings.ts +++ b/ui/src/state/settings.ts @@ -1,11 +1,5 @@ /* eslint-disable no-param-reassign */ -import { - SettingsUpdate, - Value, - putEntry as doPutEntry, - getDeskSettings, - DeskData, -} from '@urbit/api'; +import { SettingsUpdate, Value, DeskData } from '@urbit/api'; import _ from 'lodash'; import { BaseState, @@ -109,12 +103,27 @@ export const useSettingsState = createState( }, loaded: false, putEntry: async (bucket, key, val) => { - const poke = doPutEntry(window.desk, bucket, key, val); + const poke = { + app: 'settings', + mark: 'settings-update', + json: { + 'put-entry': { + desk: window.desk, + 'bucket-key': bucket, + 'entry-key': key, + value: val, + }, + }, + }; await pokeOptimisticallyN(useSettingsState, poke, reduceUpdate); }, fetchAll: async () => { - const result = (await api.scry(getDeskSettings(window.desk))) - .desk; + const result = ( + await api.scry({ + app: 'settings', + path: `/desk/${window.desk}`, + }) + ).desk; const newState = { ..._.mergeWith(get(), result, (obj, src) => _.isArray(src) ? src : undefined @@ -127,7 +136,7 @@ export const useSettingsState = createState( [], [ (set, get) => - createSubscription('settings-store', `/desk/${window.desk}`, (e) => { + createSubscription('settings', `/desk/${window.desk}`, (e) => { const data = _.get(e, 'settings-event', false); if (data) { reduceStateN(get(), data, reduceUpdate); diff --git a/ui/src/state/storage.ts b/ui/src/state/storage.ts index b0b509d..847ae36 100644 --- a/ui/src/state/storage.ts +++ b/ui/src/state/storage.ts @@ -35,7 +35,7 @@ export interface BaseStorageState { [ref: string]: unknown; } -export type StorageState = BaseStorageState & BaseState +export type StorageState = BaseStorageState & BaseState; export const useStorageState = createState( 'Storage', @@ -48,7 +48,7 @@ export const useStorageState = createState( inputMark: 'noun', outputMark: 'json', threadName: 'gcp-is-configured', - body: {} + body: {}, }); }, getToken: async () => { @@ -56,33 +56,33 @@ export const useStorageState = createState( inputMark: 'noun', outputMark: 'gcp-token', threadName: 'gcp-get-token', - body: {} + body: {}, }); get().set((state) => { state.gcp.token = token; }); - } + }, }, s3: { configuration: { buckets: new Set(), currentBucket: '', - region: 'global' + region: 'global', }, - credentials: null - } + credentials: null, + }, }), ['loaded', 's3', 'gcp'], [ (set, get) => - createSubscription('s3-store', '/all', (e) => { + createSubscription('storage', '/all', (e) => { const d = _.get(e, 's3-update', false); if (d) { - console.log(d) + console.log(d); reduceStateN(get(), d, reduce); set({ loaded: true }); } - }) + }), ] );