Skip to content

Commit 85e9388

Browse files
ci(release): publish latest release
1 parent 4afd9d2 commit 85e9388

5 files changed

Lines changed: 97 additions & 23 deletions

File tree

RELEASE

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
IPFS hash of the deployment:
2-
- CIDv0: `QmZ886Y4R1mRjvShMBjdoML9w4YWW7AQ4TEx6yPX99njMt`
3-
- CIDv1: `bafybeifahl4nbhp4cflojwiis54zx2qou27tdwekvtbi47ire27sdk73vm`
2+
- CIDv0: `QmNftmD2jYM2MD7TjHfd7wmu3tgBvXJCra1Z1z9xEYtrLA`
3+
- CIDv1: `bafybeiae5xwpty5fbnkusf3g5twy3dpy2bjgjemsw6jvd4wehdyn46xbam`
44

55
The latest release is always mirrored at [app.uniswap.org](https://app.uniswap.org).
66

@@ -10,5 +10,5 @@ You can also access the Uniswap Interface from an IPFS gateway.
1010
Your Uniswap settings are never remembered across different URLs.
1111

1212
IPFS gateways:
13-
- https://bafybeifahl4nbhp4cflojwiis54zx2qou27tdwekvtbi47ire27sdk73vm.ipfs.dweb.link/
14-
- [ipfs://QmZ886Y4R1mRjvShMBjdoML9w4YWW7AQ4TEx6yPX99njMt/](ipfs://QmZ886Y4R1mRjvShMBjdoML9w4YWW7AQ4TEx6yPX99njMt/)
13+
- https://bafybeiae5xwpty5fbnkusf3g5twy3dpy2bjgjemsw6jvd4wehdyn46xbam.ipfs.dweb.link/
14+
- [ipfs://QmNftmD2jYM2MD7TjHfd7wmu3tgBvXJCra1Z1z9xEYtrLA/](ipfs://QmNftmD2jYM2MD7TjHfd7wmu3tgBvXJCra1Z1z9xEYtrLA/)

VERSION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
web/5.154.2
1+
web/5.154.3

apps/web/src/state/livePrices/LivePricesProvider.tsx

Lines changed: 5 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,6 @@
1-
import {
2-
createFetchClient,
3-
getEntryGatewayUrl,
4-
getWebSocketUrl,
5-
provideSessionService,
6-
SharedQueryClient,
7-
} from '@universe/api'
8-
import { isDevEnv, REQUEST_SOURCE } from '@universe/environment'
9-
import { FeatureFlags, getIsSessionServiceEnabled, useFeatureFlag } from '@universe/gating'
1+
import { getEntryGatewayUrl, getWebSocketUrl, SharedQueryClient } from '@universe/api'
2+
import { isDevEnv } from '@universe/environment'
3+
import { FeatureFlags, useFeatureFlag } from '@universe/gating'
104
import type { TokenPriceMessage, TokenSubscriptionParams } from '@universe/prices'
115
import {
126
createPriceKey,
@@ -21,6 +15,7 @@ import type { ReactElement, ReactNode } from 'react'
2115
import { useState } from 'react'
2216
import { RemotePriceProvider } from 'uniswap/src/features/prices/RemotePriceProvider'
2317
import { logger } from 'utilities/src/logger/logger'
18+
import { createLivePricesFetchClient } from '~/state/livePrices/createLivePricesFetchClient'
2419

2520
function createLivePricesClient(): WebSocketClient<TokenSubscriptionParams, TokenPriceMessage['data']> | null {
2621
const wsUrl = getWebSocketUrl()
@@ -40,15 +35,7 @@ function createLivePricesClient(): WebSocketClient<TokenSubscriptionParams, Toke
4035
devtoolsName: 'livePricesConnection',
4136
})
4237

43-
const fetchClient = createFetchClient({
44-
baseUrl: subscriptionApiUrl,
45-
getHeaders: () => ({
46-
'Content-Type': 'application/json',
47-
'x-request-source': REQUEST_SOURCE,
48-
}),
49-
getSessionService: () =>
50-
provideSessionService({ getBaseUrl: () => getEntryGatewayUrl(), getIsSessionServiceEnabled }),
51-
})
38+
const fetchClient = createLivePricesFetchClient({ subscriptionApiUrl })
5239

5340
const subscriptionHandler = createPriceSubscriptionHandler({
5441
client: fetchClient,
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import { afterEach, describe, expect, it, vi } from 'vitest'
2+
import { createLivePricesFetchClient } from '~/state/livePrices/createLivePricesFetchClient'
3+
4+
vi.mock('@universe/gating', () => ({
5+
getIsSessionServiceEnabled: (): boolean => false,
6+
}))
7+
8+
/**
9+
* On web the session is an HttpOnly cookie; the browser only attaches it to
10+
* cross-origin EventSubscriptionService requests when the fetch runs with
11+
* `credentials: 'include'`. Without it the entry gateway sees no session and
12+
* Subscribe / RefreshSession fail with 401 — these tests are that contract.
13+
*/
14+
describe('createLivePricesFetchClient', () => {
15+
afterEach(() => {
16+
vi.unstubAllGlobals()
17+
})
18+
19+
function setup(): { fetchMock: ReturnType<typeof vi.fn> } {
20+
const fetchMock = vi.fn(async () => new Response(JSON.stringify({}), { status: 200 }))
21+
vi.stubGlobal('fetch', fetchMock)
22+
return { fetchMock }
23+
}
24+
25+
it('sends credentials: include on Subscribe so the web session cookie is attached', async () => {
26+
const { fetchMock } = setup()
27+
const client = createLivePricesFetchClient({ subscriptionApiUrl: 'https://entry-gateway.example.com' })
28+
29+
await client.post('/uniswap.notificationservice.v1.EventSubscriptionService/Subscribe', {
30+
body: JSON.stringify({ connectionId: 'connection-1' }),
31+
})
32+
33+
expect(fetchMock).toHaveBeenCalledWith(
34+
'https://entry-gateway.example.com/uniswap.notificationservice.v1.EventSubscriptionService/Subscribe',
35+
expect.objectContaining({ credentials: 'include', method: 'POST' }),
36+
)
37+
})
38+
39+
it('sends credentials: include on RefreshSession so the web session cookie is attached', async () => {
40+
const { fetchMock } = setup()
41+
const client = createLivePricesFetchClient({ subscriptionApiUrl: 'https://entry-gateway.example.com' })
42+
43+
await client.post('/uniswap.notificationservice.v1.EventSubscriptionService/RefreshSession', {
44+
body: JSON.stringify({ connectionId: 'connection-1' }),
45+
})
46+
47+
expect(fetchMock).toHaveBeenCalledWith(
48+
'https://entry-gateway.example.com/uniswap.notificationservice.v1.EventSubscriptionService/RefreshSession',
49+
expect.objectContaining({ credentials: 'include', method: 'POST' }),
50+
)
51+
})
52+
53+
it('still applies the request-source header alongside the credentials mode', async () => {
54+
const { fetchMock } = setup()
55+
const client = createLivePricesFetchClient({ subscriptionApiUrl: 'https://entry-gateway.example.com' })
56+
57+
await client.post('/uniswap.notificationservice.v1.EventSubscriptionService/Subscribe', {
58+
body: JSON.stringify({ connectionId: 'connection-1' }),
59+
})
60+
61+
const init = fetchMock.mock.calls[0]?.[1] as RequestInit
62+
expect(new Headers(init.headers).get('x-request-source')).toBeTruthy()
63+
})
64+
})
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { createFetchClient, type FetchClient, getEntryGatewayUrl, provideSessionService } from '@universe/api'
2+
import { REQUEST_SOURCE } from '@universe/environment'
3+
import { getIsSessionServiceEnabled } from '@universe/gating'
4+
5+
/**
6+
* FetchClient for the EventSubscriptionService RPCs (Subscribe / Unsubscribe /
7+
* RefreshSession). These are session-authenticated: on web the session lives in
8+
* an HttpOnly cookie on the entry gateway host, so requests must run with
9+
* `credentials: 'include'` for the browser to attach it when the entry gateway
10+
* is reached cross-origin (same pattern as the trading API clients).
11+
*/
12+
export function createLivePricesFetchClient({ subscriptionApiUrl }: { subscriptionApiUrl: string }): FetchClient {
13+
return createFetchClient({
14+
baseUrl: subscriptionApiUrl,
15+
getHeaders: () => ({
16+
'Content-Type': 'application/json',
17+
'x-request-source': REQUEST_SOURCE,
18+
}),
19+
getSessionService: () =>
20+
provideSessionService({ getBaseUrl: () => getEntryGatewayUrl(), getIsSessionServiceEnabled }),
21+
defaultOptions: { credentials: 'include' },
22+
})
23+
}

0 commit comments

Comments
 (0)