diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index d0121c02e..6d15d72cc 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -45,12 +45,12 @@ jobs: - run: npm run e2e --workspaces --if-present env: CI: true - REPLICA_PORT: 8000 + REPLICA_PORT: 4943 aggregate: name: e2e:required if: ${{ always() }} - needs: [ test ] + needs: [test] runs-on: ubuntu-latest steps: - name: check e2e test result diff --git a/.github/workflows/mitm.yml b/.github/workflows/mitm.yml index c1c65d824..33150f1cf 100644 --- a/.github/workflows/mitm.yml +++ b/.github/workflows/mitm.yml @@ -51,7 +51,7 @@ jobs: id: mitmdump run: | set -ex - mitmdump -p 8888 --mode reverse:http://127.0.0.1:8000 \ + mitmdump -p 8888 --mode reverse:http://127.0.0.1:4943 \ --modify-headers '/~s/Transfer-Encoding/' \ --modify-body '/~s/Hello/Hullo' \ & @@ -67,7 +67,7 @@ jobs: aggregate: name: mitm:required if: ${{ always() }} - needs: [ test ] + needs: [test] runs-on: ubuntu-latest steps: - name: check e2e test result diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml index 534988c77..a35d85a6b 100644 --- a/.github/workflows/unit-tests.yml +++ b/.github/workflows/unit-tests.yml @@ -19,7 +19,6 @@ jobs: spec: - '0.16.1' node: - - 12 - 14 - 16 diff --git a/e2e/browser/.proxyrc b/e2e/browser/.proxyrc index 190c27806..e85d9991a 100644 --- a/e2e/browser/.proxyrc +++ b/e2e/browser/.proxyrc @@ -1,5 +1,5 @@ { "/api": { - "target": "http://localhost:8000/", + "target": "http://localhost:4943/", } } diff --git a/packages/agent/src/agent/http/http.test.ts b/packages/agent/src/agent/http/http.test.ts index 5c4613acf..6c5b85b58 100644 --- a/packages/agent/src/agent/http/http.test.ts +++ b/packages/agent/src/agent/http/http.test.ts @@ -22,6 +22,8 @@ const { window } = new JSDOM(`

Hello world

`); window.fetch = global.fetch; (global as any).window = window; +const HTTP_AGENT_HOST = 'http://localhost:4943'; + const DEFAULT_INGRESS_EXPIRY_DELTA_IN_MSECS = 5 * 60 * 1000; const REPLICA_PERMITTED_DRIFT_MILLISECONDS = 60 * 1000; const NANOSECONDS_PER_MILLISECONDS = 1_000_000; @@ -313,26 +315,26 @@ test('use anonymous principal if unspecified', async () => { describe('getDefaultFetch', () => { it("should use fetch from window if it's available", async () => { - const generateAgent = () => new HttpAgent({ host: 'localhost:8000' }); + const generateAgent = () => new HttpAgent({ host: HTTP_AGENT_HOST }); expect(generateAgent).not.toThrowError(); }); it('should throw an error if fetch is not available on the window object', async () => { delete (window as any).fetch; - const generateAgent = () => new HttpAgent({ host: 'localhost:8000' }); + const generateAgent = () => new HttpAgent({ host: HTTP_AGENT_HOST }); expect(generateAgent).toThrowError('Fetch implementation was not available'); }); it('should throw error for defaultFetch with no window or global fetch', () => { delete (global as any).window; delete (global as any).fetch; - const generateAgent = () => new HttpAgent({ host: 'localhost:8000' }); + const generateAgent = () => new HttpAgent({ host: HTTP_AGENT_HOST }); expect(generateAgent).toThrowError('Fetch implementation was not available'); }); it('should fall back to global.fetch if window is not available', () => { delete (global as any).window; global.fetch = originalFetch; - const generateAgent = () => new HttpAgent({ host: 'localhost:8000' }); + const generateAgent = () => new HttpAgent({ host: HTTP_AGENT_HOST }); expect(generateAgent).not.toThrowError(); }); @@ -494,7 +496,7 @@ describe('retry failures', () => { statusText: 'Internal Server Error', }), ); - const agent = new HttpAgent({ host: 'http://localhost:8000', fetch: mockFetch, retryTimes: 0 }); + const agent = new HttpAgent({ host: HTTP_AGENT_HOST, fetch: mockFetch, retryTimes: 0 }); expect( agent.call(Principal.managementCanister(), { methodName: 'test', @@ -510,7 +512,7 @@ describe('retry failures', () => { }); }); - const agent = new HttpAgent({ host: 'http://localhost:8000', fetch: mockFetch }); + const agent = new HttpAgent({ host: HTTP_AGENT_HOST, fetch: mockFetch }); try { expect( agent.call(Principal.managementCanister(), { @@ -540,7 +542,7 @@ describe('retry failures', () => { } }); - const agent = new HttpAgent({ host: 'http://localhost:8000', fetch: mockFetch }); + const agent = new HttpAgent({ host: HTTP_AGENT_HOST, fetch: mockFetch }); const result = await agent.call(Principal.managementCanister(), { methodName: 'test', arg: new Uint8Array().buffer, @@ -556,7 +558,7 @@ test('should change nothing if time is within 30 seconds of replica', async () = // jest.setSystemTime(systemTime); const mockFetch = jest.fn(); - const agent = new HttpAgent({ host: 'http://localhost:8000', fetch: mockFetch }); + const agent = new HttpAgent({ host: HTTP_AGENT_HOST, fetch: mockFetch }); await agent.syncTime(); @@ -590,7 +592,7 @@ test('should adjust the Expiry if the clock is more than 30 seconds behind', asy await import('../../canisterStatus'); const { HttpAgent } = await import('../index'); - const agent = new HttpAgent({ host: 'http://localhost:8000', fetch: mockFetch }); + const agent = new HttpAgent({ host: HTTP_AGENT_HOST, fetch: mockFetch }); await agent.syncTime(); @@ -633,7 +635,7 @@ test('should adjust the Expiry if the clock is more than 30 seconds ahead', asyn await import('../../canisterStatus'); const { HttpAgent } = await import('../index'); - const agent = new HttpAgent({ host: 'http://localhost:8000', fetch: mockFetch }); + const agent = new HttpAgent({ host: HTTP_AGENT_HOST, fetch: mockFetch }); await agent.syncTime(); @@ -657,3 +659,43 @@ test('should adjust the Expiry if the clock is more than 30 seconds ahead', asyn expect(delay).toBe(-1 * DEFAULT_INGRESS_EXPIRY_DELTA_IN_MSECS); jest.resetModules(); }); + +test('should fetch with given call options and fetch options', async () => { + const mockFetch: jest.Mock = jest.fn(() => { + const body = cbor.encode({}); + return Promise.resolve( + new Response(body, { + status: 200, + }), + ); + }); + + const canisterId: Principal = Principal.fromText('2chl6-4hpzw-vqaaa-aaaaa-c'); + const httpAgent = new HttpAgent({ + fetch: mockFetch, + host: 'http://localhost', + callOptions: { + reactNative: { textStreaming: true }, + }, + fetchOptions: { + reactNative: { + __nativeResponseType: 'base64', + }, + }, + }); + + await httpAgent.call(canisterId, { + methodName: 'greet', + arg: new Uint8Array([]), + }); + + await httpAgent.query(canisterId, { + methodName: 'greet', + arg: new Uint8Array([]), + }); + + const { calls } = mockFetch.mock; + + expect(calls[0][1].reactNative).toStrictEqual({ textStreaming: true }); + expect(calls[1][1].reactNative.__nativeResponseType).toBe('base64'); +}); diff --git a/packages/agent/src/agent/http/index.ts b/packages/agent/src/agent/http/index.ts index 4a53c6d67..e52709762 100644 --- a/packages/agent/src/agent/http/index.ts +++ b/packages/agent/src/agent/http/index.ts @@ -72,6 +72,13 @@ export interface HttpAgentOptions { // A surrogate to the global fetch function. Useful for testing. fetch?: typeof fetch; + // Additional options to pass along to fetch. Will not override fields that + // the agent already needs to set + fetchOptions?: Record; + + // Additional options to pass along to fetch for the call API. + callOptions?: Record; + // The host to use for the client. By default, uses the same host as // the current page. host?: string; @@ -162,6 +169,8 @@ export class HttpAgent implements Agent { private readonly _pipeline: HttpAgentRequestTransformFn[] = []; private _identity: Promise | null; private readonly _fetch: typeof fetch; + private readonly _fetchOptions?: Record; + private readonly _callOptions?: Record; private _timeDiffMsecs = 0; private readonly _host: URL; private readonly _credentials: string | undefined; @@ -181,6 +190,8 @@ export class HttpAgent implements Agent { this._credentials = options.source._credentials; } else { this._fetch = options.fetch || getDefaultFetch() || fetch.bind(global); + this._fetchOptions = options.fetchOptions; + this._callOptions = options.callOptions; } if (options.host !== undefined) { if (!options.host.match(/^[a-z]+:/) && typeof window !== 'undefined') { @@ -301,6 +312,7 @@ export class HttpAgent implements Agent { const request = this._requestAndRetry(() => this._fetch('' + new URL(`/api/v2/canister/${ecid.toText()}/call`, this._host), { + ...this._callOptions, ...transformedRequest.request, body, }), @@ -386,6 +398,7 @@ export class HttpAgent implements Agent { const body = cbor.encode(transformedRequest.body); const response = await this._requestAndRetry(() => this._fetch('' + new URL(`/api/v2/canister/${canister.toText()}/query`, this._host), { + ...this._fetchOptions, ...transformedRequest.request, body, }), @@ -445,6 +458,7 @@ export class HttpAgent implements Agent { const response = await this._fetch( '' + new URL(`/api/v2/canister/${canister}/read_state`, this._host), { + ...this._fetchOptions, ...transformedRequest.request, body, }, @@ -497,7 +511,7 @@ export class HttpAgent implements Agent { : {}; const response = await this._requestAndRetry(() => - this._fetch('' + new URL(`/api/v2/status`, this._host), { headers }), + this._fetch('' + new URL(`/api/v2/status`, this._host), { headers, ...this._fetchOptions }), ); return cbor.decode(await response.arrayBuffer()); diff --git a/packages/agent/src/canisterStatus/index.test.ts b/packages/agent/src/canisterStatus/index.test.ts index 4d1f0d40f..51e55a7a0 100644 --- a/packages/agent/src/canisterStatus/index.test.ts +++ b/packages/agent/src/canisterStatus/index.test.ts @@ -46,7 +46,7 @@ const getRealStatus = async () => { ), )) as unknown as Identity; - const agent = new HttpAgent({ host: 'http://127.0.0.1:8000', fetch, identity }); + const agent = new HttpAgent({ host: 'http://127.0.0.1:4943', fetch, identity }); await agent.fetchRootKey(); const canisterBuffer = new DataView(testPrincipal.toUint8Array().buffer).buffer; canisterBuffer; diff --git a/packages/auth-client/src/index.test.ts b/packages/auth-client/src/index.test.ts index 7c1525929..9c59be3e3 100644 --- a/packages/auth-client/src/index.test.ts +++ b/packages/auth-client/src/index.test.ts @@ -85,7 +85,7 @@ describe('Auth Client', () => { (window as any).location = { reload: jest.fn(), fetch, - toString: jest.fn(() => 'http://localhost:8000'), + toString: jest.fn(() => 'http://localhost:4943'), }; const identity = Ed25519KeyIdentity.generate();