amplify-flutter icon indicating copy to clipboard operation
amplify-flutter copied to clipboard

Appsync Events implementation for flutter SDK

Open matta-pie opened this issue 8 months ago • 10 comments

Would it be possible to add the implementation for publishing/subscribing to AWS AppSync Events endpoints?

There's already an implementation for all javascript based frameworks javascript doc

matta-pie avatar Apr 04 '25 13:04 matta-pie

I would also add that this would be very useful for us if it would be supported out of the box

jamilsaadeh97 avatar Apr 04 '25 14:04 jamilsaadeh97

Hello @matta-pie, thanks for taking the time to open this issue. We will keep this open as a feature parity request, please give your description a thumbs to help us gauge community interest.

In the meantime you might be able to use our aws_signature_v4 package to call AWS APIs Amplify doesn't covered yet.

tyllark avatar Apr 05 '25 09:04 tyllark

@tyllark could you explain more about the alternative solution you proposed? in particular i need to subscribe to an appsync event (namespace/channel) using JWT auth. (i guess auth type doesn't change much). from what im seeing, your proposed solution could work to send messages, not subscribing to events.

matta-pie avatar Apr 15 '25 06:04 matta-pie

Hello @matta-pie, I used an API key to test the following code, but you should be able to update the authorization mapping to match you preferred subprotocol format as documented here. While I used an API key for simplicity I recommend using Authorization in your production frontend code.

'authorization': {
  'x-api-key': _apiKey,
  'host': _httpDomain,
},

Your DNS Endpoints (and API key) can be found at AWS Console -> AWS AppSync -> APIs -> [Your Event API] -> Settings. I got my AWSCredentials via the aws cli command aws sts get-session-token. Finally you will need add web_socket_channel: ^3.0.2 and uuid: ^4.5.1 to your pubspec.yaml.

final String _httpDomain = '[YOUR_HTTP_DOMAIN]';
final String _realtimeDomain = '[YOUR_REALTIME_DOMAIN]';
final String _apiKey = '[YOUR_API_KEY]';

Future<void> eventApiExample() async {
  String channel = 'default/myChannel';
  final events = [
    '{"event 1" : "data 1"}',
    '{"event 2" : "data 2"}',
    '{"event 3" : "data 3"}',
    '{"event 4" : "data 4"}',
  ];

  final websocket = await _createEventApiWebsocket(channel);
  websocket.stream.listen(_onEvent);

  await _callEventApiRest(channel, events);
}

void _onEvent(dynamic event) {
  safePrint('Event Recieved: $event');
}

Future<void> _callEventApiRest(String channel, List<String> events) async {
  final signer = AWSSigV4Signer(
    credentialsProvider: AWSCredentialsProvider(
      AWSCredentials(
        AwsConstants.accessKey,
        AwsConstants.secretAccessKey,
        AwsConstants.sessionToken,
        AwsConstants.expiration,
      ),
    ),
  );

  final scope = AWSCredentialScope(
    region: AwsConstants.region,
    service: AWSService.appSync,
  );

  final jsonBody = json.encode({'channel': channel, 'events': events});
  final request = AWSHttpRequest(
    method: AWSHttpMethod.post,
    headers: {
      AWSHeaders.contentType: 'application/json',
      'x-api-key': _apiKey,
    },
    uri: Uri.https(_httpDomain, '/event'),
    body: jsonBody.codeUnits,
  );

  final signedRequest = await signer.sign(request, credentialScope: scope);

  final operation = signedRequest.send();
  final response = await operation.response;
  final body = await response.decodeBody();

  safePrint(response.statusCode);
  safePrint(response.headers);
  safePrint(body);
}

Future<WebSocketChannel> _createEventApiWebsocket(String channel) async {
  final signer = AWSSigV4Signer(
    credentialsProvider: AWSCredentialsProvider(
      AWSCredentials(
        AwsConstants.accessKey,
        AwsConstants.secretAccessKey,
        AwsConstants.sessionToken,
        AwsConstants.expiration,
      ),
    ),
  );

  final scope = AWSCredentialScope(
    region: AwsConstants.region,
    service: AWSService.appSync,
  );

  final jsonBody = json.encode({'channel': channel});
  final request = AWSHttpRequest(
    method: AWSHttpMethod.get,
    headers: {
      AWSHeaders.contentType: 'application/json',
      'x-api-key': _apiKey,
      'host': _httpDomain,
    },
    uri: Uri(scheme: 'wss', host: _realtimeDomain, path: '/event/realtime'),
    body: jsonBody.codeUnits,
  );

  final uri = await signer.presign(
    request,
    credentialScope: scope,
    expiresIn: Duration(hours: 1),
  );

  final headerJson = jsonEncode({'x-api-key': _apiKey, 'host': _httpDomain});
  var header = base64.encode(utf8.encode(headerJson));
  header = header.replaceAll('+', '-');
  header = header.replaceAll('/', '-');
  header = header.replaceAll('=', '');

  final websocket = WebSocketChannel.connect(
    uri,
    protocols: ['aws-appsync-event-ws', 'header-$header'],
  );
  await websocket.ready;

  final uuid = Uuid();
  final subscribe = {
    'type': 'subscribe',
    'id': uuid.v4(),
    'channel': channel,
    'authorization': {
      'x-api-key': _apiKey,
      'host': _httpDomain,
    },
  };

  websocket.sink.add(jsonEncode(subscribe));

  final event = {
    'type': 'publish',
    'id': uuid.v4(),
    'channel': channel,
    'events': ['{"socket event 1" : "socket data 1"}',],
    'authorization': {
      'x-api-key': _apiKey,
      'host': _httpDomain,
    },
  };

  websocket.sink.add(jsonEncode(event));

  return websocket;
}

tyllark avatar Apr 16 '25 18:04 tyllark

is correct to expose the _apiKey on "frontend" code?, is it not sensitive?. Is this the prefered method? or do you recommend another one? @tyllark

abarone-btf avatar Apr 21 '25 22:04 abarone-btf

Hello @abarone-btf, for production frontend code I recommend using authorization rather than an API key, which you can read more about here. The code snippet I provided is only intended as a proof of concept for using Appsync Events with our Sigv4 package and should not be treated as production ready.

tyllark avatar Apr 21 '25 22:04 tyllark

Any update on the roadmap for this feature parity?

matta-pie avatar Jun 13 '25 07:06 matta-pie

Hello @matta-pie, Amplify Android recently released this feature, so I'm hopefully it will be prioritized for Amplify Flutter soon, but currently we do not have any updates.

tyllark avatar Jun 13 '25 19:06 tyllark

Hi @tyllark , Thanks for the update! 🙌 Just wondering if there have been any developments regarding this feature for Amplify Flutter. Do you have any idea when it might be available or if it's currently being worked on? Looking forward to any updates. Thanks again!

albertoestepa avatar Jul 23 '25 12:07 albertoestepa

@albertoestepa This feature is still sitting in our backlog. We will update the issue once we start work on it. Amplify Swift and Amplify Android released this feature, if you wanna use that for the time being.

Swift: https://docs.amplify.aws/swift/build-a-backend/data/connect-event-api/ Android: https://docs.amplify.aws/android/build-a-backend/data/connect-event-api/

There are currently no timelines on the development of this feature on Flutter. Please thumbs the issue to showcase your interest.

harsh62 avatar Jul 23 '25 14:07 harsh62