diff --git a/examples/session-and-cookie/backend/.gitignore b/examples/session-and-cookie/backend/.gitignore new file mode 100644 index 0000000..d84f0da --- /dev/null +++ b/examples/session-and-cookie/backend/.gitignore @@ -0,0 +1,3 @@ +node_modules +dist +.DS_STORE \ No newline at end of file diff --git a/examples/session-and-cookie/backend/.vscode/launch.json b/examples/session-and-cookie/backend/.vscode/launch.json new file mode 100644 index 0000000..9ba4218 --- /dev/null +++ b/examples/session-and-cookie/backend/.vscode/launch.json @@ -0,0 +1,30 @@ +{ + "configurations": [ + { + "type": "node", + "request": "launch", + "name": "mocha current file", + "program": "${workspaceFolder}/node_modules/mocha/bin/_mocha", + "args": [ + "${file}" + ], + "internalConsoleOptions": "openOnSessionStart", + "cwd": "${workspaceFolder}" + }, + { + "type": "node", + "request": "launch", + "name": "ts-node current file", + "protocol": "inspector", + "args": [ + "${relativeFile}" + ], + "cwd": "${workspaceRoot}", + "runtimeArgs": [ + "-r", + "ts-node/register" + ], + "internalConsoleOptions": "openOnSessionStart" + } + ] +} \ No newline at end of file diff --git a/examples/session-and-cookie/backend/.vscode/settings.json b/examples/session-and-cookie/backend/.vscode/settings.json new file mode 100644 index 0000000..00ad71f --- /dev/null +++ b/examples/session-and-cookie/backend/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "typescript.tsdk": "node_modules\\typescript\\lib" +} \ No newline at end of file diff --git a/examples/session-and-cookie/backend/README.md b/examples/session-and-cookie/backend/README.md new file mode 100644 index 0000000..7492c22 --- /dev/null +++ b/examples/session-and-cookie/backend/README.md @@ -0,0 +1,35 @@ +# TSRPC Server + +## Run +### Local Dev Server +``` +npm run dev +``` + + + +### Build +``` +npm run build +``` + +--- + +## Files +### Generate ServiceProto +``` +npm run proto +``` + +### Generate API templates +``` +npm run api +``` + +### Sync shared code to client + +``` +npm run sync +``` + +> If you chose symlink when using `create-tsrpc-app`, it would re-create the symlink instead of copy files. diff --git a/examples/session-and-cookie/backend/package.json b/examples/session-and-cookie/backend/package.json new file mode 100644 index 0000000..bb12229 --- /dev/null +++ b/examples/session-and-cookie/backend/package.json @@ -0,0 +1,23 @@ +{ + "name": "session-and-cookie-backend", + "version": "0.1.0", + "main": "index.js", + "private": true, + "scripts": { + "proto": "tsrpc proto -i src/shared/protocols -o src/shared/protocols/serviceProto.ts", + "sync": "tsrpc sync --from src/shared --to ../frontend/src/shared", + "api": "tsrpc api -i src/shared/protocols/serviceProto.ts -o src/api", + "dev": "onchange \"src/**/*.ts\" -i -k -- ts-node \"src/index.ts\"", + "build": "tsrpc build" + }, + "devDependencies": { + "@types/node": "^15.12.2", + "onchange": "^7.1.0", + "ts-node": "^9.1.1", + "tsrpc-cli": "^2.0.1-dev.11", + "typescript": "^4.3.2" + }, + "dependencies": { + "tsrpc": "^3.0.0-dev.19" + } +} diff --git a/examples/session-and-cookie/backend/src/api/ApiClear.ts b/examples/session-and-cookie/backend/src/api/ApiClear.ts new file mode 100644 index 0000000..d0b8e9d --- /dev/null +++ b/examples/session-and-cookie/backend/src/api/ApiClear.ts @@ -0,0 +1,9 @@ +import { ApiCall } from "tsrpc"; +import { ReqClear, ResClear } from "../shared/protocols/PtlClear"; + +export async function ApiClear(call: ApiCall) { + call.succ({ + __cookie: {}, + __session: {} + }) +} \ No newline at end of file diff --git a/examples/session-and-cookie/backend/src/api/ApiGetCookie.ts b/examples/session-and-cookie/backend/src/api/ApiGetCookie.ts new file mode 100644 index 0000000..63f166a --- /dev/null +++ b/examples/session-and-cookie/backend/src/api/ApiGetCookie.ts @@ -0,0 +1,13 @@ +import { ApiCall } from "tsrpc"; +import { ReqGetCookie, ResGetCookie } from "../shared/protocols/PtlGetCookie"; + +let times = 0; + +export async function ApiGetCookie(call: ApiCall) { + call.succ({ + __cookie: { + ...call.req.__cookie, + testCookie: 'Cookie ' + (++times), + } + }) +} \ No newline at end of file diff --git a/examples/session-and-cookie/backend/src/api/ApiGetSession.ts b/examples/session-and-cookie/backend/src/api/ApiGetSession.ts new file mode 100644 index 0000000..4e5c52f --- /dev/null +++ b/examples/session-and-cookie/backend/src/api/ApiGetSession.ts @@ -0,0 +1,13 @@ +import { ApiCall } from "tsrpc"; +import { ReqGetSession, ResGetSession } from "../shared/protocols/PtlGetSession"; + +let times = 0; + +export async function ApiGetSession(call: ApiCall) { + call.succ({ + __session: { + ...call.req.__session, + testSession: 'Session ' + (++times) + } + }) +} \ No newline at end of file diff --git a/examples/session-and-cookie/backend/src/api/ApiTest.ts b/examples/session-and-cookie/backend/src/api/ApiTest.ts new file mode 100644 index 0000000..4473445 --- /dev/null +++ b/examples/session-and-cookie/backend/src/api/ApiTest.ts @@ -0,0 +1,6 @@ +import { ApiCall } from "tsrpc"; +import { ReqTest, ResTest } from "../shared/protocols/PtlTest"; + +export async function ApiTest(call: ApiCall) { + call.succ({}); +} \ No newline at end of file diff --git a/examples/session-and-cookie/backend/src/index.ts b/examples/session-and-cookie/backend/src/index.ts new file mode 100644 index 0000000..5a4dbc9 --- /dev/null +++ b/examples/session-and-cookie/backend/src/index.ts @@ -0,0 +1,38 @@ +import * as path from "path"; +import { HttpServer } from "tsrpc"; +import { BaseRequest, BaseResponse } from "./shared/protocols/base"; +import { serviceProto } from "./shared/protocols/serviceProto"; + +// Create the Server +const server = new HttpServer(serviceProto, { + port: 3000, + cors: '*' +}); + +// Return session and cookie as they are, except reset by res +server.flows.preApiReturnFlow.push(v => { + if (v.return.isSucc) { + let req = v.call.req as BaseRequest; + let res = v.return.res as BaseResponse; + res.__session = res.__session ?? req.__session; + res.__cookie = res.__cookie ?? req.__cookie; + } + return v; +}); + +// Entry function +async function main() { + // Auto implement APIs + await server.autoImplementApi(path.resolve(__dirname, 'api')); + + // TODO + // Prepare something... (e.g. connect the db) + + await server.start(); +}; + +main().catch(e => { + // Exit if any error during the startup + server.logger.error(e); + process.exit(-1); +}); \ No newline at end of file diff --git a/examples/session-and-cookie/backend/src/shared/protocols/PtlClear.ts b/examples/session-and-cookie/backend/src/shared/protocols/PtlClear.ts new file mode 100644 index 0000000..bd4af9b --- /dev/null +++ b/examples/session-and-cookie/backend/src/shared/protocols/PtlClear.ts @@ -0,0 +1,9 @@ +import { BaseRequest, BaseResponse } from './base'; + +export interface ReqClear extends BaseRequest { + +} + +export interface ResClear extends BaseResponse { + +} \ No newline at end of file diff --git a/examples/session-and-cookie/backend/src/shared/protocols/PtlGetCookie.ts b/examples/session-and-cookie/backend/src/shared/protocols/PtlGetCookie.ts new file mode 100644 index 0000000..e77630b --- /dev/null +++ b/examples/session-and-cookie/backend/src/shared/protocols/PtlGetCookie.ts @@ -0,0 +1,9 @@ +import { BaseRequest, BaseResponse } from './base' + +export interface ReqGetCookie extends BaseRequest { + +} + +export interface ResGetCookie extends BaseResponse { + +} \ No newline at end of file diff --git a/examples/session-and-cookie/backend/src/shared/protocols/PtlGetSession.ts b/examples/session-and-cookie/backend/src/shared/protocols/PtlGetSession.ts new file mode 100644 index 0000000..f00f4e6 --- /dev/null +++ b/examples/session-and-cookie/backend/src/shared/protocols/PtlGetSession.ts @@ -0,0 +1,9 @@ +import { BaseRequest, BaseResponse } from './base' + +export interface ReqGetSession extends BaseRequest { + +} + +export interface ResGetSession extends BaseResponse { + +} \ No newline at end of file diff --git a/examples/session-and-cookie/backend/src/shared/protocols/PtlTest.ts b/examples/session-and-cookie/backend/src/shared/protocols/PtlTest.ts new file mode 100644 index 0000000..d3e5367 --- /dev/null +++ b/examples/session-and-cookie/backend/src/shared/protocols/PtlTest.ts @@ -0,0 +1,9 @@ +import { BaseRequest, BaseResponse } from './base' + +export interface ReqTest extends BaseRequest { + +} + +export interface ResTest extends BaseResponse { + +} \ No newline at end of file diff --git a/examples/session-and-cookie/backend/src/shared/protocols/base.ts b/examples/session-and-cookie/backend/src/shared/protocols/base.ts new file mode 100644 index 0000000..12991a0 --- /dev/null +++ b/examples/session-and-cookie/backend/src/shared/protocols/base.ts @@ -0,0 +1,9 @@ +export interface BaseRequest { + __session?: { [key: string]: any }; + __cookie?: { [key: string]: any }; +} + +export interface BaseResponse { + __session?: { [key: string]: any }; + __cookie?: { [key: string]: any }; +} \ No newline at end of file diff --git a/examples/session-and-cookie/backend/src/shared/protocols/serviceProto.ts b/examples/session-and-cookie/backend/src/shared/protocols/serviceProto.ts new file mode 100644 index 0000000..c509617 --- /dev/null +++ b/examples/session-and-cookie/backend/src/shared/protocols/serviceProto.ts @@ -0,0 +1,219 @@ +import { ServiceProto } from 'tsrpc-proto'; +import { ReqClear, ResClear } from './PtlClear'; +import { ReqGetCookie, ResGetCookie } from './PtlGetCookie'; +import { ReqGetSession, ResGetSession } from './PtlGetSession'; +import { ReqTest, ResTest } from './PtlTest'; + +export interface ServiceType { + api: { + "Clear": { + req: ReqClear, + res: ResClear + }, + "GetCookie": { + req: ReqGetCookie, + res: ResGetCookie + }, + "GetSession": { + req: ReqGetSession, + res: ResGetSession + }, + "Test": { + req: ReqTest, + res: ResTest + } + }, + msg: { + + } +} + +export const serviceProto: ServiceProto = { + "version": 3, + "services": [ + { + "id": 3, + "name": "Clear", + "type": "api" + }, + { + "id": 0, + "name": "GetCookie", + "type": "api" + }, + { + "id": 1, + "name": "GetSession", + "type": "api" + }, + { + "id": 2, + "name": "Test", + "type": "api" + } + ], + "types": { + "PtlClear/ReqClear": { + "type": "Interface", + "extends": [ + { + "id": 0, + "type": { + "type": "Reference", + "target": "base/BaseRequest" + } + } + ] + }, + "base/BaseRequest": { + "type": "Interface", + "properties": [ + { + "id": 0, + "name": "__session", + "type": { + "type": "Interface", + "indexSignature": { + "keyType": "String", + "type": { + "type": "Any" + } + } + }, + "optional": true + }, + { + "id": 1, + "name": "__cookie", + "type": { + "type": "Interface", + "indexSignature": { + "keyType": "String", + "type": { + "type": "Any" + } + } + }, + "optional": true + } + ] + }, + "PtlClear/ResClear": { + "type": "Interface", + "extends": [ + { + "id": 0, + "type": { + "type": "Reference", + "target": "base/BaseResponse" + } + } + ] + }, + "base/BaseResponse": { + "type": "Interface", + "properties": [ + { + "id": 0, + "name": "__session", + "type": { + "type": "Interface", + "indexSignature": { + "keyType": "String", + "type": { + "type": "Any" + } + } + }, + "optional": true + }, + { + "id": 1, + "name": "__cookie", + "type": { + "type": "Interface", + "indexSignature": { + "keyType": "String", + "type": { + "type": "Any" + } + } + }, + "optional": true + } + ] + }, + "PtlGetCookie/ReqGetCookie": { + "type": "Interface", + "extends": [ + { + "id": 0, + "type": { + "type": "Reference", + "target": "base/BaseRequest" + } + } + ] + }, + "PtlGetCookie/ResGetCookie": { + "type": "Interface", + "extends": [ + { + "id": 0, + "type": { + "type": "Reference", + "target": "base/BaseResponse" + } + } + ] + }, + "PtlGetSession/ReqGetSession": { + "type": "Interface", + "extends": [ + { + "id": 0, + "type": { + "type": "Reference", + "target": "base/BaseRequest" + } + } + ] + }, + "PtlGetSession/ResGetSession": { + "type": "Interface", + "extends": [ + { + "id": 0, + "type": { + "type": "Reference", + "target": "base/BaseResponse" + } + } + ] + }, + "PtlTest/ReqTest": { + "type": "Interface", + "extends": [ + { + "id": 0, + "type": { + "type": "Reference", + "target": "base/BaseRequest" + } + } + ] + }, + "PtlTest/ResTest": { + "type": "Interface", + "extends": [ + { + "id": 0, + "type": { + "type": "Reference", + "target": "base/BaseResponse" + } + } + ] + } + } +}; \ No newline at end of file diff --git a/examples/session-and-cookie/backend/tsconfig.json b/examples/session-and-cookie/backend/tsconfig.json new file mode 100644 index 0000000..d18498f --- /dev/null +++ b/examples/session-and-cookie/backend/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "lib": [ + "es2018" + ], + "module": "commonjs", + "target": "es2018", + "outDir": "dist", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "moduleResolution": "node" + }, + "include": [ + "src" + ] +} \ No newline at end of file diff --git a/examples/session-and-cookie/frontend/.gitignore b/examples/session-and-cookie/frontend/.gitignore new file mode 100644 index 0000000..ae16b5a --- /dev/null +++ b/examples/session-and-cookie/frontend/.gitignore @@ -0,0 +1,3 @@ +.DS_STORE +node_modules +dist \ No newline at end of file diff --git a/examples/session-and-cookie/frontend/package.json b/examples/session-and-cookie/frontend/package.json new file mode 100644 index 0000000..7c9a0d3 --- /dev/null +++ b/examples/session-and-cookie/frontend/package.json @@ -0,0 +1,21 @@ +{ + "name": "session-and-cookie-frontend", + "version": "0.1.0", + "private": true, + "scripts": { + "dev": "webpack serve --mode=development --open", + "build": "webpack --mode=production" + }, + "devDependencies": { + "copy-webpack-plugin": "^9.0.0", + "html-webpack-plugin": "^5.3.1", + "ts-loader": "^9.2.3", + "typescript": "^4.3.2", + "webpack": "^5.38.1", + "webpack-cli": "^4.7.2", + "webpack-dev-server": "^3.11.2" + }, + "dependencies": { + "tsrpc-browser": "^3.0.0-dev.15" + } +} diff --git a/examples/session-and-cookie/frontend/public/favicon.ico b/examples/session-and-cookie/frontend/public/favicon.ico new file mode 100644 index 0000000..e69de29 diff --git a/examples/session-and-cookie/frontend/public/index.css b/examples/session-and-cookie/frontend/public/index.css new file mode 100644 index 0000000..7b8b068 --- /dev/null +++ b/examples/session-and-cookie/frontend/public/index.css @@ -0,0 +1,46 @@ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +body { + text-align: center; +} + +body>* { + margin: 20px auto; +} + +.buttons { + width: 450px; + font-size: 0px; +} + +.buttons>button { + display: inline-block; + font-size: 16px; + margin: 10px; + padding: 20px; + width: calc(50% - 20px); + background: #3178c6; + color: #fff; + cursor: pointer; + border-radius: 4px; + border: none; +} + +.buttons>button:hover { + background: #5ea8e9; +} + +pre { + background: #333; + width: 450px; + padding: 20px; + min-height: 100px; + font-size: 14px; + color: #fff; + text-align: left; + border-radius: 4px; +} \ No newline at end of file diff --git a/examples/session-and-cookie/frontend/public/index.html b/examples/session-and-cookie/frontend/public/index.html new file mode 100644 index 0000000..24ee9f8 --- /dev/null +++ b/examples/session-and-cookie/frontend/public/index.html @@ -0,0 +1,29 @@ + + + + + + + + TSRPC Example + + + + +

Session and Cookie

+ +
+ + + + +
+ +

API Request

+

+
+    

API Return

+

+
+
+
\ No newline at end of file
diff --git a/examples/session-and-cookie/frontend/src/index.ts b/examples/session-and-cookie/frontend/src/index.ts
new file mode 100644
index 0000000..5179ae1
--- /dev/null
+++ b/examples/session-and-cookie/frontend/src/index.ts
@@ -0,0 +1,31 @@
+import { HttpClient } from 'tsrpc-browser';
+import { enableSessionAndCookie } from './models/enableSessionAndCookie';
+import { showReqAndRes } from './models/showReqAndRes';
+import { serviceProto } from './shared/protocols/serviceProto';
+
+// Create Client
+let client = new HttpClient(serviceProto, {
+    server: 'http://127.0.0.1:3000',
+    logger: console
+});
+
+// Session and Cookie
+enableSessionAndCookie(client);
+
+// Show Req and Return to View
+showReqAndRes(client);
+
+// Bind Events
+const $ = document.querySelector.bind(document) as (v: string) => HTMLElement;
+$('#btnTest').onclick = () => {
+    client.callApi('Test', {});
+};
+$('#btnClear').onclick = () => {
+    client.callApi('Clear', {});
+};
+$('#btnGetCookie').onclick = () => {
+    client.callApi('GetCookie', {});
+};
+$('#btnGetSession').onclick = () => {
+    client.callApi('GetSession', {});
+};
\ No newline at end of file
diff --git a/examples/session-and-cookie/frontend/src/models/enableSessionAndCookie.ts b/examples/session-and-cookie/frontend/src/models/enableSessionAndCookie.ts
new file mode 100644
index 0000000..0ed7f5c
--- /dev/null
+++ b/examples/session-and-cookie/frontend/src/models/enableSessionAndCookie.ts
@@ -0,0 +1,35 @@
+import { HttpClient } from "tsrpc-browser";
+
+const CookieStorageKey = '__MY_COOKIE__';
+const SessionStorageKey = '__MY_SESSION__';
+
+export function enableSessionAndCookie(client: HttpClient) {
+    // Send
+    client.flows.preCallApiFlow.push(v => {
+        // Auto get __cookie from localStorage
+        let cookieStr = localStorage.getItem(CookieStorageKey);
+        v.req.__cookie = cookieStr ? JSON.parse(cookieStr) : undefined;
+
+        // Auto get __session from sessionStorage
+        let sessionStr = sessionStorage.getItem(SessionStorageKey);
+        v.req.__session = sessionStr ? JSON.parse(sessionStr) : undefined;
+
+        return v;
+    })
+
+    // Return
+    client.flows.preApiReturnFlow.push(v => {
+        if (v.return.isSucc) {
+            // Auto set __cookie to localStorage
+            if (v.return.res.__cookie) {
+                localStorage.setItem(CookieStorageKey, JSON.stringify(v.return.res.__cookie))
+            }
+            // Auto set __session to sessionStorage
+            if (v.return.res.__session) {
+                sessionStorage.setItem(SessionStorageKey, JSON.stringify(v.return.res.__session))
+            }
+        }
+
+        return v;
+    })
+}
\ No newline at end of file
diff --git a/examples/session-and-cookie/frontend/src/models/showReqAndRes.ts b/examples/session-and-cookie/frontend/src/models/showReqAndRes.ts
new file mode 100644
index 0000000..f670996
--- /dev/null
+++ b/examples/session-and-cookie/frontend/src/models/showReqAndRes.ts
@@ -0,0 +1,10 @@
+import { HttpClient } from "tsrpc-browser";
+
+export function showReqAndRes(client: HttpClient) {
+    // Send
+    client.flows.postApiReturnFlow.push(v => {
+        (document.querySelector('.apiReq') as HTMLElement).innerText = 'callApi: ' + v.apiName + '\n\n' + JSON.stringify(v.req, null, 2);
+        (document.querySelector('.apiReturn') as HTMLElement).innerText = JSON.stringify(v.return, null, 2);
+        return v;
+    });
+}
\ No newline at end of file
diff --git a/examples/session-and-cookie/frontend/src/shared/protocols/PtlClear.ts b/examples/session-and-cookie/frontend/src/shared/protocols/PtlClear.ts
new file mode 100644
index 0000000..bd4af9b
--- /dev/null
+++ b/examples/session-and-cookie/frontend/src/shared/protocols/PtlClear.ts
@@ -0,0 +1,9 @@
+import { BaseRequest, BaseResponse } from './base';
+
+export interface ReqClear extends BaseRequest {
+
+}
+
+export interface ResClear extends BaseResponse {
+
+}
\ No newline at end of file
diff --git a/examples/session-and-cookie/frontend/src/shared/protocols/PtlGetCookie.ts b/examples/session-and-cookie/frontend/src/shared/protocols/PtlGetCookie.ts
new file mode 100644
index 0000000..e77630b
--- /dev/null
+++ b/examples/session-and-cookie/frontend/src/shared/protocols/PtlGetCookie.ts
@@ -0,0 +1,9 @@
+import { BaseRequest, BaseResponse } from './base'
+
+export interface ReqGetCookie extends BaseRequest {
+    
+}
+
+export interface ResGetCookie extends BaseResponse {
+    
+}
\ No newline at end of file
diff --git a/examples/session-and-cookie/frontend/src/shared/protocols/PtlGetSession.ts b/examples/session-and-cookie/frontend/src/shared/protocols/PtlGetSession.ts
new file mode 100644
index 0000000..f00f4e6
--- /dev/null
+++ b/examples/session-and-cookie/frontend/src/shared/protocols/PtlGetSession.ts
@@ -0,0 +1,9 @@
+import { BaseRequest, BaseResponse } from './base'
+
+export interface ReqGetSession extends BaseRequest {
+
+}
+
+export interface ResGetSession extends BaseResponse {
+
+}
\ No newline at end of file
diff --git a/examples/session-and-cookie/frontend/src/shared/protocols/PtlTest.ts b/examples/session-and-cookie/frontend/src/shared/protocols/PtlTest.ts
new file mode 100644
index 0000000..d3e5367
--- /dev/null
+++ b/examples/session-and-cookie/frontend/src/shared/protocols/PtlTest.ts
@@ -0,0 +1,9 @@
+import { BaseRequest, BaseResponse } from './base'
+
+export interface ReqTest extends BaseRequest {
+
+}
+
+export interface ResTest extends BaseResponse {
+
+}
\ No newline at end of file
diff --git a/examples/session-and-cookie/frontend/src/shared/protocols/base.ts b/examples/session-and-cookie/frontend/src/shared/protocols/base.ts
new file mode 100644
index 0000000..12991a0
--- /dev/null
+++ b/examples/session-and-cookie/frontend/src/shared/protocols/base.ts
@@ -0,0 +1,9 @@
+export interface BaseRequest {
+    __session?: { [key: string]: any };
+    __cookie?: { [key: string]: any };
+}
+
+export interface BaseResponse {
+    __session?: { [key: string]: any };
+    __cookie?: { [key: string]: any };
+}
\ No newline at end of file
diff --git a/examples/session-and-cookie/frontend/src/shared/protocols/serviceProto.ts b/examples/session-and-cookie/frontend/src/shared/protocols/serviceProto.ts
new file mode 100644
index 0000000..c509617
--- /dev/null
+++ b/examples/session-and-cookie/frontend/src/shared/protocols/serviceProto.ts
@@ -0,0 +1,219 @@
+import { ServiceProto } from 'tsrpc-proto';
+import { ReqClear, ResClear } from './PtlClear';
+import { ReqGetCookie, ResGetCookie } from './PtlGetCookie';
+import { ReqGetSession, ResGetSession } from './PtlGetSession';
+import { ReqTest, ResTest } from './PtlTest';
+
+export interface ServiceType {
+    api: {
+        "Clear": {
+            req: ReqClear,
+            res: ResClear
+        },
+        "GetCookie": {
+            req: ReqGetCookie,
+            res: ResGetCookie
+        },
+        "GetSession": {
+            req: ReqGetSession,
+            res: ResGetSession
+        },
+        "Test": {
+            req: ReqTest,
+            res: ResTest
+        }
+    },
+    msg: {
+
+    }
+}
+
+export const serviceProto: ServiceProto = {
+    "version": 3,
+    "services": [
+        {
+            "id": 3,
+            "name": "Clear",
+            "type": "api"
+        },
+        {
+            "id": 0,
+            "name": "GetCookie",
+            "type": "api"
+        },
+        {
+            "id": 1,
+            "name": "GetSession",
+            "type": "api"
+        },
+        {
+            "id": 2,
+            "name": "Test",
+            "type": "api"
+        }
+    ],
+    "types": {
+        "PtlClear/ReqClear": {
+            "type": "Interface",
+            "extends": [
+                {
+                    "id": 0,
+                    "type": {
+                        "type": "Reference",
+                        "target": "base/BaseRequest"
+                    }
+                }
+            ]
+        },
+        "base/BaseRequest": {
+            "type": "Interface",
+            "properties": [
+                {
+                    "id": 0,
+                    "name": "__session",
+                    "type": {
+                        "type": "Interface",
+                        "indexSignature": {
+                            "keyType": "String",
+                            "type": {
+                                "type": "Any"
+                            }
+                        }
+                    },
+                    "optional": true
+                },
+                {
+                    "id": 1,
+                    "name": "__cookie",
+                    "type": {
+                        "type": "Interface",
+                        "indexSignature": {
+                            "keyType": "String",
+                            "type": {
+                                "type": "Any"
+                            }
+                        }
+                    },
+                    "optional": true
+                }
+            ]
+        },
+        "PtlClear/ResClear": {
+            "type": "Interface",
+            "extends": [
+                {
+                    "id": 0,
+                    "type": {
+                        "type": "Reference",
+                        "target": "base/BaseResponse"
+                    }
+                }
+            ]
+        },
+        "base/BaseResponse": {
+            "type": "Interface",
+            "properties": [
+                {
+                    "id": 0,
+                    "name": "__session",
+                    "type": {
+                        "type": "Interface",
+                        "indexSignature": {
+                            "keyType": "String",
+                            "type": {
+                                "type": "Any"
+                            }
+                        }
+                    },
+                    "optional": true
+                },
+                {
+                    "id": 1,
+                    "name": "__cookie",
+                    "type": {
+                        "type": "Interface",
+                        "indexSignature": {
+                            "keyType": "String",
+                            "type": {
+                                "type": "Any"
+                            }
+                        }
+                    },
+                    "optional": true
+                }
+            ]
+        },
+        "PtlGetCookie/ReqGetCookie": {
+            "type": "Interface",
+            "extends": [
+                {
+                    "id": 0,
+                    "type": {
+                        "type": "Reference",
+                        "target": "base/BaseRequest"
+                    }
+                }
+            ]
+        },
+        "PtlGetCookie/ResGetCookie": {
+            "type": "Interface",
+            "extends": [
+                {
+                    "id": 0,
+                    "type": {
+                        "type": "Reference",
+                        "target": "base/BaseResponse"
+                    }
+                }
+            ]
+        },
+        "PtlGetSession/ReqGetSession": {
+            "type": "Interface",
+            "extends": [
+                {
+                    "id": 0,
+                    "type": {
+                        "type": "Reference",
+                        "target": "base/BaseRequest"
+                    }
+                }
+            ]
+        },
+        "PtlGetSession/ResGetSession": {
+            "type": "Interface",
+            "extends": [
+                {
+                    "id": 0,
+                    "type": {
+                        "type": "Reference",
+                        "target": "base/BaseResponse"
+                    }
+                }
+            ]
+        },
+        "PtlTest/ReqTest": {
+            "type": "Interface",
+            "extends": [
+                {
+                    "id": 0,
+                    "type": {
+                        "type": "Reference",
+                        "target": "base/BaseRequest"
+                    }
+                }
+            ]
+        },
+        "PtlTest/ResTest": {
+            "type": "Interface",
+            "extends": [
+                {
+                    "id": 0,
+                    "type": {
+                        "type": "Reference",
+                        "target": "base/BaseResponse"
+                    }
+                }
+            ]
+        }
+    }
+};
\ No newline at end of file
diff --git a/examples/session-and-cookie/frontend/tsconfig.json b/examples/session-and-cookie/frontend/tsconfig.json
new file mode 100644
index 0000000..7517bf5
--- /dev/null
+++ b/examples/session-and-cookie/frontend/tsconfig.json
@@ -0,0 +1,23 @@
+{
+  "compilerOptions": {
+    "lib": [
+      "dom",
+      "es2018"
+    ],
+    "module": "esnext",
+    "target": "es2018",
+    "allowSyntheticDefaultImports": true,
+    "esModuleInterop": true,
+    "forceConsistentCasingInFileNames": true,
+    "moduleResolution": "node",
+    "outDir": "dist",
+    "skipLibCheck": true,
+    "strict": true,
+    "jsx": "react-jsx",
+    "sourceMap": true,
+    "isolatedModules": true
+  },
+  "include": [
+    "src"
+  ]
+}
\ No newline at end of file
diff --git a/examples/session-and-cookie/frontend/webpack.config.js b/examples/session-and-cookie/frontend/webpack.config.js
new file mode 100644
index 0000000..062d1a4
--- /dev/null
+++ b/examples/session-and-cookie/frontend/webpack.config.js
@@ -0,0 +1,58 @@
+const webpack = require('webpack');
+const path = require('path');
+const HtmlWebpackPlugin = require('html-webpack-plugin');
+const CopyWebpackPlugin = require('copy-webpack-plugin');
+
+const isProduction = process.argv.indexOf('--mode=production') > -1;
+
+module.exports = {
+    entry: './src/index.ts',
+    output: {
+        filename: 'bundle.[contenthash].js',
+        path: path.resolve(__dirname, 'dist'),
+        clean: true
+    },
+    resolve: {
+        extensions: ['.ts', '.tsx', '.js']
+    },
+    module: {
+        rules: [
+            {
+                test: /\.tsx?$/,
+                use: [{
+                    loader: 'ts-loader',
+                    options: {
+                        compilerOptions: isProduction ? {
+                            "lib": [
+                                "dom",
+                                "es2015.promise"
+                            ],
+                            "target": "es5",
+                        } : undefined
+                    }
+                }],
+                exclude: /node_modules/
+            },
+        ]
+    },
+    plugins: [
+        // Copy "public" to "dist"
+        new CopyWebpackPlugin({
+            patterns: [{
+                from: 'public',
+                to: '.',
+                toType: 'dir',
+                globOptions: {
+                    gitignore: true,
+                    ignore: [path.resolve(__dirname, 'public/index.html').replace(/\\/g, '/')]
+                },
+                noErrorOnMissing: true
+            }]
+        }),
+        // Auto add