Skip to content

Commit

Permalink
Yoshi Serverless Client (#2616)
Browse files Browse the repository at this point in the history
  • Loading branch information
yanivefraim committed Jun 23, 2020
1 parent 8663931 commit fd0a092
Show file tree
Hide file tree
Showing 5 changed files with 222 additions and 0 deletions.
24 changes: 24 additions & 0 deletions packages/yoshi-serverless-client/package.json
@@ -0,0 +1,24 @@
{
"name": "yoshi-serverless-client",
"version": "4.105.0",
"main": "build/index.js",
"sideEffects": false,
"keywords": [
"yoshi",
"server"
],
"author": "Yaniv Efraim",
"license": "ISC",
"dependencies": {
"@types/node-fetch": "1.6.9",
"@wix/headers": "^1.7.0",
"isomorphic-unfetch": "3.0.0",
"toastify-js": "1.8.0"
},
"peerDependencies": {
"yoshi-serverless": "^4.14.0"
},
"devDependencies": {
"yoshi-serverless": "4.106.0"
}
}
1 change: 1 addition & 0 deletions packages/yoshi-serverless-client/src/external-types.d.ts
@@ -0,0 +1 @@
declare module 'toastify-js';
97 changes: 97 additions & 0 deletions packages/yoshi-serverless-client/src/index.ts
@@ -0,0 +1,97 @@
import unfetch from 'isomorphic-unfetch';
import {
FunctionArgs,
FunctionResult,
UnpackPromise,
DSL,
RequestPayload,
} from 'yoshi-serverless/types';
import { createHeaders } from '@wix/headers';
import { joinUrls, showToast } from './utils';

type Options = {
baseUrl?: string;
};

export interface HttpClient {
request<Result extends FunctionResult, Args extends FunctionArgs>(
method: DSL<Result, Args>,
options?: { headers?: { [index: string]: string } },
): (...args: Args) => Promise<UnpackPromise<Result>>;
}

// https://github.com/developit/unfetch/issues/46
const fetch = unfetch;
const serverlessBase = process.env.YOSHI_SERVERLESS_BASE;

const defaultBaseUrl = serverlessBase
? serverlessBase
: `/_api/${process.env.PACKAGE_NAME}`;

export default class implements HttpClient {
private baseUrl: string;

constructor({ baseUrl = defaultBaseUrl }: Options = {}) {
this.baseUrl = baseUrl;
}

private getWixHeaders() {
const wixHeaders = createHeaders();
if (process.env.NODE_ENV === 'development') {
delete wixHeaders['x-xsrf-token'];
}
return wixHeaders as Record<string, string>;
}

request<Result extends FunctionResult, Args extends FunctionArgs>(
method: DSL<Result, Args>,
options?: { headers?: { [index: string]: string } },
): (...args: Args) => Promise<UnpackPromise<Result>> {
return async (...args: Args) => {
const url = joinUrls(this.baseUrl, '/_api_');
const { fileName, functionName } = method;
const body: RequestPayload = { fileName, functionName, args };

const res = await fetch(url, {
credentials: 'same-origin',
method: 'POST',
headers: {
'Content-Type': 'application/json',
// WixHeaders has ? for each key. Here, keys which are undefined will be filtered automatically
...this.getWixHeaders(),
...options?.headers,
},
body: JSON.stringify(body),
});

if (!res.ok) {
if (res.headers.get('content-type')?.includes('application/json')) {
const error = await res.json();
if (process.env.NODE_ENV !== 'production') {
if (process.env.browser) {
showToast(error, method, args, this.baseUrl);
}
console.error(error);
}
throw new Error(JSON.stringify(error));
} else {
const error = await res.text();
const errorMessage = `
Yoshi Server: the server returned a non JSON response.
This is probable due to an error in one of the middlewares before Yoshi Server.
${error}
`;
if (process.env.NODE_ENV !== 'production') {
console.error(error);
}

throw new Error(errorMessage);
}
}

const result = await res.json();

return result.payload;
};
}
}
82 changes: 82 additions & 0 deletions packages/yoshi-serverless-client/src/utils.ts
@@ -0,0 +1,82 @@
import unfetch from 'isomorphic-unfetch';
import { DSL, FunctionResult, FunctionArgs } from 'yoshi-serverless/types';
// https://github.com/developit/unfetch/issues/46
const fetch = unfetch;

export function joinUrls(baseUrl: string, relativeUrl: string) {
return baseUrl.replace(/\/+$/, '') + '/' + relativeUrl.replace(/^\/+/, '');
}

export function showToast<
Result extends FunctionResult,
Args extends FunctionArgs
>(error: any, method: DSL<Result, Args>, args: any, baseUrl: string) {
const Toastify = require('toastify-js');

createCss();

Toastify({
duration: 60000,
gravity: 'bottom',
position: 'right',
stopOnFocus: true,
close: true,
text: `😱😱😱 There was an error on your server function call. Click to open on your IDE<BR/>
File: <b style="color: blue;cursor: pointer;"><u>${method.fileName}</u></b>, method: <b>${method.functionName}</b>: <BR/>
Arguments: <b>${args}</b> <BR/>
Error: ${error.errors[0].message}
`,
className: 'popover',
async onClick() {
const [, file, line, col] = error.errors[0].stack.match(
/\/([/\w-_.]+\.[j|t]sx?):(\d*):(\d*)/,
);
await fetch(
baseUrl + `/_launchEditor_?file=${file}&line=${line}&col=${col}`,
{
credentials: 'same-origin',
method: 'GET',
},
);
},
}).showToast();
}

function createCss() {
const toastStyle = 'toast-style';
if (document.querySelector(`#${toastStyle}`)) {
return;
}
const css = document.createElement('style');
css.id = toastStyle;
css.textContent = `
.toast-close {
position: absolute;
top: 0px;
right: 0px;
margin: 5px;
cursor: pointer;
}
.popover {
position: absolute;
right: 20px;
padding: 10px 20px;
max-width: 500px;
background: #fff;
color: #000;
font: initial;
cursor: initial;
letter-spacing: initial;
text-shadow: initial;
text-transform: initial;
visibility: initial;
font-size: 12px;
line-height: 1.5;
align-items: center;
box-shadow: 0 11px 40px 0 rgba(0, 0, 0, 0.25), 0 2px 10px 0 rgba(0, 0, 0, 0.12);
transition: bottom 1.5s ease;
}
`;

document.body.appendChild(css);
}
18 changes: 18 additions & 0 deletions packages/yoshi-serverless-client/tsconfig.json
@@ -0,0 +1,18 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"rootDir": "src",
"outDir": "build",
"target": "es5"
},
"include": [
"src/**/*",
"src/**/*.json"
],
"files": [
"./src/external-types.d.ts",
],
"references": [
{ "path": "../yoshi-serverless" }
]
}

0 comments on commit fd0a092

Please sign in to comment.