Skip to content

Commit

Permalink
Merge pull request #5 from Opetushallitus/http-client-pure-fetch
Browse files Browse the repository at this point in the history
Http client pure fetch
  • Loading branch information
pretseli authored Apr 19, 2024
2 parents 76b4979 + 2b6ebf0 commit 9fe891f
Show file tree
Hide file tree
Showing 6 changed files with 145 additions and 134 deletions.
99 changes: 54 additions & 45 deletions .github/workflows/build.yml
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
30 changes: 2 additions & 28 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 2 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,7 @@
"cookie": "^0.6.0",
"next": "^14.2.0",
"react": "^18",
"react-dom": "^18",
"xior": "^0.3.11"
"react-dom": "^18"
},
"devDependencies": {
"@playwright/test": "^1.42.0",
Expand All @@ -45,8 +44,8 @@
"http-proxy-middleware": "^3.0.0",
"husky": "^9.0.11",
"jsdom": "^24.0.0",
"postcss": "^8",
"lint-staged": "^15.2.2",
"postcss": "^8",
"prettier": "^3.2.5",
"prettier-eslint-cli": "^8.0.1",
"typescript": "^5",
Expand Down
144 changes: 86 additions & 58 deletions src/app/lib/http-client.ts
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.
1 change: 1 addition & 0 deletions tests/e2e/playwright.setup.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export default async function playwrightSetup() {
response.end();
return;
} else if (request.url.includes(`henkilo/current/asiointiKieli`)) {
response.setHeader('Content-Type', 'text/plain');
response.write('fi');
response.end();
return;
Expand Down

0 comments on commit 9fe891f

Please sign in to comment.