diff --git a/.gitignore b/.gitignore index 7d1da57..f6e643f 100644 --- a/.gitignore +++ b/.gitignore @@ -32,6 +32,7 @@ dist-ssr # lock *-lock.yarl +*-lock.yaml *-lock.json apps/aelf-template/.next diff --git a/apps/aelf-template/.gitignore b/apps/aelf-template/.gitignore index 01976c8..ed7b311 100644 --- a/apps/aelf-template/.gitignore +++ b/apps/aelf-template/.gitignore @@ -29,6 +29,7 @@ yarn-error.log* # local env files .env*.local +.env # vercel .vercel @@ -39,3 +40,9 @@ next-env.d.ts # Sentry Config File .env.sentry-build-plugin + +# npm pack local test +package + +# git ls-files > filelist.txt +filelist.txt diff --git a/apps/aelf-template/appsettings.ts b/apps/aelf-template/appsettings.ts index 7bb4610..07f8303 100644 --- a/apps/aelf-template/appsettings.ts +++ b/apps/aelf-template/appsettings.ts @@ -3,7 +3,7 @@ export const APP_SETTINGS: IAppSettings = { "serviceName": "create-aelf-dapp", "serviceVersion": "v1.0", // https://opentelemetry.io/docs/languages/sdk-configuration/otlp-exporter/ - "collectorEndpoint": "http://localhost:8093/v1/traces", + "collectorEndpoint": "/v1/traces", "tracerName": "create-aelf-dapp-tracer", ignoreUrls: [ /localhost:8093\/sockjs-node/, diff --git a/apps/aelf-template/next.config.mjs b/apps/aelf-template/next.config.mjs index e16c031..577c721 100644 --- a/apps/aelf-template/next.config.mjs +++ b/apps/aelf-template/next.config.mjs @@ -35,4 +35,4 @@ disableLogger: true, // https://docs.sentry.io/product/crons/ // https://vercel.com/docs/cron-jobs automaticVercelMonitors: true, -}); \ No newline at end of file +}); diff --git a/apps/aelf-template/nginx.template.md b/apps/aelf-template/nginx.template.md new file mode 100644 index 0000000..c086965 --- /dev/null +++ b/apps/aelf-template/nginx.template.md @@ -0,0 +1,83 @@ +# 1. Prepare +```bash +brew install nginx + +# turn to the directory of nginx +mkdir https +openssl req -nodes -new -x509 -keyout server.key -out server.crt +``` + +# 2. Config +```nginx +server { + listen 443 ssl; + server_name localhost; + + ssl_certificate /opt/homebrew/etc/nginx/https/server.crt; + ssl_certificate_key /opt/homebrew/etc/nginx/https/server.key; + + # template + location / { + proxy_pass http://localhost:3005; + proxy_ssl_verify off; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + + # https://github.com/AElfProject/backend-templates + location /v1/token { + proxy_pass https://localhost:44376/token; + proxy_ssl_verify off; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + + # https://github.com/AElfProject/backend-templates + location /v1/api { + proxy_set_header host $host; + proxy_set_header X-real-ip $remote_addr; + proxy_set_header X-forward-for $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_ssl_verify off; + proxy_pass https://localhost:44376/api; + } + + # https://github.com/AElfProject/aelf-observability-setup + location /v1/traces { + proxy_set_header host $host; + proxy_set_header X-real-ip $remote_addr; + proxy_set_header X-forward-for $proxy_add_x_forwarded_for; + proxy_pass http://127.0.0.1:4316/v1/traces; + } + + # packages/server-socket-io + location /socket-io/ { + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header Host $host; + + proxy_pass http://localhost:3006; + + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + } + + # packages/server-chaingpt + location /api/chaingpt { + proxy_set_header host $host; + proxy_set_header X-real-ip $remote_addr; + proxy_set_header X-forward-for $proxy_add_x_forwarded_for; + proxy_pass http://127.0.0.1:3007/chaingpt; + } +} +``` + +# 3. Run +```bash +nginx -t +nginx -s reload +``` diff --git a/apps/aelf-template/package.json b/apps/aelf-template/package.json index 9110933..6ce4a68 100644 --- a/apps/aelf-template/package.json +++ b/apps/aelf-template/package.json @@ -3,7 +3,7 @@ "version": "0.1.0", "license": "MIT", "scripts": { - "dev": "next dev -p 3005", + "dev": "NODE_OPTIONS='--trace-warnings' next dev -p 3005", "build": "next build", "start": "next start", "lint": "next lint", diff --git a/apps/aelf-template/src/app/demos/api-all-in-one/author/csrfTokenProvider.tsx b/apps/aelf-template/src/app/demos/api-all-in-one/author/csrfTokenProvider.tsx new file mode 100644 index 0000000..379521b --- /dev/null +++ b/apps/aelf-template/src/app/demos/api-all-in-one/author/csrfTokenProvider.tsx @@ -0,0 +1,34 @@ +"use client" +import {createContext, ReactNode, useEffect, useState} from 'react'; +import {RequestAllInOne} from 'request-all-in-one'; +interface Props { + readonly children: ReactNode; +} +export const CSRFTokenContext = createContext(''); + +export const CSRFTokenProvider = ({ children }: Props) => { + const [token, setToken] = useState(); + useEffect(() => { + const token = localStorage.getItem('csrfToken'); + if (token) { + setToken(token); + } + const fetchToken = async () => { + const client = new RequestAllInOne({}); + const response = await client.post('/v1/token'); + if (response.code && response.code === '20000') { + localStorage.setItem('csrfToken', response.data); + setToken(response.data); + return response.data; + } + throw Error('fetch token error'); + } + fetchToken().then(token => { + console.log('fetch token done', token); + }).catch(error => { + console.log('fetch token error: ', error); + }); + }, []); + + return {children}; +}; diff --git a/apps/aelf-template/src/app/demos/api-all-in-one/author/layout.tsx b/apps/aelf-template/src/app/demos/api-all-in-one/author/layout.tsx new file mode 100644 index 0000000..b80cba4 --- /dev/null +++ b/apps/aelf-template/src/app/demos/api-all-in-one/author/layout.tsx @@ -0,0 +1,9 @@ +import { CSRFTokenProvider } from '@/app/demos/api-all-in-one/author/csrfTokenProvider'; + +export default function Layout({ + children, // will be a page or nested layout +}: { + children: React.ReactNode +}) { + return {children}; +} diff --git a/apps/aelf-template/src/app/demos/api-all-in-one/author/page.tsx b/apps/aelf-template/src/app/demos/api-all-in-one/author/page.tsx new file mode 100644 index 0000000..9af35c0 --- /dev/null +++ b/apps/aelf-template/src/app/demos/api-all-in-one/author/page.tsx @@ -0,0 +1,42 @@ +'use client'; +import {RequestAllInOne} from 'request-all-in-one'; +import {useContext, useState} from 'react'; +import {Button} from 'antd'; +import {CSRFTokenContext} from '@/app/demos/api-all-in-one/author/csrfTokenProvider'; + +const client = new RequestAllInOne({}); +export default function Author() { + const csrfToken = useContext(CSRFTokenContext); + console.log('csrfToken: ', csrfToken); + const [data, setData] = useState(''); + + return <> +
+ + +
{data}
+
+
+
Request token and storage in local by default
+ +
+ ; +} diff --git a/apps/aelf-template/src/app/demos/api-all-in-one/layout.tsx b/apps/aelf-template/src/app/demos/api-all-in-one/layout.tsx index 4b5f726..9f8d2b2 100644 --- a/apps/aelf-template/src/app/demos/api-all-in-one/layout.tsx +++ b/apps/aelf-template/src/app/demos/api-all-in-one/layout.tsx @@ -1,14 +1,15 @@ import { Menu } from 'antd'; import Link from 'next/link'; -import type { MenuProps } from 'antd'; +// import type { MenuProps } from 'antd'; -type MenuItem = Required['items'][number]; +// type MenuItem = Required['items'][number]; -const items: MenuItem[] = [ +const items: ({ label: JSX.Element; key: string })[] = [ { key: '1', label: Fetch }, { key: '2', label: Graphql }, { key: '3', label: Socket }, { key: '4', label: Stream }, + { key: '5', label: Author }, ]; export default function APIAllInOneLayout({ children, // will be a page or nested layout diff --git a/apps/aelf-template/src/app/demos/api-all-in-one/stream/page.tsx b/apps/aelf-template/src/app/demos/api-all-in-one/stream/page.tsx index 1903e10..c21544b 100644 --- a/apps/aelf-template/src/app/demos/api-all-in-one/stream/page.tsx +++ b/apps/aelf-template/src/app/demos/api-all-in-one/stream/page.tsx @@ -10,7 +10,8 @@ export default function Fetch() {
+ +
{data}
+
+
+
Request token and storage in local by default
+ +
+ ; +} diff --git a/apps/create-aelf-dapp/lib/generator/templates/src/app/demos/api-all-in-one/fetch-data.ts b/apps/create-aelf-dapp/lib/generator/templates/src/app/demos/api-all-in-one/fetch-data.ts index f8c9482..93e795d 100644 --- a/apps/create-aelf-dapp/lib/generator/templates/src/app/demos/api-all-in-one/fetch-data.ts +++ b/apps/create-aelf-dapp/lib/generator/templates/src/app/demos/api-all-in-one/fetch-data.ts @@ -7,7 +7,7 @@ import {ISocketRequestOptions} from '@/app/demos/api-all-in-one/socket-io'; export interface IBaseRequestOptions { method?: string; headers?: Record; - body?: Record; + body?: any; // Record; } export interface IRequestOptions extends ISocketRequestOptions, IApolloRequestOptions {} @@ -20,7 +20,7 @@ export class RequestAllInOne { private readonly headers: Record; private readonly apolloConfig: any; private readonly apolloClient: ApolloClient | undefined; // TCacheShape - private readonly socketClient: Socket | undefined; + public socketClient: Socket | undefined; private options: Partial; constructor(options: Partial) { this.options = options || {}; diff --git a/apps/create-aelf-dapp/lib/generator/templates/src/app/demos/api-all-in-one/fetch/page.tsx b/apps/create-aelf-dapp/lib/generator/templates/src/app/demos/api-all-in-one/fetch/page.tsx index 9b3c5ca..44047fa 100644 --- a/apps/create-aelf-dapp/lib/generator/templates/src/app/demos/api-all-in-one/fetch/page.tsx +++ b/apps/create-aelf-dapp/lib/generator/templates/src/app/demos/api-all-in-one/fetch/page.tsx @@ -1,5 +1,6 @@ 'use client'; -import {RequestAllInOne} from '@/app/demos/api-all-in-one/fetch-data'; +// import {RequestAllInOne} from '@/app/demos/api-all-in-one/fetch-data'; +import {RequestAllInOne} from 'request-all-in-one'; import {useEffect, useState} from 'react'; const client = new RequestAllInOne({}); @@ -31,65 +32,3 @@ export default function Fetch() { ; } - -// function Example() { -// const { isPending, error, data, isFetching } = useQuery({ -// queryKey: ['repoData'], -// queryFn: async () => { -// const response = await fetch( -// 'https://api.github.com/repos/TanStack/query', -// ) -// return await response.json() -// }, -// }) -// -// if (isPending) return 'Loading...' -// -// if (error) return 'An error has occurred: ' + error.message -// -// return ( -//
-//

{data.full_name}

-//

{data.description}

-// 👀 {data.subscribers_count}{' '} -// ✨ {data.stargazers_count}{' '} -// 🍴 {data.forks_count} -//
{isFetching ? 'Updating...' : ''}
-//
-// ) -// } - -// -// function Todos() { -// // Access the client -// const queryClient = useQueryClient() -// -// // Queries -// const query = useQuery({ queryKey: ['todos'], queryFn: getTodos }) -// -// // Mutations -// const mutation = useMutation({ -// mutationFn: postTodo, -// onSuccess: () => { -// // Invalidate and refetch -// queryClient.invalidateQueries({ queryKey: ['todos'] }) -// }, -// }) -// -// return ( -//
-//
    {query.data?.map((todo) =>
  • {todo.title}
  • )}
-// -// -//
-// ) -// } diff --git a/apps/create-aelf-dapp/lib/generator/templates/src/app/demos/api-all-in-one/layout.tsx b/apps/create-aelf-dapp/lib/generator/templates/src/app/demos/api-all-in-one/layout.tsx index 4ef9618..9f8d2b2 100644 --- a/apps/create-aelf-dapp/lib/generator/templates/src/app/demos/api-all-in-one/layout.tsx +++ b/apps/create-aelf-dapp/lib/generator/templates/src/app/demos/api-all-in-one/layout.tsx @@ -1,13 +1,15 @@ import { Menu } from 'antd'; import Link from 'next/link'; -import type { MenuProps } from 'antd'; +// import type { MenuProps } from 'antd'; -type MenuItem = Required['items'][number]; +// type MenuItem = Required['items'][number]; -const items: MenuItem[] = [ +const items: ({ label: JSX.Element; key: string })[] = [ { key: '1', label: Fetch }, { key: '2', label: Graphql }, { key: '3', label: Socket }, + { key: '4', label: Stream }, + { key: '5', label: Author }, ]; export default function APIAllInOneLayout({ children, // will be a page or nested layout diff --git a/apps/create-aelf-dapp/lib/generator/templates/src/app/demos/api-all-in-one/socket/page.tsx b/apps/create-aelf-dapp/lib/generator/templates/src/app/demos/api-all-in-one/socket/page.tsx index ff7007d..c13d6a0 100644 --- a/apps/create-aelf-dapp/lib/generator/templates/src/app/demos/api-all-in-one/socket/page.tsx +++ b/apps/create-aelf-dapp/lib/generator/templates/src/app/demos/api-all-in-one/socket/page.tsx @@ -39,7 +39,7 @@ export default function SocketPage() { console.log('Question: '); client.socket('/socket-io/', { type: 'emit', - event: 'question-chain-gpt', + event: 'question-chat-gpt', value: 'question socket case', callback: (response: any) => { console.log(response.status); diff --git a/apps/create-aelf-dapp/lib/generator/templates/src/app/demos/api-all-in-one/stream/page.tsx b/apps/create-aelf-dapp/lib/generator/templates/src/app/demos/api-all-in-one/stream/page.tsx new file mode 100644 index 0000000..c21544b --- /dev/null +++ b/apps/create-aelf-dapp/lib/generator/templates/src/app/demos/api-all-in-one/stream/page.tsx @@ -0,0 +1,41 @@ +'use client'; +import { useState} from 'react'; +import {Button} from 'antd'; +import { fetchEventSource } from '@microsoft/fetch-event-source'; + +let message = ''; +export default function Fetch() { + const [data, setData] = useState(''); + return <> +
+ +
{data}
+
+ ; +} diff --git a/apps/create-aelf-dapp/lib/generator/templates/src/app/demos/chaingpt/components/AnswerBox.tsx b/apps/create-aelf-dapp/lib/generator/templates/src/app/demos/chaingpt/components/AnswerBox.tsx deleted file mode 100644 index 7bd15fd..0000000 --- a/apps/create-aelf-dapp/lib/generator/templates/src/app/demos/chaingpt/components/AnswerBox.tsx +++ /dev/null @@ -1,13 +0,0 @@ -export function AnswerBox({ - children, - className - }: { - children: React.ReactNode, - className?: string -}) { - return
-
- {children} -
-
-} diff --git a/apps/create-aelf-dapp/lib/generator/templates/src/app/demos/chaingpt/components/QuestionBox.tsx b/apps/create-aelf-dapp/lib/generator/templates/src/app/demos/chaingpt/components/QuestionBox.tsx deleted file mode 100644 index f2b43c1..0000000 --- a/apps/create-aelf-dapp/lib/generator/templates/src/app/demos/chaingpt/components/QuestionBox.tsx +++ /dev/null @@ -1,11 +0,0 @@ -export function QuestionBox({ - children, // will be a page or nested layout - }: { - children: React.ReactNode -}) { - return
-
- {children} -
-
-} diff --git a/apps/create-aelf-dapp/lib/generator/templates/src/app/demos/chaingpt/page.tsx b/apps/create-aelf-dapp/lib/generator/templates/src/app/demos/chaingpt/page.tsx index 48006a5..9b449f2 100644 --- a/apps/create-aelf-dapp/lib/generator/templates/src/app/demos/chaingpt/page.tsx +++ b/apps/create-aelf-dapp/lib/generator/templates/src/app/demos/chaingpt/page.tsx @@ -7,112 +7,35 @@ * and inject antd's first-screen styles into HTML to avoid page flicker. * https://ant.design/docs/react/use-with-next **/ -import { Input, Spin } from 'antd'; -const { Search } = Input; -import { QuestionBox } from '@/app/demos/chaingpt/components/QuestionBox'; -import { AnswerBox } from '@/app/demos/chaingpt/components/AnswerBox'; -import {useEffect, useRef, useState} from 'react'; - -interface IChatItem { - text: string; - type: 'question' | 'answer'; -} - -const defaultList: IChatItem[] = [{"text":"the current price of ELF","type":"question"},{"text":"The current price of ELF (aelf) is $0.3994312670003399. Please note that cryptocurrency prices are highly volatile and can change rapidly. It's always a good idea to check the latest prices on a reliable cryptocurrency exchange or market data platform. Let me know if there's anything else I can help you with!","type":"answer"}]; - -// TODO: Virtual list to hold the messages -// import VirtualList from 'rc-virtual-list'; +import React, {useEffect, useState} from 'react'; +import {Button} from 'antd'; +import {ChatBoxButton, ChainGPT} from 'chaingpt-component' +import 'chaingpt-component/dist/index.css'; +const {TipBox, ChainGPTLogo, CustomChatBox} = ChainGPT; export default function Page() { - const [chatList, setChainList] = useState(defaultList); - const [searchDisable, setSearchDisable] = useState(false); - const [searchInput, setSearchInput] = useState(''); - - const onSearch = async (value: string) => { - console.log('value: ', value); - if (value.trim().length <= 0) { - return; - } - const question: IChatItem = { - text: value, - type: 'question' - }; - const _list = [...chatList, question]; - setChainList(_list); - setSearchDisable(true); - - const askChainGPT = async (question: string) => { - const url = '/api/demos/chaingpt'; - let data; - - const response = await fetch(url, { - method: "POST", - body: JSON.stringify({ question }), - }); - if (!response.ok) { - throw new Error(`Response status: ${response.status}`); - } - - const json = await response.json(); - console.log(json); - data = json.data; - return data; - }; - let answerMessage; - try { - answerMessage = await askChainGPT(value); - setSearchInput(''); - } catch (error) { - answerMessage = error instanceof Error ? error.message : 'Please try again'; - } - const answer: IChatItem = { - text: answerMessage, - type: 'answer' - }; - setChainList([..._list, answer]); - setSearchDisable(false); - }; - - console.log('chatList', chatList, JSON.stringify(chatList)); - const bottomRef = useRef(null); + const [chatAPI, setChatAPI] = useState(''); useEffect(() => { - if (bottomRef.current) { - bottomRef.current.scrollIntoView({ behavior: "smooth" }); - } - }, [chatList]); - - return <> -
-
-
-
- {/*Hello World*/} - {/*Hello World hzz! aelf addresses interoperability with other blockchain networks through its multi-chain architecture and cross-chain communication protocols.*/} - {/* The aelf blockchain is designed to support seamless communication and data exchange between different blockchains. It achieves this by implementing a main chain and multiple side chains. The main chain handles general functions and acts as a bridge between the side chains. Each side chain is dedicated to specific applications or business scenarios, allowing for better scalability and performance.*/} - {/*Hello World 2331111*/} - {/*Hello World*/} - {/*Hello World hzz! aelf addresses interoperability with other blockchain networks through its multi-chain architecture and cross-chain communication protocols.*/} - {/* The aelf blockchain is designed to support seamless communication and data exchange between different blockchains. It achieves this by implementing a main chain and multiple side chains. The main chain handles general functions and acts as a bridge between the side chains. Each side chain is dedicated to specific applications or business scenarios, allowing for better scalability and performance.*/} - {/*Hello World 2331111*/} - {chatList.map((item, index) => ( -
- {item.type === 'question' ? {item.text} : {item.text}} -
- ))} - -
-
-
-
-
- setSearchInput(e.target.value)} - onSearch={onSearch} - placeholder="Please input your question" /> -
+ // setChatAPI(`${location.protocol}//${location.host}/chaingpt/api/chat`); + // setChatAPI('/api/demos/chaingpt'); + setChatAPI('/api/chaingpt'); + }, []); + + return
+
+ + + +
+
+
- + +
; } diff --git a/apps/create-aelf-dapp/lib/generator/templates/src/app/demos/graphql/apollo-client.ts b/apps/create-aelf-dapp/lib/generator/templates/src/app/demos/graphql/apollo-client.ts new file mode 100644 index 0000000..5a320d3 --- /dev/null +++ b/apps/create-aelf-dapp/lib/generator/templates/src/app/demos/graphql/apollo-client.ts @@ -0,0 +1,19 @@ +import { ApolloClient, InMemoryCache, gql } from "@apollo/client"; +const createApolloClient = () => { + return new ApolloClient({ + uri: "https://countries.trevorblades.com", + cache: new InMemoryCache(), + }); +}; + +export default createApolloClient; + +export const queryCountries = gql` + query Countries { + countries { + code + name + emoji + } + } +`; diff --git a/apps/create-aelf-dapp/lib/generator/templates/src/app/demos/graphql/client/page.tsx b/apps/create-aelf-dapp/lib/generator/templates/src/app/demos/graphql/client/page.tsx new file mode 100644 index 0000000..06d4b83 --- /dev/null +++ b/apps/create-aelf-dapp/lib/generator/templates/src/app/demos/graphql/client/page.tsx @@ -0,0 +1,39 @@ +'use client'; +import createApolloClient, {queryCountries} from '@/app/demos/graphql/apollo-client'; +import { ApolloProvider, useQuery } from "@apollo/client"; + +const client = createApolloClient(); +export default function Client() { + return ( + + + + ); +} +function Countries() { + const { data, loading, error } = useQuery(queryCountries); + + if (loading) { + return

Loading...

; + } + + if (error) { + console.error(error); + return null; + } + + const countries = data.countries.slice(0, 4); + + return ( +
+ {countries.map((country: any) => ( +
+

{country.name}

+

+ {country.code} - {country.emoji} +

+
+ ))} +
+ ); +} diff --git a/apps/create-aelf-dapp/lib/generator/templates/src/app/demos/graphql/layout.tsx b/apps/create-aelf-dapp/lib/generator/templates/src/app/demos/graphql/layout.tsx new file mode 100644 index 0000000..70c65aa --- /dev/null +++ b/apps/create-aelf-dapp/lib/generator/templates/src/app/demos/graphql/layout.tsx @@ -0,0 +1,20 @@ +import { Menu } from 'antd'; +import Link from 'next/link'; +import type { MenuProps } from 'antd'; + +type MenuItem = Required['items'][number]; + +const items: MenuItem[] = [ + { key: '1', label: Server }, + { key: '2', label: Client }, +]; +export default function GraphqlLayout({ + children, // will be a page or nested layout +}: { + children: React.ReactNode +}) { + return <> + +
{children}
+ ; +} diff --git a/apps/create-aelf-dapp/lib/generator/templates/src/app/demos/graphql/page.tsx b/apps/create-aelf-dapp/lib/generator/templates/src/app/demos/graphql/page.tsx new file mode 100644 index 0000000..e2ffce2 --- /dev/null +++ b/apps/create-aelf-dapp/lib/generator/templates/src/app/demos/graphql/page.tsx @@ -0,0 +1,5 @@ +export default async function Graphql() { + return <> +
Nothing here
+ +} diff --git a/apps/create-aelf-dapp/lib/generator/templates/src/app/demos/graphql/server/page.tsx b/apps/create-aelf-dapp/lib/generator/templates/src/app/demos/graphql/server/page.tsx new file mode 100644 index 0000000..dc11589 --- /dev/null +++ b/apps/create-aelf-dapp/lib/generator/templates/src/app/demos/graphql/server/page.tsx @@ -0,0 +1,23 @@ +import createApolloClient, {queryCountries} from '@/app/demos/graphql/apollo-client'; +export async function getData() { + const client = createApolloClient(); + const { data } = await client.query({ + query: queryCountries, + }); + + return data.countries.slice(0, 4); +} + +export default async function Graphql() { + const countries = await getData(); + return
+ {countries.map((country: any) => ( +
+

{country.name}

+

+ {country.code} - {country.emoji} +

+
+ ))} +
+} diff --git a/apps/create-aelf-dapp/lib/generator/templates/src/app/demos/opentelemetry-request/page.tsx b/apps/create-aelf-dapp/lib/generator/templates/src/app/demos/opentelemetry-request/page.tsx new file mode 100644 index 0000000..c857e3c --- /dev/null +++ b/apps/create-aelf-dapp/lib/generator/templates/src/app/demos/opentelemetry-request/page.tsx @@ -0,0 +1,56 @@ +'use client' +import { WebTracerProvider } from '@opentelemetry/sdk-trace-web'; +import { registerInstrumentations } from '@opentelemetry/instrumentation'; +import { XMLHttpRequestInstrumentation } from '@opentelemetry/instrumentation-xml-http-request'; +import { W3CTraceContextPropagator } from '@opentelemetry/core'; + +import { Button } from 'aelf-design'; +import {FetchInstrumentation} from '@opentelemetry/instrumentation-fetch'; + +const provider = new WebTracerProvider(); + +provider.register({ + propagator: new W3CTraceContextPropagator(), +}); + +registerInstrumentations({ + instrumentations: [ + new XMLHttpRequestInstrumentation({ + propagateTraceHeaderCorsUrls: /.*/, + }), + new FetchInstrumentation({ + propagateTraceHeaderCorsUrls: /.*/, + }), + ], +}); + +const getData = (url: string) => new Promise((resolve, reject) => { + const req = new XMLHttpRequest(); + req.open('GET', url, true); + req.setRequestHeader('Content-Type', 'application/json'); + req.setRequestHeader('Accept', 'application/json'); + // req.setRequestHeader('TraceId', 'trace-556688991'); + req.onload = () => { + resolve(null); + }; + req.onerror = () => { + reject(); + }; + req.send(); +}); +// const URL_TEST = 'https://httpbin.org/get?trace=233333'; +const URL_TEST = '/v1/sharp/api/app/book'; +export default function Page() { + return <> + + + +} diff --git a/apps/create-aelf-dapp/lib/generator/templates/src/app/demos/opentelemetry/OpentelemetryProvider.tsx b/apps/create-aelf-dapp/lib/generator/templates/src/app/demos/opentelemetry/OpentelemetryProvider.tsx new file mode 100644 index 0000000..8870efa --- /dev/null +++ b/apps/create-aelf-dapp/lib/generator/templates/src/app/demos/opentelemetry/OpentelemetryProvider.tsx @@ -0,0 +1,21 @@ +"use client"; +import type {ReactNode} from "react"; +import { initWebTracerWithZone } from 'opentelemetry-launcher' +import {createContext, useEffect, useState} from 'react'; +import {Tracer} from '@opentelemetry/sdk-trace-base'; +import {APP_SETTINGS} from '../../../../appsettings'; +interface Props { + readonly children: ReactNode; +} +export const OpentelemetryContext = createContext(undefined); + +export const OpentelemetryProvider = ({ children }: Props) => { + const [webTracerWithZone, setWebTracerWithZone] = useState(); + useEffect(() => { + const _webTracerWithZone = initWebTracerWithZone(APP_SETTINGS.openTelemetry); + setWebTracerWithZone(_webTracerWithZone); + console.log('getWebTracerWithZone done'); + }, []); + + return {children}; +}; diff --git a/apps/create-aelf-dapp/lib/generator/templates/src/app/demos/opentelemetry/decoraterDemo.ts b/apps/create-aelf-dapp/lib/generator/templates/src/app/demos/opentelemetry/decoraterDemo.ts new file mode 100644 index 0000000..799569c --- /dev/null +++ b/apps/create-aelf-dapp/lib/generator/templates/src/app/demos/opentelemetry/decoraterDemo.ts @@ -0,0 +1,14 @@ +import { aggregateExecutionTime } from 'opentelemetry-launcher'; +export class ExampleService { + @aggregateExecutionTime + syncMethod() { + for (let i = 0; i < 1e6; i++) {} // Simulate workload + return 'Sync result'; + } + + @aggregateExecutionTime + async asyncMethod() { + await new Promise(resolve => setTimeout(resolve, 1000)); // Simulate async workload + return 'Async result'; + } +} diff --git a/apps/create-aelf-dapp/lib/generator/templates/src/app/demos/opentelemetry/exceptionCaptureInstrumentation.ts b/apps/create-aelf-dapp/lib/generator/templates/src/app/demos/opentelemetry/exceptionCaptureInstrumentation.ts new file mode 100644 index 0000000..e404db6 --- /dev/null +++ b/apps/create-aelf-dapp/lib/generator/templates/src/app/demos/opentelemetry/exceptionCaptureInstrumentation.ts @@ -0,0 +1,54 @@ +import { InstrumentationBase, InstrumentationConfig } from '@opentelemetry/instrumentation'; +import { Span, trace } from '@opentelemetry/api'; + +interface ExceptionCaptureConfig extends InstrumentationConfig { + captureUnhandledRejections?: boolean; +} + +class ExceptionCaptureInstrumentation extends InstrumentationBase { + constructor(config: ExceptionCaptureConfig = {}) { + super('exception-capture-instrumentation', '1.0.0', config); + } + + init() { + return []; + } + + override enable() { + this._initializeGlobalErrorHandler(); + } + + override disable() { + this._resetGlobalErrorHandler(); + } + + private _initializeGlobalErrorHandler() { + window.onerror = (message, source, lineno, colno, error) => { + const span: Span = this.tracer.startSpan('window.onerror'); + span.setAttribute('error.message', message as any); + span.setAttribute('error.source', source as any); + span.setAttribute('error.lineno', lineno as any); + span.setAttribute('error.colno', colno as any); + if (error) { + span.recordException(error); + } + span.end(); + }; + + if (this._config.captureUnhandledRejections) { + window.onunhandledrejection = (event) => { + const span: Span = this.tracer.startSpan('window.onunhandledrejection'); + span.setAttribute('error.reason', event.reason); + span.recordException(event.reason); + span.end(); + }; + } + } + + private _resetGlobalErrorHandler() { + window.onerror = null; + window.onunhandledrejection = null; + } +} + +export { ExceptionCaptureInstrumentation }; diff --git a/apps/create-aelf-dapp/lib/generator/templates/src/app/demos/opentelemetry/layout.tsx b/apps/create-aelf-dapp/lib/generator/templates/src/app/demos/opentelemetry/layout.tsx new file mode 100644 index 0000000..b57a709 --- /dev/null +++ b/apps/create-aelf-dapp/lib/generator/templates/src/app/demos/opentelemetry/layout.tsx @@ -0,0 +1,11 @@ +import {OpentelemetryProvider} from '@/app/demos/opentelemetry/OpentelemetryProvider'; + +export default function Layout({ + children, // will be a page or nested layout +}: { + children: React.ReactNode +}) { + return +
{children}
+
; +} diff --git a/apps/create-aelf-dapp/lib/generator/templates/src/app/demos/opentelemetry/page.tsx b/apps/create-aelf-dapp/lib/generator/templates/src/app/demos/opentelemetry/page.tsx new file mode 100644 index 0000000..8f11a80 --- /dev/null +++ b/apps/create-aelf-dapp/lib/generator/templates/src/app/demos/opentelemetry/page.tsx @@ -0,0 +1,84 @@ +'use client' +import {useContext} from 'react'; +const { context, trace } = require('@opentelemetry/api'); +import { Button } from 'aelf-design'; +import {registerInstrumentations} from '@opentelemetry/instrumentation'; +import {ExceptionCaptureInstrumentation} from '@/app/demos/opentelemetry/exceptionCaptureInstrumentation'; +import {useOpentelemetry} from '@/hooks/useOpentelemetry'; +// import { aggregateExecutionTime } from 'opentelemetry-launcher'; +import {ExampleService} from '@/app/demos/opentelemetry/decoraterDemo'; + +const getData = (url: string) => new Promise((resolve, reject) => { + const req = new XMLHttpRequest(); + req.open('GET', url, true); + req.setRequestHeader('Content-Type', 'application/json'); + req.setRequestHeader('Accept', 'application/json'); + req.onload = () => { + resolve(null); + }; + req.onerror = () => { + reject(); + }; + req.send(); +}); +// const URL_TEST = 'https://httpbin.org/get?trace=233333'; +const URL_TEST = '/v1/api/app/book'; +export default function Page() { + // Solution1: use useContext + // const webTracerWithZone = useContext(OpentelemetryContext); + // Solution2: use useOpentelemetry + const webTracerWithZone = useOpentelemetry(); + + return <> + + + + + + +} diff --git a/apps/create-aelf-dapp/lib/generator/templates/src/app/demos/sentry/page.jsx b/apps/create-aelf-dapp/lib/generator/templates/src/app/demos/sentry/page.jsx index ab7981f..52ecf03 100644 --- a/apps/create-aelf-dapp/lib/generator/templates/src/app/demos/sentry/page.jsx +++ b/apps/create-aelf-dapp/lib/generator/templates/src/app/demos/sentry/page.jsx @@ -9,6 +9,23 @@ async function justThrowError(message) { throw Error(`Promise then, but not catch ${message}`); } +const getData = (url) => new Promise((resolve, reject) => { + const req = new XMLHttpRequest(); + req.open('GET', url, true); + req.setRequestHeader('Content-Type', 'application/json'); + req.setRequestHeader('Accept', 'application/json'); + // req.setRequestHeader('TraceId', 'trace-556688991'); + req.onload = () => { + resolve(null); + }; + req.onerror = () => { + reject(); + }; + req.send(); +}); +// const URL_TEST = 'https://httpbin.org/get'; +const URL_TEST = '/v1/api/app/book'; + export default function Page() { return (
@@ -80,6 +97,7 @@ export default function Page() { }}> Throw error with promise await + getData(URL_TEST)}>getData

Next, look for the error on the{" "} Issues Page. diff --git a/apps/create-aelf-dapp/lib/generator/templates/src/app/demos/socket/page.tsx b/apps/create-aelf-dapp/lib/generator/templates/src/app/demos/socket/page.tsx index ee0c375..0051edb 100644 --- a/apps/create-aelf-dapp/lib/generator/templates/src/app/demos/socket/page.tsx +++ b/apps/create-aelf-dapp/lib/generator/templates/src/app/demos/socket/page.tsx @@ -33,7 +33,7 @@ export default function SocketPage() { }}>Connect