Skip to content

Latest commit

 

History

History

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 

README.md

GraphQL Subscriptions (WebSockets)

Basta's Client API supports GraphQL subscriptions over WebSockets to deliver near real-time updates to connected clients.

These updates enhance the responsiveness and interactivity of auction experiences — especially near the end of a sale when timing and feedback are critical.

Subscriptions support both unauthenticated and authenticated access via bidder tokens.

Near real-time updates for connected clients are achieved with GraphQL subscriptions. These updates enhance the auction experience and play a crucial part near the end of an auction.

⚙️ Connection Details

  • Endpoint: wss://client.api.basta.app/query
  • Protocol: GraphQL over WebSocket (graphql-ws)
  • Ping/Pong: Automatic keep-alive every 10 seconds

🔐 Authentication

To authenticate, send a bidder token in the initPayload when establishing the WebSocket connection:

{
  "type": "connection_init",
  "payload": {
    "token": "<BIDDER_JWT_TOKEN>"
  }
}

If no token is provided, the connection is treated as unauthenticated.

Using graphql-ws Package

If you're using the graphql-ws package you can use the connectionParams to provide the bidder token like so:

connectionParams: () => {
  return {
    token: bidderToken,
  };
},

🛡️ Why the init payload?

For integrators building advanced clients, here's how auth is handled on the server:

  1. The token field in the WebSocket connection_init payload is validated via JWT
  2. Valid claims are injected into the request context
  3. Bids and subscriptions are scoped to the authenticated userId

📡 Available Subscriptions

saleActivity(saleId, itemIdFilter)

📌 Primary subscription for real-time updates on both sales and items.

subscription SaleActivity($saleId: ID!, $itemIdFilter: ItemIdsFilter) {
  saleActivity(saleId: $saleId, itemIdFilter: $itemIdFilter) {
    __typename
    ... on Sale {
      id
      status
    }
    ... on Item {
      id
      status
      currentBid
      userBids {
        id
        amount
        maxAmount
        bidderIdentifier
      }
      bids {
        amount
        maxAmount
        bidDate
        bidderIdentifier
      }
    }
  }
}

Use Cases:

  • Listen for all changes on sale object and item objects for a given saleId
  • Track sale status changes (e.g. "OPEN" → "CLOSING")
  • Update UI countdowns and prices dynamically

Auth: Optional, but required to get user scoped properties such as userBids

Code Examples

JavaScript/TypeScript with graphql-ws

import { createClient } from 'graphql-ws';

// Create WebSocket client
const wsClient = createClient({
  url: 'wss://client.api.basta.app/query',
  connectionParams: () => {
    return {
      token: bidderToken, // Optional: include for authenticated subscriptions
    };
  },
});

// Subscribe to sale activity
const unsubscribe = wsClient.subscribe(
  {
    query: `
      subscription SaleActivity($saleId: ID!) {
        saleActivity(saleId: $saleId) {
          __typename
          ... on Sale {
            id
            status
          }
          ... on Item {
            id
            status
            currentBid
            userBids {
              id
              amount
              maxAmount
            }
          }
        }
      }
    `,
    variables: {
      saleId: 'sale-123',
    },
  },
  {
    next: (data) => {
      console.log('Received update:', data);
    },
    error: (error) => {
      console.error('Subscription error:', error);
    },
    complete: () => {
      console.log('Subscription completed');
    },
  }
);

// Clean up when done
// unsubscribe();

React with Apollo Client

import { ApolloClient, InMemoryCache, split, HttpLink } from '@apollo/client';
import { GraphQLWsLink } from '@apollo/client/link/subscriptions';
import { getMainDefinition } from '@apollo/client/utilities';
import { createClient } from 'graphql-ws';
import { useSubscription, gql } from '@apollo/client';

// Create HTTP link for queries and mutations
const httpLink = new HttpLink({
  uri: 'https://client.api.basta.app/graphql',
});

// Create WebSocket link for subscriptions
const wsLink = new GraphQLWsLink(
  createClient({
    url: 'wss://client.api.basta.app/query',
    connectionParams: () => ({
      token: getBidderToken(), // Your function to get the token
    }),
  })
);

// Split based on operation type
const splitLink = split(
  ({ query }) => {
    const definition = getMainDefinition(query);
    return (
      definition.kind === 'OperationDefinition' &&
      definition.operation === 'subscription'
    );
  },
  wsLink,
  httpLink
);

// Create Apollo Client
const client = new ApolloClient({
  link: splitLink,
  cache: new InMemoryCache(),
});

// Use in component
const SALE_ACTIVITY_SUBSCRIPTION = gql`
  subscription SaleActivity($saleId: ID!) {
    saleActivity(saleId: $saleId) {
      __typename
      ... on Sale {
        id
        status
      }
      ... on Item {
        id
        status
        currentBid
        userBids {
          id
          amount
          maxAmount
        }
      }
    }
  }
`;

function AuctionMonitor({ saleId }) {
  const { data, loading, error } = useSubscription(
    SALE_ACTIVITY_SUBSCRIPTION,
    {
      variables: { saleId },
    }
  );

  if (loading) return <div>Connecting...</div>;
  if (error) return <div>Error: {error.message}</div>;

  return (
    <div>
      <h2>Real-time Auction Updates</h2>
      <pre>{JSON.stringify(data, null, 2)}</pre>
    </div>
  );
}

Ruby with graphql-client

require 'graphql/client'
require 'graphql/client/http'

# Configure HTTP adapter for schema introspection
HTTP = GraphQL::Client::HTTP.new('https://client.api.basta.app/graphql')

# Load schema
Schema = GraphQL::Client.load_schema(HTTP)

# Create client
Client = GraphQL::Client.new(
  schema: Schema,
  execute: HTTP
)

# Define subscription
SaleActivitySubscription = Client.parse <<-GRAPHQL
  subscription SaleActivity($saleId: ID!) {
    saleActivity(saleId: $saleId) {
      __typename
      ... on Sale {
        id
        status
      }
      ... on Item {
        id
        status
        currentBid
      }
    }
  }
GRAPHQL

# Note: For WebSocket subscriptions in Ruby, you'll need to use
# a WebSocket client library like faye-websocket or action_cable_client
# and implement the graphql-ws protocol manually.

Python with gql

import asyncio
from gql import gql, Client
from gql.transport.websockets import WebsocketsTransport

# Create WebSocket transport
transport = WebsocketsTransport(
    url='wss://client.api.basta.app/query',
    init_payload={'token': bidder_token}  # Optional
)

# Create client
async def subscribe_to_auction():
    async with Client(
        transport=transport,
        fetch_schema_from_transport=True,
    ) as session:
        # Define subscription
        subscription = gql("""
            subscription SaleActivity($saleId: ID!) {
                saleActivity(saleId: $saleId) {
                    __typename
                    ... on Sale {
                        id
                        status
                    }
                    ... on Item {
                        id
                        status
                        currentBid
                    }
                }
            }
        """)

        # Subscribe and handle updates
        async for result in session.subscribe(
            subscription,
            variable_values={'saleId': 'sale-123'}
        ):
            print(f"Received update: {result}")

# Run subscription
asyncio.run(subscribe_to_auction())

Best Practices

  1. Handle Reconnection: Implement automatic reconnection logic for network interruptions
  2. Cleanup: Always unsubscribe when components unmount or when subscriptions are no longer needed
  3. Error Handling: Implement proper error handling for connection failures
  4. Token Refresh: If using authenticated subscriptions, handle token expiration and refresh
  5. Selective Subscriptions: Use itemIdFilter to subscribe only to specific items when needed

Troubleshooting

Connection Fails

  • Verify the WebSocket endpoint URL is correct
  • Check that your bidder token is valid (if using authenticated mode)
  • Ensure your firewall allows WebSocket connections

Missing Updates

  • Verify you're subscribed to the correct saleId
  • Check that the sale is in an active state
  • Confirm authentication if trying to access user-scoped data

High Latency

  • Consider your network conditions
  • Check if you're subscribed to too many items simultaneously
  • Use itemIdFilter to reduce the volume of updates

Related Documentation

Support

For questions about subscriptions: