Skip to content

Commit

Permalink
Replace GraphQL subscriptions (WS) by queries with polling (HTTP) (#203)
Browse files Browse the repository at this point in the history
The Graph is having issues with GraphQL subscriptions, so we are temporarily
moving to queries with polling for the time being. Once subscriptions get
enabled again on The Graph, we will move back to GraphQL subscriptions.

This commit also adds the possibility to consume the organization connector
from any app connector, allowing to retrieve its options for example. This was
done so that app connectors using TheGraph can inherit from the pollInterval
defined in the main connector.

This commit also updates the nodejs/ demos.
  • Loading branch information
bpierre authored Aug 17, 2020
1 parent d1349b5 commit 1fff067
Show file tree
Hide file tree
Showing 38 changed files with 438 additions and 211 deletions.
7 changes: 2 additions & 5 deletions examples/nodejs/src/describe-script.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { connect, describeScript, App } from '@aragon/connect'
import { Vote, Voting } from '@aragon/connect-voting'
import connectVoting, { Vote } from '@aragon/connect-voting'

const VOTING_SUBGRAPH_URL =
'https://api.thegraph.com/subgraphs/name/aragon/aragon-voting-mainnet'
Expand All @@ -14,10 +14,7 @@ function voteId(vote: Vote) {
async function main() {
const org = await connect('beehive.aragonid.eth', 'thegraph')
const apps = await org.apps()
const votingApp = apps.find((app: App) => app.name === 'voting')!

const voting = new Voting(votingApp.address, VOTING_SUBGRAPH_URL)

const voting = await connectVoting(org.app('voting'))
const votes = await voting.votes()

const { script } = votes.find((vote) => voteId(vote) === '#54')!
Expand Down
4 changes: 2 additions & 2 deletions examples/nodejs/src/organization.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ async function main() {
permissions.map(console.log)

console.log('\nA role from a permission:')
const role = await permissions[4].getRole()
const role = await permissions[4].role()
console.log(role)

console.log('\nApps:')
Expand All @@ -34,7 +34,7 @@ async function main() {
console.log(appByAddress)

console.log('\nAn app from a permission:')
const appFromPermission = await permissions[1].getApp()
const appFromPermission = await permissions[1].app()
console.log(appFromPermission)
}

Expand Down
2 changes: 1 addition & 1 deletion examples/nodejs/src/subscriptions-org.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ const ORG_ADDRESS = '0xd697d1f417c621a6a54d9a7fa2805596ca393222'

async function main() {
const org = (await connect(ORG_ADDRESS, 'thegraph', {
chainId: 4,
network: 4,
})) as Organization

const subscription = org.onPermissions((permissions: Permission[]) => {
Expand Down
19 changes: 9 additions & 10 deletions examples/nodejs/src/subscriptions-tokens.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,24 @@
import { TokenManager, TokenHolder } from '@aragon/connect-tokens'
import connect from '@aragon/connect'
import connectTokens, { TokenHolder } from '@aragon/connect-tokens'
import { keepRunning } from './helpers'

const ORG_ADDRESS = '0x7318f8bc0a9f0044d5c77a6aca9c73c1da49c51e'
const TOKENS_APP_ADDRESS = '0xb27004bf714ce2aa38f14647b38836f26df86cbf'
const ALL_TOKEN_MANAGER_SUBGRAPH_URL =
'https://api.thegraph.com/subgraphs/name/aragon/aragon-tokens-mainnet'

async function main() {
const tokenManager = new TokenManager(
TOKENS_APP_ADDRESS,
ALL_TOKEN_MANAGER_SUBGRAPH_URL
)
const org = await connect(ORG_ADDRESS, 'thegraph')
const tokens = await connectTokens(org.app(TOKENS_APP_ADDRESS))
const token = await tokens.token()

const token = await tokenManager.token()
console.log(`\nToken:`)
console.log(token)

const subscription = token.onHolders((holders: TokenHolder[]) => {
const subscription = tokens.onHolders((holders: TokenHolder[]) => {
console.log(`\nHolders:`)
holders.map(console.log)
})

await keepRunning()
// await keepRunning()

// Simply to illustrate how to close a subscription
subscription.unsubscribe()
Expand Down
24 changes: 17 additions & 7 deletions examples/nodejs/src/subscriptions-voting.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,25 @@
import { Voting, Vote, Cast } from '@aragon/connect-voting'
import connect from '@aragon/connect'
import connectVoting, { Voting, Vote, Cast } from '@aragon/connect-voting'
import { keepRunning } from './helpers'

const ORG_ADDRESS = '0x7cee20f778a53403d4fc8596e88deb694bc91c98'
const VOTING_APP_ADDRESS = '0xf7f9a33ed13b01324884bd87137609251b5f7c88'
const ALL_VOTING_SUBGRAPH_URL =
'https://api.thegraph.com/subgraphs/name/aragon/aragon-voting-rinkeby'

async function main() {
const voting = new Voting(VOTING_APP_ADDRESS, ALL_VOTING_SUBGRAPH_URL)
const org = await connect(ORG_ADDRESS, 'thegraph', { network: 4 })
const voting = await connectVoting(org.app(VOTING_APP_ADDRESS))

const votesSubscription = voting.onVotes((votes: Vote[]) => {
const votesSubscription = voting.onVotes({}, (votes: Vote[]) => {
console.log('\nVotes: ')
votes.map(console.log)
votes
.map((vote, index) => ({
index,
title: vote.metadata,
id: vote.id,
}))
.forEach((vote) => {
console.log(vote)
})
console.log(
`\nTry creating a new vote at https://rinkeby.aragon.org/#/${ORG_ADDRESS}/${VOTING_APP_ADDRESS}/\n`
)
Expand All @@ -23,7 +31,9 @@ async function main() {

const castsSubscription = vote1.onCasts((casts: Cast[]) => {
console.log(`\nCasts:`)
casts.map(console.log)
casts.forEach((cast) => {
console.log(cast)
})
console.log(
`\nTry casting a vote on https://rinkeby.aragon.org/#/${ORG_ADDRESS}/${VOTING_APP_ADDRESS}/vote/1 (You must first mint yourself a token in the Token Manager)\n`
)
Expand Down
20 changes: 8 additions & 12 deletions examples/nodejs/src/tokens.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,19 @@
import { TokenManager } from '@aragon/connect-tokens'
import connect from '@aragon/connect'
import connectTokens from '@aragon/connect-tokens'

const ORG_ADDRESS = '0x7318f8bc0a9f0044d5c77a6aca9c73c1da49c51e'
const TOKENS_APP_ADDRESS = '0xb27004bf714ce2aa38f14647b38836f26df86cbf'
const ALL_TOKEN_MANAGER_SUBGRAPH_URL =
'https://api.thegraph.com/subgraphs/name/aragon/aragon-tokens-mainnet'

async function main() {
const tokenManager = new TokenManager(
TOKENS_APP_ADDRESS,
ALL_TOKEN_MANAGER_SUBGRAPH_URL,
true
)
const org = await connect(ORG_ADDRESS, 'thegraph', { verbose: true })
const tokens = await connectTokens(org.app(TOKENS_APP_ADDRESS))

console.log(tokenManager)

const token = await tokenManager.token()
const token = await tokens.token()
console.log(token)

console.log('\nHolders:')
const holders = await token.holders()
const holders = await tokens.holders()

holders.map(console.log)
}

Expand Down
23 changes: 7 additions & 16 deletions examples/nodejs/src/voting.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
import { connect } from '@aragon/connect'
import { Cast, Vote, Voting } from '@aragon/connect-voting'
import connectVoting, { Cast, Vote, Voting } from '@aragon/connect-voting'

type Env = { chainId: number; org: string; votingSubgraphUrl: string }
type Env = { network: number; org: string; votingSubgraphUrl: string }

const envs = new Map(
Object.entries({
rinkeby: {
chainId: 4,
network: 4,
org: 'gardens.aragonid.eth',
votingSubgraphUrl:
'https://api.thegraph.com/subgraphs/name/aragon/aragon-voting-rinkeby',
},
mainnet: {
chainId: 1,
network: 1,
org: 'governance.aragonproject.eth',
votingSubgraphUrl:
'https://api.thegraph.com/subgraphs/name/aragon/aragon-voting-mainnet',
Expand Down Expand Up @@ -41,20 +41,11 @@ function voteId(vote: Vote) {
}

async function main() {
const org = await connect(env.org, 'thegraph', { chainId: env.chainId })
const apps = await org.apps()
const votingApp = apps.find((app) => app.appName === 'voting.aragonpm.eth')
const org = await connect(env.org, 'thegraph', { network: env.network })
const voting = await connectVoting(org.app('voting'))

console.log('\nOrganization:', org.location, `(${org.address})`)

if (!votingApp?.address) {
console.log('\nNo voting app found in this organization')
return
}

console.log(`\nVoting app: ${votingApp.address}`)

const voting = new Voting(votingApp.address, env.votingSubgraphUrl)
console.log(`\nVoting app: ${voting.address}`)

console.log(`\nVotes:`)
const votes = await voting.votes()
Expand Down
4 changes: 2 additions & 2 deletions examples/nodejs/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"references": [
{ "path": "../../packages/connect" },
{ "path": "../../packages/connect-thegraph" },
{ "path": "../../packages/connect-thegraph-tokens" },
{ "path": "../../packages/connect-thegraph-voting" }
{ "path": "../../packages/connect-tokens" },
{ "path": "../../packages/connect-voting" }
]
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ describe('Agreement', () => {
let connector: AgreementConnectorTheGraph

beforeAll(() => {
connector = new AgreementConnectorTheGraph(AGREEMENT_SUBGRAPH_URL)
connector = new AgreementConnectorTheGraph({
subgraphUrl: AGREEMENT_SUBGRAPH_URL,
})
})

afterAll(async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ describe('Agreement signers', () => {
let connector: AgreementConnectorTheGraph

beforeAll(() => {
connector = new AgreementConnectorTheGraph(AGREEMENT_SUBGRAPH_URL)
connector = new AgreementConnectorTheGraph({
subgraphUrl: AGREEMENT_SUBGRAPH_URL,
})
})

afterAll(async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ describe('Agreement versions', () => {
let connector: AgreementConnectorTheGraph

beforeAll(() => {
connector = new AgreementConnectorTheGraph(AGREEMENT_SUBGRAPH_URL)
connector = new AgreementConnectorTheGraph({
subgraphUrl: AGREEMENT_SUBGRAPH_URL,
})
})

afterAll(async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ describe('Agreement', () => {
let agreement: Agreement

beforeAll(() => {
const connector = new AgreementConnectorTheGraph(AGREEMENT_SUBGRAPH_URL)
const connector = new AgreementConnectorTheGraph({
subgraphUrl: AGREEMENT_SUBGRAPH_URL,
})
agreement = new Agreement(connector, AGREEMENT_APP_ADDRESS)
})

Expand Down
25 changes: 17 additions & 8 deletions packages/connect-agreement/src/connect.ts
Original file line number Diff line number Diff line change
@@ -1,28 +1,37 @@
import { createAppConnector } from '@aragon/connect-core'

import Agreement from './models/Agreement'
import AgreementConnectorTheGraph, {
subgraphUrlFromChainId,
} from './thegraph/connector'

type Config = {
subgraphUrl: string
pollInterval?: number
subgraphUrl?: string
}

export default createAppConnector<Agreement, Config>(
({ app, config, connector, network, verbose }) => {
({ app, config, connector, network, orgConnector, verbose }) => {
if (connector !== 'thegraph') {
console.warn(
`Connector unsupported: ${connector}. Using "thegraph" instead.`
)
}

const subgraphUrl =
config.subgraphUrl ?? subgraphUrlFromChainId(network.chainId)
const agreementConnector = new AgreementConnectorTheGraph(
config.subgraphUrl ?? subgraphUrlFromChainId(network.chainId) ?? undefined

let pollInterval
if (orgConnector.name === 'thegraph') {
pollInterval =
config?.pollInterval ?? orgConnector.config?.pollInterval ?? undefined
}

const connectorTheGraph = new AgreementConnectorTheGraph({
pollInterval,
subgraphUrl,
verbose
)
return new Agreement(agreementConnector, app.address)
verbose,
})

return new Agreement(connectorTheGraph, app.address)
}
)
15 changes: 12 additions & 3 deletions packages/connect-agreement/src/thegraph/connector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,16 +28,25 @@ export function subgraphUrlFromChainId(chainId: number) {
return null
}

type AgreementConnectorTheGraphConfig = {
pollInterval?: number
subgraphUrl?: string
verbose?: boolean
}

export default class AgreementConnectorTheGraph implements IAgreementConnector {
#gql: GraphQLWrapper

constructor(subgraphUrl: string, verbose: boolean = false) {
if (!subgraphUrl) {
constructor(config: AgreementConnectorTheGraphConfig) {
if (!config.subgraphUrl) {
throw new Error(
'AgreementConnectorTheGraph requires subgraphUrl to be passed.'
)
}
this.#gql = new GraphQLWrapper(subgraphUrl, verbose)
this.#gql = new GraphQLWrapper(config.subgraphUrl, {
pollInterval: config.pollInterval,
verbose: config.verbose,
})
}

async disconnect() {
Expand Down
10 changes: 6 additions & 4 deletions packages/connect-core/src/connections/ConnectorJson.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,15 @@ export type ConnectorJsonConfig = {

class ConnectorJson implements IOrganizationConnector {
#permissions: Permission[]
connection?: ConnectionContext
readonly config: ConnectorJsonConfig
readonly name = 'json'
readonly network: Network
connection?: ConnectionContext

constructor({ permissions, network }: ConnectorJsonConfig) {
this.#permissions = permissions
this.network = network
constructor(config: ConnectorJsonConfig) {
this.config = config
this.network = config.network
this.#permissions = config.permissions
}

async connect(connection: ConnectionContext) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import Role from '../entities/Role'
export default interface IOrganizationConnector {
readonly name: string
readonly network: Network
readonly config: { [key: string]: any }
appByAddress(organization: Organization, appAddress: string): Promise<App>
appForOrg(organization: Organization, filters?: AppFilters): Promise<App>
appsForOrg(organization: Organization, filters?: AppFilters): Promise<App[]>
Expand Down
9 changes: 8 additions & 1 deletion packages/connect-core/src/utils/app-connectors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@ import App from '../entities/App'

type AppConnectContext = {
app: App
connector: string
config: object
connector: string
ipfs: ConnectionContext['ipfs']
network: Network
orgConnector: ConnectionContext['orgConnector']
verbose: boolean
}

Expand Down Expand Up @@ -56,7 +57,12 @@ export function createAppConnector<

const { connection } = app.organization
const { orgConnector } = connection

// App connector config.
const [connectorName, connectorConfig] = normalizeConnectorConfig<Config>(
// Contrary to the main connect() function, app connectors don’t require
// the connector to be passed. In this case, the name of the org
// connector (e.g. `name`) is used instead.
connector || orgConnector.name
)

Expand All @@ -66,6 +72,7 @@ export function createAppConnector<
connector: connectorName,
ipfs: connection.ipfs,
network: orgConnector.network,
orgConnector,
verbose: connection.verbose,
})

Expand Down
Loading

0 comments on commit 1fff067

Please sign in to comment.