community: convert realtime poll sample app to hooks (close #7675)

GITHUB_PR_NUMBER: 7694
GITHUB_PR_URL: https://github.com/hasura/graphql-engine/pull/7694

PR-URL: https://github.com/hasura/graphql-engine-mono/pull/2620
Co-authored-by: andykcom <532952+andykcom@users.noreply.github.com>
GitOrigin-RevId: 428d9a17468f18376957963a152afb11d176a39c
This commit is contained in:
hasura-bot 2021-10-26 10:24:57 +02:00
parent 44977bdf9d
commit 0e6bcfdba4
16 changed files with 496 additions and 529 deletions

View File

@ -16,12 +16,13 @@ hosted on GitHub pages and the Postgres+GraphQL Engine is running on Postgres.
- Checkout the [live app](https://realtime-poll.demo.hasura.app/). - Checkout the [live app](https://realtime-poll.demo.hasura.app/).
- Explore the backend using [Hasura - Explore the backend using [Hasura
Console](https://realtime-poll.hasura.app/console). Console](https://realtime-poll.hasura.app/console).
# Running the app yourself # Running the app yourself
- Deploy GraphQL Engine on Hasura Cloud and setup PostgreSQL via Heroku: - Deploy GraphQL Engine on Hasura Cloud and setup PostgreSQL via Heroku:
[![Deploy to Hasura Cloud](https://graphql-engine-cdn.hasura.io/img/deploy_to_hasura.png)](https://cloud.hasura.io/signup) [![Deploy to Hasura Cloud](https://graphql-engine-cdn.hasura.io/img/deploy_to_hasura.png)](https://cloud.hasura.io/signup)
- Get the Hasura app URL (say `realtime-poll.hasura.app`) - Get the Hasura app URL (say `realtime-poll.hasura.app`)
- Clone this repo: - Clone this repo:
```bash ```bash
@ -39,10 +40,10 @@ hosted on GitHub pages and the Postgres+GraphQL Engine is running on Postgres.
hasura migrate apply hasura migrate apply
hasura metadata reload hasura metadata reload
``` ```
- Edit `HASURA_GRAPHQL_ENGINE_HOSTNAME` in `src/apollo.js` and set it to the - Edit `GRAPHQL_ENDPOINT` in `src/apollo.js` and set it to the
Hasura app URL: Hasura app URL:
```js ```js
export const HASURA_GRAPHQL_ENGINE_HOSTNAME = 'realtime-poll.hasura.app'; export const GRAPHQL_ENDPOINT = "realtime-poll.hasura.app";
``` ```
- Run the app (go the root of repo): - Run the app (go the root of repo):
```bash ```bash

View File

@ -3,21 +3,14 @@
"version": "0.1.0", "version": "0.1.0",
"private": true, "private": true,
"dependencies": { "dependencies": {
"apollo-boost": "^0.4.7", "@apollo/client": "^3.4.16",
"apollo-cache-inmemory": "^1.2.7", "graphql": "^15.6.0",
"apollo-client": "^2.3.8", "react": "^17.0.2",
"apollo-link": "^1.2.2",
"apollo-link-http": "^1.5.4",
"apollo-link-ws": "^1.0.8",
"apollo-utilities": "^1.0.18",
"graphql": "^15.0.0",
"react": "^16.4.2",
"react-apollo": "^3.1.4",
"react-bootstrap": "^1.0.0", "react-bootstrap": "^1.0.0",
"react-dom": "^16.4.2", "react-dom": "^16.4.2",
"react-google-charts": "^3.0.5", "react-google-charts": "^3.0.5",
"react-scripts": "^3.4.1", "react-scripts": "^3.4.1",
"subscriptions-transport-ws": "^0.9.14" "subscriptions-transport-ws": "^0.9.19"
}, },
"scripts": { "scripts": {
"start": "react-scripts start", "start": "react-scripts start",

View File

@ -3,22 +3,19 @@
} }
.App-logo { .App-logo {
/* animation: App-logo-spin infinite 20s linear;
height: 80px; */
width: 90px; width: 90px;
display: inline-block; display: inline-block;
margin-right: 50px; margin-right: 50px;
} }
.displayFlex .displayFlex {
{
display: flex; display: flex;
align-items: center; align-items: center;
} }
.hasura-logo a:hover { .hasura-logo a:hover {
text-decoration: none; text-decoration: none;
} }
.hasura-logo img { .hasura-logo img {
height: 30px; height: 30px;
} }
.App-header { .App-header {
background-color: #222; background-color: #222;
@ -71,13 +68,17 @@ pre {
width: 100%; width: 100%;
} }
@keyframes App-logo-spin { @keyframes App-logo-spin {
from { transform: rotate(0deg); } from {
to { transform: rotate(360deg); } transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
} }
.footer-small-text { .footer-small-text {
padding-top: 10px; padding-top: 10px;
font-size: 0.8em; font-size: 0.8em;
} }
.online-users { .online-users {
@ -87,22 +88,22 @@ pre {
margin-top: 30px; margin-top: 30px;
} }
.online-users .alert { .online-users .alert {
margin-bottom: 0px; margin-bottom: 0px;
} }
.poll-result-chart-container { .poll-result-chart-container {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
max-width: 900px; max-width: 900px;
width: 100%; width: 100%;
min-height: 350px; min-height: 350px;
} }
@media(max-width: 767px) { @media (max-width: 767px) {
.displayFlex { .displayFlex {
display: block; display: block;
} }
} }
.hasura-logo img { .hasura-logo img {

View File

@ -1,66 +0,0 @@
import React, { Component } from 'react';
import logo from './img/logo-white.svg';
import './App.css';
import { ApolloProvider } from 'react-apollo';
import client from './apollo';
import Poll from './Poll';
import { getUserId } from './session';
import { GraphQL } from './GraphQL';
import { Users } from './Users';
class App extends Component {
constructor(props) {
super(props);
this.state = { loading: true, userId: '' };
}
componentDidMount() {
getUserId().then((userId) => {
this.setState({ loading: false, userId });
});
}
render() {
// if (this.state.loading) return <p>Loading...</p>;
return (
<ApolloProvider client={client}>
<div className="App">
<header className="App-header displayFlex">
<div className="container displayFlex">
<img src={logo} className="App-logo" alt="logo" />
<h1 className="App-title">Realtime Poll</h1>
</div>
</header>
<Users />
<Poll userId={this.state.userId} />
<GraphQL />
<footer className="App-footer displayFlex">
<div className="container hasura-logo">
<a href="https://hasura.io" target="_blank" rel="noopener noreferrer">
<img className="hasura-logo" alt="hasura logo" src="https://graphql-engine-cdn.hasura.io/img/powered_by_hasura_black_200px.png" />
</a>
&nbsp; | &nbsp;
<a href="https://realtime-poll.hasura.app/console" target="_blank">
Backend
</a>
&nbsp; | &nbsp;
<a href="https://github.com/hasura/graphql-engine/tree/master/community/sample-apps/realtime-poll" target="_blank" rel="noopener noreferrer">
Source
</a>
<div className="footer-small-text"><span>(The database resets every 24 hours)</span></div>
</div>
</footer>
</div>
</ApolloProvider>
);
}
}
export default App;

View File

@ -0,0 +1,37 @@
import { ApolloProvider } from "@apollo/client";
import React, { useEffect, useState } from "react";
import client from "./apollo";
import "./App.css";
import { Footer, Header } from "./Components";
import { GraphQLQueryList } from "./GraphQL";
import { Poll } from "./Poll";
import { getUserId } from "./session";
import { Users } from "./Users";
const App = () => {
const defaultState = { loading: true, userId: "" };
const [state, setState] = useState(defaultState);
useEffect(() => {
const fetchUserId = async () => {
const userId = await getUserId();
setState({ loading: false, userId });
};
fetchUserId();
}, []);
return (
<ApolloProvider client={client}>
<div className="App">
<Header />
<Users />
<Poll userId={state.userId} />
<GraphQLQueryList />
<Footer />
</div>
</ApolloProvider>
);
};
export default App;

View File

@ -0,0 +1,49 @@
import React from "react";
import logo from "./img/logo-white.svg";
// State indicator components
export const Loading = () => <div>Loading...</div>;
export const Error = ({ message }) => (
<div class="alert alert-danger" role="alert">
<b>Error:</b> {message}
</div>
);
// Layout components
export const Header = () => (
<header className="App-header displayFlex">
<div className="container displayFlex">
<img src={logo} className="App-logo" alt="logo" />
<h1 className="App-title">Realtime Poll</h1>
</div>
</header>
);
export const Footer = () => (
<footer className="App-footer displayFlex">
<div className="container hasura-logo">
<a href="https://hasura.io" target="_blank" rel="noopener noreferrer">
<img
className="hasura-logo"
alt="hasura logo"
src="https://graphql-engine-cdn.hasura.io/img/powered_by_hasura_black_200px.png"
/>
</a>
&nbsp; | &nbsp;
<a href="https://realtime-poll.hasura.app/console" target="_blank">
Backend
</a>
&nbsp; | &nbsp;
<a
href="https://github.com/hasura/graphql-engine/tree/master/community/sample-apps/realtime-poll"
target="_blank"
rel="noopener noreferrer"
>
Source
</a>
<div className="footer-small-text">
<span>(The database resets every 24 hours)</span>
</div>
</div>
</footer>
);

View File

@ -1,115 +0,0 @@
import React from 'react';
import { Card } from 'react-bootstrap';
const QUERY_GET_POLL = `
query {
poll (limit: 10) {
id
question
options (order_by: {id:desc}){
id
text
}
}
}`;
const MUTATION_VOTE = `
mutation vote($optionId: uuid!, $userId: uuid!) {
insert_vote(objects:[{
option_id: $optionId,
created_by_user_id: $userId
}]) {
returning {
id
}
}
}`;
const SUBSCRIPTION_RESULT = `
subscription getResult($pollId: uuid!) {
poll_results (
order_by: {option_id:desc},
where: { poll_id: {_eq: $pollId} }
) {
option_id
option { id text }
votes
}
}`;
const SUBSCRIPTION_ONLINE_USERS = `
subscription getOnlineUsersCount {
online_users {
count
}
}`;
const MUTATION_MARK_USER_ONLINE = `
mutation userOnline($uuid: uuid) {
update_user(
where: {id: {_eq: $uuid}},
_set : { online_ping: true }
) {
affected_rows
returning {
last_seen_at
}
}
}`;
const MUTATION_NEW_USER = `
mutation newUser($uuid: uuid) {
insert_user (
objects:[{ id: $uuid }]
) {
returning {
id
created_at
}
}
}`;
const GraphQL = () => (
<div className="container">
<div className="col-md-12 cardGraphQL">
<Card>
<Card.Header>GraphQL Queries/Mutations/Subscriptions in this page</Card.Header>
<Card.Body>
<div className="row">
<div className="col-md-4">
Get the Poll question and options:
<pre>{QUERY_GET_POLL}</pre>
Create a new user:
<pre>{MUTATION_NEW_USER}</pre>
</div>
<div className="col-md-4">
Cast a vote:
<pre>{MUTATION_VOTE}</pre>
Mark user online:
<pre>{MUTATION_MARK_USER_ONLINE}</pre>
</div>
<div className="col-md-4">
Show live results:
<pre>{SUBSCRIPTION_RESULT}</pre>
Get real-time number of users:
<pre>{SUBSCRIPTION_ONLINE_USERS}</pre>
</div>
</div>
</Card.Body>
</Card>
</div>
</div>
)
export {
GraphQL,
QUERY_GET_POLL,
MUTATION_VOTE,
SUBSCRIPTION_RESULT,
SUBSCRIPTION_ONLINE_USERS,
MUTATION_MARK_USER_ONLINE,
MUTATION_NEW_USER,
};

View File

@ -0,0 +1,118 @@
import gql from "graphql-tag";
import React from "react";
import { Card } from "react-bootstrap";
const QUERY_GET_POLL = gql`
query {
poll(limit: 10) {
id
question
options(order_by: { id: desc }) {
id
text
}
}
}
`;
const MUTATION_VOTE = gql`
mutation vote($optionId: uuid!, $userId: uuid!) {
insert_vote(
objects: [{ option_id: $optionId, created_by_user_id: $userId }]
) {
returning {
id
}
}
}
`;
const SUBSCRIPTION_RESULT = gql`
subscription getResult($pollId: uuid!) {
poll_results(
order_by: { option_id: desc }
where: { poll_id: { _eq: $pollId } }
) {
option_id
option {
id
text
}
votes
}
}
`;
const SUBSCRIPTION_ONLINE_USERS = gql`
subscription getOnlineUsersCount {
online_users {
count
}
}
`;
const MUTATION_MARK_USER_ONLINE = gql`
mutation userOnline($uuid: uuid) {
update_user(where: { id: { _eq: $uuid } }, _set: { online_ping: true }) {
affected_rows
returning {
last_seen_at
}
}
}
`;
const MUTATION_NEW_USER = gql`
mutation newUser($uuid: uuid) {
insert_user(objects: [{ id: $uuid }]) {
returning {
id
created_at
}
}
}
`;
const GraphQLQueryList = () => (
<div className="container">
<div className="col-md-12 cardGraphQL">
<Card>
<Card.Header>
GraphQL Queries/Mutations/Subscriptions in this page
</Card.Header>
<Card.Body>
<div className="row">
<div className="col-md-4">
Get the Poll question and options:
<pre>{QUERY_GET_POLL.loc.source.body}</pre>
Create a new user:
<pre>{MUTATION_NEW_USER.loc.source.body}</pre>
</div>
<div className="col-md-4">
Cast a vote:
<pre>{MUTATION_VOTE.loc.source.body}</pre>
Mark user online:
<pre>{MUTATION_MARK_USER_ONLINE.loc.source.body}</pre>
</div>
<div className="col-md-4">
Show live results:
<pre>{SUBSCRIPTION_RESULT.loc.source.body}</pre>
Get real-time number of users:
<pre>{SUBSCRIPTION_ONLINE_USERS.loc.source.body}</pre>
</div>
</div>
</Card.Body>
</Card>
</div>
</div>
);
export {
GraphQLQueryList,
QUERY_GET_POLL,
MUTATION_VOTE,
SUBSCRIPTION_RESULT,
SUBSCRIPTION_ONLINE_USERS,
MUTATION_MARK_USER_ONLINE,
MUTATION_NEW_USER,
};

View File

@ -1,137 +0,0 @@
import React, { Component } from 'react';
import { Query, Mutation } from 'react-apollo';
import gql from 'graphql-tag';
import { Button, Form } from 'react-bootstrap';
import { Result } from './Result';
import { QUERY_GET_POLL, MUTATION_VOTE } from './GraphQL';
class PollQuestion extends Component {
constructor(props) {
super(props);
this.state = {
optionId: '',
pollId: props.poll.id,
voteBtnText: '🗳 Vote',
voteBtnStyle: 'primary'
};
}
handleOptionChange = (e) => {
this.setState({
optionId: e.currentTarget.value
});
}
onMutationCompleted = () => {
this.setState({
voteBtnText: '👍 Done',
voteBtnStyle: 'success'
});
// re-authorize to vote after 5 seconds
window.setTimeout(() => {
this.setState({
voteBtnText: '🗳️ Vote',
voteBtnStyle: 'primary'
});
}, 5000);
}
onMutationError = () => {
this.setState({
voteBtnText: 'Error 😞 Try again',
voteBtnStyle: 'danger'
});
}
handlesubmitVote = (e, vote) => {
e.preventDefault();
if (!this.state.optionId) {
this.setState({
voteBtnText: '✋ Select an option and try again',
voteBtnStyle: 'warning'
});
return
}
this.setState({
voteBtnText: '🗳️ Submitting',
voteBtnStyle: 'info'
});
vote({
variables: {
optionId: this.state.optionId,
userId: this.props.userId
}
});
}
render() {
return (
<Mutation
mutation={gql`${MUTATION_VOTE}`}
onCompleted={this.onMutationCompleted}
onError={this.onMutationError}
>
{(vote) => (
<div className="textLeft">
<h3>{this.props.poll.question}</h3>
<Form className="pollForm textLeft" onSubmit={e => { this.handlesubmitVote(e, vote) }}>
{
this.props.poll.options.map(option => (
<Form.Check
custom
type="radio"
name="voteCandidate"
id={option.id}
key={option.id}
value={option.id}
label={option.text}
onChange={this.handleOptionChange}
/>
))
}
<Button className="voteBtn info" variant={this.state.voteBtnStyle} type="submit">
{this.state.voteBtnText}
</Button>
</Form>
</div>
)}
</Mutation>
);
}
};
const Poll = ({ userId }) => (
<div>
<Query query={gql`${QUERY_GET_POLL}`}>
{({ loading, error, data }) => {
if (loading) return <p>Loading...</p>;
if (error) {
return <div class="alert alert-danger" role="alert"><b>Error:</b> ${error.message}</div>;
}
return (
<div className="container">
{
data.poll.map(poll => (
<div key={poll.id} className="pollWrapper wd100">
<div className="displayFlex">
<div className="col-md-4 pollSlider">
<PollQuestion poll={poll} userId={userId} />
</div>
<div className="col-md-8 pollresult">
<Result pollId={poll.id} />
</div>
</div>
</div>
))
}
</div>
);
}}
</Query>
</div>
);
export default Poll;

View File

@ -0,0 +1,128 @@
import { useMutation, useQuery } from "@apollo/client";
import React, { useEffect, useState } from "react";
import { Button, Form } from "react-bootstrap";
import { Error, Loading } from "./Components";
import { MUTATION_VOTE, QUERY_GET_POLL } from "./GraphQL";
import { Result } from "./Result";
const PollQuestion = ({ poll, userId }) => {
const defaultState = {
optionId: "",
pollId: poll.id,
voteBtnText: "🗳 Vote",
voteBtnStyle: "primary",
};
const [state, setState] = useState(defaultState);
const [vote, { data, loading, error }] = useMutation(MUTATION_VOTE);
const handlesubmitVote = (e) => {
e.preventDefault();
if (!state.optionId) {
setState({
voteBtnText: "✋ Select an option and try again",
voteBtnStyle: "warning",
});
return;
}
setState({
voteBtnText: "🗳️ Submitting",
voteBtnStyle: "info",
});
vote({
variables: {
optionId: state.optionId,
userId,
},
});
};
// To-do: use cleanup
useEffect(() => {
if (data) {
setState({
voteBtnText: "👍 Done",
voteBtnStyle: "success",
});
// Re-authorize to vote after 5 seconds
let timer = setTimeout(() => {
setState({
voteBtnText: "🗳️ Vote",
voteBtnStyle: "primary",
});
}, 5000);
return () => clearTimeout(timer);
}
if (error) {
setState({
voteBtnText: "Error 😞 Try again",
voteBtnStyle: "danger",
});
}
}, [data, error]);
const handleOptionChange = (e) => {
const optionId = e.currentTarget.value;
setState((prev) => ({ ...prev, optionId }));
};
return (
<div className="textLeft">
<h3>{poll.question}</h3>
<Form
className="pollForm textLeft"
onSubmit={(e) => {
handlesubmitVote(e);
}}
>
{poll.options.map(({ id, text }) => (
<Form.Check
custom
type="radio"
name="voteCandidate"
id={id}
key={id}
value={id}
label={text}
onChange={handleOptionChange}
/>
))}
<Button
className="voteBtn info"
variant={state.voteBtnStyle}
type="submit"
>
{state.voteBtnText}
</Button>
</Form>
</div>
);
};
export const Poll = ({ userId }) => {
const { data, loading, error } = useQuery(QUERY_GET_POLL);
if (loading) return <Loading />;
if (error) return <Error message={error.message} />;
return (
<div className="container">
{data?.poll.map((poll) => (
<div key={poll.id} className="pollWrapper wd100">
<div className="displayFlex">
<div className="col-md-4 pollSlider">
<PollQuestion poll={poll} userId={userId} />
</div>
<div className="col-md-8 pollresult">
<Result pollId={poll.id} />
</div>
</div>
</div>
))}
</div>
);
};

View File

@ -1,58 +0,0 @@
import React from 'react';
import {
Subscription,
} from 'react-apollo';
import { Chart } from 'react-google-charts';
import gql from 'graphql-tag';
import { SUBSCRIPTION_RESULT } from './GraphQL'
const renderChart = (data) => {
const d = [
['Option', 'No. of votes', { role: 'annotation' }, { role: 'style' }],
];
for (var r of data.poll_results) {
console.log(r);
d.push([r.option.text, parseInt(r.votes, 10), parseInt(r.votes, 10), 'color: #4285f4']);
}
return (
<Chart className="poll-result-chart-container"
chartType="BarChart"
loader={<div>Loading Chart</div>}
data={d}
options={{
height: '100%',
chart: {
title: 'Realtime results',
},
legend: { position: 'none' },
animation: {
duration: 1000,
easing: 'out',
startup: true,
},
}}
/>
)
};
export const Result = (pollId) => (
<Subscription subscription={gql`${SUBSCRIPTION_RESULT}`} variables={pollId}>
{({ loading, error, data }) => {
if (loading) return <p>Loading...</p>;
if (error) return <p>Error :</p>;
return (
<div>
{data.poll_results.length > 0
?
<div>
{renderChart(data)}
</div>
:
<p>No result</p>
}
</div>
);
}}
</Subscription>
)

View File

@ -0,0 +1,56 @@
import { useSubscription } from "@apollo/client";
import React from "react";
import { Chart } from "react-google-charts";
import { Error, Loading } from "./Components";
import { SUBSCRIPTION_RESULT } from "./GraphQL";
export const Result = ({ pollId }) => {
const { data, loading, error } = useSubscription(SUBSCRIPTION_RESULT, {
variables: { pollId },
});
const hasResults = data?.poll_results.length > 0;
if (loading) return <Loading />;
if (error) return <Error message={error.message} />;
return (
<div>
{hasResults ? <PollChart data={data?.poll_results} /> : <p>No result</p>}
</div>
);
};
const PollChart = ({ data }) => {
const COLOR = "color: #4285f4";
const d = [
["Option", "No. of votes", { role: "annotation" }, { role: "style" }],
];
data.forEach(({ option, votes }) =>
d.push([option.text, parseInt(votes), parseInt(votes), COLOR])
);
return (
<Chart
className="poll-result-chart-container"
chartType="BarChart"
loader={<div>Loading Chart</div>}
data={d}
options={chartOptions}
/>
);
};
const chartOptions = {
height: "100%",
chart: {
title: "Realtime results",
},
legend: { position: "none" },
animation: {
duration: 1000,
easing: "out",
startup: true,
},
};

View File

@ -1,27 +0,0 @@
import React from 'react';
import gql from 'graphql-tag';
import { Subscription } from 'react-apollo';
import {
Alert,
} from 'react-bootstrap';
import { SUBSCRIPTION_ONLINE_USERS } from './GraphQL';
export const Users = () => (
<Subscription subscription={gql`${SUBSCRIPTION_ONLINE_USERS}`}>
{({ loading, error, data }) => {
if (loading) return <span>Loading...</span>;
if (error) {
return <div class="alert alert-danger" role="alert"><b>Error:</b> ${error.message}</div>;
}
return (
<div className="displayFlex online-users">
<div className="col-md-6">
<Alert variant="info">
<span role="img" aria-label="online users">👥</span> Online users: {data.online_users[0].count}
</Alert>
</div>
</div>
);
}}
</Subscription>
)

View File

@ -0,0 +1,26 @@
import { useSubscription } from "@apollo/client";
import React from "react";
import { Alert } from "react-bootstrap";
import { Error, Loading } from "./Components";
import { SUBSCRIPTION_ONLINE_USERS } from "./GraphQL";
export const Users = () => {
const { data, loading, error } = useSubscription(SUBSCRIPTION_ONLINE_USERS);
const { count } = data?.online_users[0] || {};
if (loading) return <Loading />;
if (error) return <Error message={error.message} />;
return (
<div className="displayFlex online-users">
<div className="col-md-6">
<Alert variant="info">
<span role="img" aria-label="online users">
👥
</span>{" "}
Online users: {count}
</Alert>
</div>
</div>
);
};

View File

@ -1,52 +1,26 @@
// Remove the apollo-boost import and change to this: import { ApolloClient, HttpLink, InMemoryCache, split } from "@apollo/client";
import ApolloClient from "apollo-client"; import { WebSocketLink } from "@apollo/client/link/ws";
import { getMainDefinition } from "@apollo/client/utilities";
// Setup the network "links" const scheme = (proto) =>
import { WebSocketLink } from 'apollo-link-ws'; window.location.protocol === "https:" ? `${proto}s` : proto;
import { HttpLink } from 'apollo-link-http';
import { split } from 'apollo-link';
import { getMainDefinition } from 'apollo-utilities';
import { InMemoryCache } from 'apollo-cache-inmemory'; const splitter = ({ query }) => {
const { kind, operation } = getMainDefinition(query) || {};
export const HASURA_GRAPHQL_ENGINE_HOSTNAME = 'realtime-poll.hasura.app'; const isSubscription =
kind === "OperationDefinition" && operation === "subscription";
const scheme = (proto) => { return isSubscription;
return window.location.protocol === 'https:' ? `${proto}s` : proto;
}
const wsurl = `${scheme('ws')}://${HASURA_GRAPHQL_ENGINE_HOSTNAME}/v1/graphql`;
const httpurl = `${scheme('http')}://${HASURA_GRAPHQL_ENGINE_HOSTNAME}/v1/graphql`;
const wsLink = new WebSocketLink({
uri: wsurl,
options: {
reconnect: true,
}
});
const httpLink = new HttpLink({
uri: httpurl,
});
const link = split(
// split based on operation type
({ query }) => {
const { kind, operation } = getMainDefinition(query);
return kind === 'OperationDefinition' && operation === 'subscription';
},
wsLink,
httpLink,
);
const createApolloClient = () => {
return new ApolloClient({
link,
cache: new InMemoryCache()
});
}; };
const GRAPHQL_ENDPOINT = "realtime-poll.hasura.app";
const cache = new InMemoryCache();
const options = { reconnect: true };
const client = createApolloClient(); const wsURI = `${scheme("ws")}://${GRAPHQL_ENDPOINT}/v1/graphql`;
const httpurl = `${scheme("https")}://${GRAPHQL_ENDPOINT}/v1/graphql`;
const wsLink = new WebSocketLink({ uri: wsURI, options });
const httpLink = new HttpLink({ uri: httpurl });
const link = split(splitter, wsLink, httpLink);
const client = new ApolloClient({ link, cache });
export default client; export default client;

View File

@ -1,9 +1,5 @@
import gql from 'graphql-tag'; import client from "./apollo";
import client from './apollo'; import { MUTATION_MARK_USER_ONLINE, MUTATION_NEW_USER } from "./GraphQL";
import {
MUTATION_MARK_USER_ONLINE,
MUTATION_NEW_USER,
} from './GraphQL';
const newUUID = () => { const newUUID = () => {
const p8 = (s) => { const p8 = (s) => {
@ -13,49 +9,40 @@ const newUUID = () => {
return p8() + p8(true) + p8(true) + p8(); return p8() + p8(true) + p8(true) + p8();
}; };
const getUserId = async () => {
const getUserId = () => { const { data } = await client.mutate({
return new Promise((resolve, reject) => { mutation: MUTATION_NEW_USER,
// let uid = window.localStorage.getItem('uid'); variables: { uuid: newUUID() },
// if (!uid) {
client.mutate({
mutation: gql`${MUTATION_NEW_USER}`,
variables: {
uuid: newUUID(),
}
}).then(({ data }) => {
if (data.insert_user.returning.length > 0) {
const user = data.insert_user.returning[0];
console.log("getUserId user.id:", user.id);
// window.localStorage.setItem('uid', user.id);
// window.localStorage.setItem('createdAt', user.created_at);
reportUserOnline(user.id);
resolve(user.id);
} else {
reject('no data');
}
}).catch((error) => {
console.error(error);
reject(error);
});
// } else {
// reportUserOnline(uid);
// resolve(uid);
// }
}); });
try {
if (data?.insert_user.returning.length > 0) {
const { id } = data.insert_user.returning[0] || {};
reportUserOnline(id);
return id;
} else {
throw new Error(400);
}
} catch (error) {
console.error(error);
throw new Error(400);
}
}; };
const reportUserOnline = (userId) => { const reportUserOnline = (userId) => {
window.setInterval(() => { window.setInterval(async () => {
client.mutate({ try {
mutation: gql`${MUTATION_MARK_USER_ONLINE}`, await client.mutate({
variables: { mutation: MUTATION_MARK_USER_ONLINE,
uuid: userId, variables: {
}, uuid: userId,
}); },
});
} catch (error) {
console.error(error);
}
}, 10000); }, 10000);
}; };
export { export { getUserId };
getUserId
};