Skip to content

Latest commit

 

History

History
183 lines (136 loc) · 4.69 KB

File metadata and controls

183 lines (136 loc) · 4.69 KB
title Custom XRPC Calls
description Call private services, new lexicons, and raw AT Protocol endpoints before generated descriptors exist.

Generated descriptors are the best path for stable known lexicons. Raw XRPC calls are still useful when you are integrating with a private service, testing a new lexicon, or writing a small exploratory tool.

Use PoptartClient.get and PoptartClient.post from poptart when you want the app-facing client. Use poptart_xrpc directly only when you are building a lower-level transport wrapper.

Install

dart pub add poptart
import 'package:poptart/poptart.dart';

Anonymous GET

Use anonymous clients for public endpoints.

Future<Map<String, dynamic>> resolveHandle(String handle) async {
  final client = PoptartClient.anonymous();

  final response = await client.get<Map<String, dynamic>>(
    NSID.parse('com.atproto.identity.resolveHandle'),
    parameters: {'handle': handle},
  );

  return response.data;
}

Raw calls return the type you ask for. For exploratory endpoints, Map<String, dynamic> is usually the most flexible output type.

Authenticated GET

Use PoptartClient.fromSession or PoptartClient.fromOAuthSession when an endpoint needs auth.

Future<Map<String, dynamic>> getRepoStatus(Session session, String repo) async {
  final client = PoptartClient.fromSession(session);

  final response = await client.get<Map<String, dynamic>>(
    NSID.parse('com.atproto.sync.getRepoStatus'),
    parameters: {'did': repo},
  );

  return response.data;
}

The client adds the right authorization headers for the session type.

POST With A Body

Use post for XRPC procedures. The body can be a map or a generated model's toJson() result.

Future<Map<String, dynamic>> callCustomProcedure(
  Session session,
  String subject,
) async {
  final client = PoptartClient.fromSession(session);

  final response = await client.post<Map<String, dynamic>>(
    NSID.parse('com.example.moderation.markSubject'),
    body: {
      'subject': subject,
      'reason': 'reviewed',
    },
  );

  return response.data;
}

For stable AT Protocol repo writes, prefer generated descriptors and generated input models. Raw post is best when the method is not generated yet.

Override The Service

Poptart defaults to the public Bluesky AT Protocol services. Override the service when you are targeting a self-hosted PDS, a staging service, or a private XRPC server.

final client = PoptartClient.anonymous(
  service: 'https://pds.example.com',
);

final response = await client.get<Map<String, dynamic>>(
  NSID.parse('com.example.status.get'),
);

You can also override the service for a single call:

final response = await client.get<Map<String, dynamic>>(
  NSID.parse('com.example.status.get'),
  service: 'https://staging.example.com',
);

Convert A Raw Call To A Generated Call Later

Start raw while the lexicon is still moving:

final response = await client.get<Map<String, dynamic>>(
  NSID.parse('com.example.feed.getTimeline'),
  parameters: {'limit': 30},
);

After the lexicon is generated, switch to the generated method or descriptor:

import 'package:example_poptart/com/example/feed/get_timeline.dart'
    as get_timeline;

final response = await client.call(
  get_timeline.comExampleFeedGetTimeline,
  parameters: const get_timeline.FeedGetTimelineInput(limit: 30),
);

client.call(get_timeline.methodDescriptor, ...) is the same typed request with the descriptor spelled explicitly. Both forms give consumers typed inputs, typed responses, and generated JSON conversion.

Use poptart_xrpc Directly

Use poptart_xrpc directly when you do not want the higher-level Poptart client at all.

dart pub add poptart_xrpc
import 'dart:convert';

import 'package:poptart_xrpc/poptart_xrpc.dart' as xrpc;

Future<void> main() async {
  final response = await xrpc.procedure<String>(
    xrpc.NSID.create('server.atproto.com', 'createSession'),
    body: {
      'identifier': 'alice.example.com',
      'password': 'app-password',
    },
  );

  final session = jsonDecode(response.data) as Map<String, dynamic>;
  print(session['did']);
}

Most apps should not need this layer. It is for transport-focused packages, small experiments, and cases where you intentionally do not want generated clients.

When To Avoid Raw Calls

Avoid raw calls for stable, generated lexicons. Generated descriptors catch misspelled fields, produce typed output, and keep code easier to refactor when schemas change.

Avoid raw response maps deep in your app. Convert them near the boundary or promote the lexicon to generated code once the endpoint is part of your normal workflow.