-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #5 from Opetushallitus/http-client-pure-fetch
Http client pure fetch
- Loading branch information
Showing
6 changed files
with
145 additions
and
134 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,62 +1,71 @@ | ||
name: Valintojen toteuttaminen workflow | ||
on: | ||
push: | ||
branches: [ main, master ] | ||
branches: [main, master] | ||
pull_request: | ||
branches: [ main, master ] | ||
branches: [main, master] | ||
jobs: | ||
lint: | ||
timeout-minutes: 10 | ||
runs-on: ubuntu-latest | ||
steps: | ||
- uses: actions/checkout@v3 | ||
- uses: actions/setup-node@v3 | ||
with: | ||
node-version-file: '.nvmrc' | ||
- name: Install dependencies | ||
run: npm ci | ||
- name: Run lint | ||
run: npm run lint | ||
- uses: actions/checkout@v4 | ||
- uses: actions/setup-node@v4 | ||
with: | ||
node-version-file: '.nvmrc' | ||
- name: Install dependencies | ||
run: npm ci | ||
- name: Run lint | ||
run: npm run lint | ||
test: | ||
timeout-minutes: 10 | ||
runs-on: ubuntu-latest | ||
steps: | ||
- uses: actions/checkout@v3 | ||
- uses: actions/setup-node@v3 | ||
with: | ||
node-version-file: '.nvmrc' | ||
- name: Install dependencies | ||
run: npm ci | ||
- name: Run unit tests | ||
run: npm test | ||
- uses: actions/checkout@v4 | ||
- uses: actions/setup-node@v4 | ||
with: | ||
node-version-file: '.nvmrc' | ||
- name: Install dependencies | ||
run: npm ci | ||
- name: Run unit tests | ||
run: npm test | ||
e2e: | ||
timeout-minutes: 60 | ||
runs-on: ubuntu-latest | ||
steps: | ||
- uses: actions/checkout@v3 | ||
- uses: actions/setup-node@v3 | ||
with: | ||
node-version-file: '.nvmrc' | ||
- name: Install dependencies | ||
run: npm ci | ||
- name: Install Playwright Browsers | ||
run: npx playwright install --with-deps | ||
- name: install mkcert | ||
run: | | ||
curl -JLO "https://dl.filippo.io/mkcert/latest?for=linux/amd64" | ||
chmod +x mkcert-v*-linux-amd64 | ||
sudo cp mkcert-v*-linux-amd64 /usr/local/bin/mkcert | ||
- name: Create dev certificates | ||
run: npm run create-dev-certs | ||
- name: Start the app | ||
run: npm run dev-test & | ||
- name: Run Playwright tests | ||
run: npx playwright test | ||
- uses: actions/upload-artifact@v3 | ||
if: always() | ||
with: | ||
name: playwright-report | ||
path: playwright-report/ | ||
retention-days: 10 | ||
|
||
|
||
- uses: actions/checkout@v4 | ||
- uses: actions/setup-node@v4 | ||
with: | ||
node-version-file: '.nvmrc' | ||
- name: Install dependencies | ||
run: npm ci --no-audit --prefer-offline | ||
- name: Get installed Playwright version | ||
id: playwright-version | ||
run: echo "PLAYWRIGHT_VERSION=$(node -e "console.log(require('./package-lock.json').dependencies['@playwright/test'].version)")" >> $GITHUB_ENV | ||
- name: Cache playwright binaries | ||
uses: actions/cache@v4 | ||
id: playwright-cache | ||
with: | ||
path: | | ||
~/.cache/ms-playwright | ||
key: ${{ runner.os }}-playwright-${{ env.PLAYWRIGHT_VERSION }} | ||
- name: 'Install Playwright browsers' | ||
run: npx playwright install --with-deps | ||
if: steps.playwright-cache.outputs.cache-hit != 'true' | ||
- name: install mkcert | ||
run: | | ||
curl -JLO "https://dl.filippo.io/mkcert/latest?for=linux/amd64" | ||
chmod +x mkcert-v*-linux-amd64 | ||
sudo cp mkcert-v*-linux-amd64 /usr/local/bin/mkcert | ||
- name: Create dev certificates | ||
run: npm run create-dev-certs | ||
- name: Start the app | ||
run: npm run dev-test & | ||
- name: Run Playwright tests | ||
run: npx playwright test | ||
- uses: actions/upload-artifact@v4 | ||
if: always() | ||
with: | ||
name: playwright-report | ||
path: playwright-report/ | ||
retention-days: 10 |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,79 +1,107 @@ | ||
import xior, { XiorError, XiorInterceptorRequestConfig } from 'xior'; | ||
import { getCookies } from './cookie'; | ||
import { redirect } from 'next/navigation'; | ||
import { configuration } from './configuration'; | ||
|
||
const createClient = () => { | ||
const client_ = xior.create({ | ||
headers: { | ||
Accept: 'application/json', | ||
'Caller-id': '1.2.246.562.10.00000000001.valintojen-toteuttaminen', | ||
}, | ||
}); | ||
|
||
client_.interceptors.request.use( | ||
(request: XiorInterceptorRequestConfig<any>) => { | ||
const { method } = request; | ||
if (['post', 'put', 'patch', 'delete'].includes(method)) { | ||
const csrfCookie = getCookies()['CSRF']; | ||
if (csrfCookie) { | ||
request.headers.CSRF = csrfCookie; | ||
} | ||
} | ||
return request; | ||
}, | ||
); | ||
return client_; | ||
const doFetch = async (request: Request) => { | ||
try { | ||
const response = await fetch(request); | ||
return response.status >= 400 | ||
? Promise.reject(response) | ||
: Promise.resolve(response); | ||
} catch (e) { | ||
return Promise.reject(e); | ||
} | ||
}; | ||
|
||
export const client = createClient(); | ||
|
||
const bareClient = createClient(); | ||
|
||
const isUnauthorized = (error: XiorError) => { | ||
return error.response?.status === 401; | ||
const isUnauthenticated = (response: Response) => { | ||
return response?.status === 401; | ||
}; | ||
|
||
const isRedirected = (response: Response) => { | ||
return response.redirected; | ||
}; | ||
|
||
const redirectToLogin = () => { | ||
const loginUrl = new URL(configuration.loginUrl); | ||
loginUrl.searchParams.set('service', window.location.href); | ||
redirect(loginUrl.toString()); | ||
}; | ||
|
||
const makeBareRequest = (request: Request) => { | ||
const { method } = request; | ||
let modifiedOptions: RequestInit = { | ||
headers: { | ||
'Caller-id': '1.2.246.562.10.00000000001.valintojen-toteuttaminen', | ||
}, | ||
}; | ||
if (['POST', 'PUT', 'PATCH', 'DELETE'].includes(method)) { | ||
const csrfCookie = getCookies()['CSRF']; | ||
if (csrfCookie) { | ||
modifiedOptions.headers = { | ||
CSRF: csrfCookie, | ||
}; | ||
} | ||
} | ||
|
||
return doFetch(new Request(request, modifiedOptions)); | ||
}; | ||
|
||
const retryWithLogin = async (request: any, loginUrl: string) => { | ||
await bareClient.request(loginUrl); | ||
return await bareClient.request(request); | ||
await makeBareRequest(new Request(loginUrl)); | ||
return await makeBareRequest(request); | ||
}; | ||
|
||
client.interceptors.response.use( | ||
(data) => { | ||
if (isRedirected(data.response)) { | ||
if (data.response.url.includes('/cas/login')) { | ||
const loginUrl = new URL(configuration.loginUrl); | ||
loginUrl.searchParams.set('service', window.location.href); | ||
redirect(loginUrl.toString()); | ||
const responseToData = async (res: Response) => { | ||
const contentType = res.headers.get('Content-Type') ?? 'application/json'; | ||
|
||
if (contentType?.includes('json')) { | ||
try { | ||
const result = { data: await res.json() }; | ||
return result; | ||
} catch (e) { | ||
console.error('Parsing fetch response body as JSON failed!'); | ||
return Promise.reject(e); | ||
} | ||
} else { | ||
return { data: await res.text() }; | ||
} | ||
}; | ||
|
||
const makeRequest = async <T>(request: Request) => { | ||
try { | ||
const response = await makeBareRequest(request); | ||
|
||
if (isRedirected(response)) { | ||
if (response.url.includes('/cas/login')) { | ||
redirectToLogin(); | ||
} | ||
} | ||
// NOTE: oppijanumerorekisteri ohjautuu tässä omaan login-service-osoitteeseensa, josta tulee 406, mutta sen jälkeen pyynnöt toimii | ||
return data; | ||
}, | ||
async (error) => { | ||
console.log(error); | ||
if (isUnauthorized(error)) { | ||
const request = error.request; | ||
try { | ||
if (request?.url?.includes('/kouta-internal')) { | ||
return await retryWithLogin( | ||
request, | ||
configuration.koutaInternalLogin, | ||
); | ||
|
||
return responseToData(response); | ||
} catch (error: unknown) { | ||
if (error instanceof Response) { | ||
if (isUnauthenticated(error)) { | ||
try { | ||
if (request?.url?.includes('/kouta-internal')) { | ||
const resp = await retryWithLogin( | ||
request, | ||
configuration.koutaInternalLogin, | ||
); | ||
|
||
return responseToData(resp); | ||
} | ||
} catch (e) { | ||
return Promise.reject(e); | ||
} | ||
} catch (e) { | ||
console.error(`Retry with login failed for request: ${request?.url}`); | ||
} | ||
} | ||
return Promise.reject(error); | ||
} | ||
}; | ||
|
||
// Ei autentikaatiota, ohjataan login-sivulle! | ||
const loginUrl = new URL(configuration.loginUrl); | ||
loginUrl.searchParams.set('service', window.location.href); | ||
redirect(loginUrl.toString()); | ||
}, | ||
); | ||
export const client = { | ||
get: (url: string, options: RequestInit = {}) => | ||
makeRequest(new Request(url, { method: 'GET', ...options })), | ||
post: (url: string, options: RequestInit = {}) => | ||
makeRequest(new Request(url, { method: 'POST', ...options })), | ||
}; |
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters