Skip to content

Commit

Permalink
Feat: support payg (#148)
Browse files Browse the repository at this point in the history
* Intergrate plan for payg

* Change `orderType` to `projectType`

* support payg in authHttpLink

* Restructure for Apollo links

* Add `responseLink`

* Improve the import

* Export more types

* Resolve import in authLink test

* Fix cluster auth link

* feat: request token and report to chs

* chore: reset

* chore: skip test and TODO

---------

Co-authored-by: mzxyz <[email protected]>
Co-authored-by: HZHAIX <>
Co-authored-by: cyrbuzz <[email protected]>
  • Loading branch information
3 people authored Jul 13, 2023
1 parent 0600381 commit faea387
Show file tree
Hide file tree
Showing 19 changed files with 326 additions and 145 deletions.
74 changes: 0 additions & 74 deletions packages/apollo-links/src/auth-link/clusterAuthLink.ts

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import jwt_decode from 'jwt-decode';
import buffer from 'buffer';

import { AuthMessage, buildTypedMessage, createAuthRequestBody } from './eip712';
import { POST } from '../query';
import { POST } from '../utils/query';

const Buffer = buffer.Buffer;

Expand Down
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,3 @@

export * from './authHelper';
export * from './eip712';
export * from './clusterAuthLink';
export * from './authLink';
36 changes: 20 additions & 16 deletions packages/apollo-links/src/authHttpLink.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,17 @@

import { from, ApolloLink, HttpOptions } from '@apollo/client/core';

import { ClusterAuthLink } from './auth-link';
import { DynamicHttpLink } from './dynamicHttpLink';
import AgreementManager from './agreementManager';
import { creatErrorLink } from './errorLink';
import { createRetryLink } from './retryLink';
import { Logger, silentLogger } from './logger';
import { FallbackLink } from './fallbackLink';
import { OrderType } from './types';
import OrderMananger from './utils/orderManager';
import { Logger, silentLogger } from './utils/logger';
import { ProjectType } from './types';
import {
createRetryLink,
FallbackLink,
DynamicHttpLink,
ResponseLink,
creatErrorLink,
ClusterAuthLink,
} from './core';

interface DictAuthOptions extends BaseAuthOptions {
chainId: string; // chain id for the requested dictionary
Expand All @@ -21,7 +24,7 @@ interface DeploymentAuthOptions extends BaseAuthOptions {
}

interface AuthOptions extends DeploymentAuthOptions {
orderType: OrderType; // order type
projectType: ProjectType; // order type
}

interface BaseAuthOptions {
Expand All @@ -33,11 +36,11 @@ interface BaseAuthOptions {

export function dictHttpLink(options: DictAuthOptions): ApolloLink {
const { chainId } = options;
return authHttpLink({ ...options, deploymentId: chainId, orderType: OrderType.dictionary });
return authHttpLink({ ...options, deploymentId: chainId, projectType: ProjectType.dictionary });
}

export function deploymentHttpLink(options: DeploymentAuthOptions): ApolloLink {
return authHttpLink({ ...options, orderType: OrderType.deployment });
return authHttpLink({ ...options, projectType: ProjectType.deployment });
}

function authHttpLink(options: AuthOptions): ApolloLink {
Expand All @@ -47,32 +50,33 @@ function authHttpLink(options: AuthOptions): ApolloLink {
fallbackServiceUrl,
authUrl,
logger: _logger,
orderType,
projectType,
} = options;

const logger = _logger ?? silentLogger();
const agreementManager = new AgreementManager({
const orderMananger = new OrderMananger({
authUrl,
projectId: deploymentId,
logger,
orderType,
projectType,
});

const retryLink = createRetryLink(logger);
const fallbackLink = new FallbackLink(fallbackServiceUrl, logger);
const httpLink = new DynamicHttpLink({ httpOptions, logger });
const responseLink = new ResponseLink({ logger, authUrl });
const errorLink = creatErrorLink({ logger, fallbackLink, httpLink });
const authLink = new ClusterAuthLink({
authUrl,
projectId: deploymentId,
logger,
agreementManager,
orderMananger,
});

// 1. errorLink: This link helps in handling and logging any GraphQL or network errors that may occur down the chain.
// Placing it at the beginning ensures that it catches any errors that may occur in any of the other links.
// 2. retryLink: This comes after the errorLink to allow it to handle network errors and retry requests if necessary.
// 3. authLink: The authLink comes next. It is responsible for adding authentication credentials to every request.
// 4. httpLink: This should always be at the end of the link chain. This link is responsible for sending the request to the server.
return from([errorLink, retryLink, authLink, fallbackLink, httpLink]);
return from([errorLink, retryLink, authLink, fallbackLink, responseLink, httpLink]);
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@
import { ApolloLink, FetchResult, NextLink, Observable, Operation } from '@apollo/client/core';
import { Subscription } from 'zen-observable-ts';

import { isTokenExpired, requestAuthToken } from './authHelper';
import { Message } from './eip712';
import { Logger } from '../logger';
import { isTokenExpired, requestAuthToken } from '../auth/authHelper';
import { Message } from '../auth/eip712';
import { Logger } from '../utils/logger';

interface AuthOptions extends Message {
indexerUrl: string; // indexer url
Expand Down
119 changes: 119 additions & 0 deletions packages/apollo-links/src/core/clusterAuthLink.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
// Copyright 2020-2022 SubQuery Pte Ltd authors & contributors
// SPDX-License-Identifier: Apache-2.0

import { ApolloLink, FetchResult, NextLink, Observable, Operation } from '@apollo/client/core';
import { Subscription } from 'zen-observable-ts';

import { isTokenExpired } from '../auth/authHelper';
import OrderMananger from '../utils/orderManager';
import { POST } from '../utils/query';
import { Logger } from '../utils/logger';
import { ChannelState, OrderType } from '../types';

export type AuthOptions = {
authUrl: string; // the url for geting token
projectId: string; // chainId or deploymentId for the project
orderMananger: OrderMananger; // agreement manager for managing agreements
logger: Logger; // logger for logging
};

type RequestParams = {
url: string;
token: string;
type: OrderType;
};

export class ClusterAuthLink extends ApolloLink {
private options: AuthOptions;
private logger: Logger;
private orderMananger: OrderMananger;

constructor(options: AuthOptions) {
super();
this.options = options;
this.logger = options.logger;
this.orderMananger = options.orderMananger;
}

override request(operation: Operation, forward?: NextLink): Observable<FetchResult> | null {
if (!forward) return null;

return new Observable<FetchResult>((observer) => {
let sub: Subscription;
this.getRequestParams()
.then((data) => {
if (data) {
const { token, url, type } = data;
const headers = { authorization: `${token}` };
operation.setContext({ url, headers, type });
}

sub = forward(operation).subscribe(observer);
})
.catch((error) => {
this.logger.warn(`Failed to get token: ${error.message}`);
observer.error(new Error('failed to get indexer url and token'));
});

return () => sub?.unsubscribe();
});
}

private async getRequestParams(): Promise<RequestParams | undefined> {
const orderType = await this.orderMananger.getNextOrderType();
if (!orderType) return undefined;
switch (orderType) {
case OrderType.agreement:
return this.getAgreementRequestParams();
case OrderType.flexPlan:
return this.getPlanRequestParams();
default:
return undefined;
}
}

private async getAgreementRequestParams(): Promise<RequestParams | undefined> {
const nextAgreement = await this.orderMananger.getNextAgreement();
if (!nextAgreement) return undefined;

const type = OrderType.agreement;
const { token, id, url, indexer } = nextAgreement;
if (!isTokenExpired(token)) return { token, url, type };
this.logger.debug(`request new token for indexer ${indexer}`);
const { projectId, authUrl } = this.options;

const tokenUrl = new URL('/orders/token', authUrl);
const res = await POST<{ token: string }>(tokenUrl.toString(), {
projectId,
indexer,
agreementId: id,
});

this.orderMananger.updateTokenById(id, res.token);
this.logger.debug(`request new token for indexer ${indexer} success`);
return { token: `Bearer ${res.token}`, url, type };
}

private async getPlanRequestParams(): Promise<RequestParams | undefined> {
const nextPlan = await this.orderMananger.getNextPlan();
if (!nextPlan) return undefined;

const type = OrderType.flexPlan;
const { id: channelId, url, indexer } = nextPlan;

this.logger.debug(`request new signature for indexer ${indexer}`);
const { projectId: deployment, authUrl } = this.options;

const tokenUrl = new URL('/channel/sign', authUrl);
const signedState = await POST<ChannelState>(tokenUrl.toString(), {
deployment,
channelId,
});

this.logger.debug(`state signature: ${signedState}`);
const token = JSON.stringify(signedState);
this.logger.debug(`request new state signature for indexer ${indexer} success`);

return { token, url, type };
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,23 +10,23 @@ import {
Observable,
Operation,
} from '@apollo/client/core';
import { Logger } from './logger';
import { Logger } from '../utils/logger';

export interface Options {
type Options = {
httpOptions: HttpOptions; // http options for init `HttpLink`
logger?: Logger;
}
};

export class DynamicHttpLink extends ApolloLink {
private _options: Options;
private options: Options;

constructor(options: Options) {
super();
this._options = options;
this.options = options;
}

get logger(): Logger | undefined {
return this._options.logger;
return this.options.logger;
}

override request(operation: Operation, forward?: NextLink): Observable<FetchResult> | null {
Expand All @@ -36,6 +36,7 @@ export class DynamicHttpLink extends ApolloLink {
observer.error(new Error(`empty url`));
});
}

this.logger?.debug(`use url: ${url}`);
const httpLink = this.createHttpLink(url);

Expand All @@ -44,7 +45,7 @@ export class DynamicHttpLink extends ApolloLink {

private createHttpLink(url: string): HttpLink {
return new HttpLink({
...this._options.httpOptions,
...this.options.httpOptions,
uri: url,
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// SPDX-License-Identifier: Apache-2.0

import { onError } from '@apollo/client/link/error';
import { Logger } from './logger';
import { Logger } from '../utils/logger';
import { ApolloLink, FetchResult, NextLink, Observable } from '@apollo/client/core';

export type ErrorLinkOption = {
Expand All @@ -12,7 +12,7 @@ export type ErrorLinkOption = {
};

export const creatErrorLink = ({ logger, fallbackLink, httpLink }: ErrorLinkOption) =>
onError(({ graphQLErrors, networkError, operation, forward }) => {
onError(({ graphQLErrors, networkError, operation }) => {
if (graphQLErrors)
graphQLErrors.forEach(({ message, locations, path }) =>
logger?.debug(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// SPDX-License-Identifier: Apache-2.0

import { ApolloLink, Operation, NextLink, Observable, FetchResult } from '@apollo/client/core';
import { Logger } from './logger';
import { Logger } from '../utils/logger';

export class FallbackLink extends ApolloLink {
constructor(private url?: string, private logger?: Logger) {
Expand Down
10 changes: 10 additions & 0 deletions packages/apollo-links/src/core/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// Copyright 2020-2023 SubQuery Pte Ltd authors & contributors
// SPDX-License-Identifier: Apache-2.0

export * from './authLink';
export * from './clusterAuthLink';
export * from './dynamicHttpLink';
export * from './errorLink';
export * from './fallbackLink';
export * from './responseLink';
export * from './retryLink';
Loading

0 comments on commit faea387

Please sign in to comment.