From ab4adb1f9d6f454ec9d04ab696c449fa7b63989c Mon Sep 17 00:00:00 2001 From: tjroach Date: Fri, 9 May 2025 14:42:50 -0400 Subject: [PATCH 1/3] Add Android AppSync Events Documentation --- .../data/connect-event-api/index.mdx | 556 +++++++++++++++++- 1 file changed, 555 insertions(+), 1 deletion(-) diff --git a/src/pages/[platform]/build-a-backend/data/connect-event-api/index.mdx b/src/pages/[platform]/build-a-backend/data/connect-event-api/index.mdx index e3e55757a7a..18040139e25 100644 --- a/src/pages/[platform]/build-a-backend/data/connect-event-api/index.mdx +++ b/src/pages/[platform]/build-a-backend/data/connect-event-api/index.mdx @@ -10,7 +10,8 @@ export const meta = { 'nextjs', 'react', 'react-native', - 'vue' + 'vue', + 'android' ] }; @@ -27,12 +28,179 @@ export function getStaticProps(context) { }; } + This guide walks through how you can connect to AWS AppSync Events using the Amplify library. + + + +This guide walks through how you can connect to AWS AppSync Events, with or without Amplify. + AWS AppSync Events lets you create secure and performant serverless WebSocket APIs that can broadcast real-time event data to millions of subscribers, without you having to manage connections or resource scaling. With this feature, you can build multi-user features such as a collaborative document editors, chat apps, and live polling systems. Learn more about AWS AppSync Events by visiting the [Developer Guide](https://docs.aws.amazon.com/appsync/latest/eventapi/event-api-welcome.html). +## Install the AWS AppSync Events library + + + + + + + +Add the `aws-sdk-appsync-events` dependency to your app/build.gradle.kts file. + +```kotlin title="app/build.gradle.kts" +dependencies { + // highlight-start + // Adds the AWS AppSync Events library without adding any Amplify dependencies + implementation("com.amazonaws:aws-sdk-appsync-events:1.0.0") + // highlight-end +} +``` + + + + + +Add the `aws-sdk-appsync-events` and `aws-sdk-appsync-amplify` dependencies to your app/build.gradle.kts file. + +```kotlin title="app/build.gradle.kts" +dependencies { + // highlight-start + // Adds the AWS AppSync Events library + implementation("com.amazonaws:aws-sdk-appsync-events:1.0.0") + // Contains AppSync Authorizers which connect to Amplify Auth + implementation("com.amazonaws:aws-sdk-appsync-amplify:1.0.0") + // highlight-end +} +``` + + + + + +### Providing AppSync Authorizers + + + + + +The AWS AppSync Events library imports a number of Authorizer classes to match the various authorization strategies that may be used for your Events API. You should choose the appropriate Authorizer type for your authorization strategy. + +* `apiKey` authorization, **APIKeyAuthorizer** +* `identityPool` authorization, **IAMAuthorizer** +* `userPool` authorization, **AuthTokenAuthorizer** + +You can create as many Events clients as necessary if you require multiple authorization types. +#### API KEY + +An `ApiKeyAuthorizer` can be used with a hardcoded API key or by fetching the key from some source.: + +```kotlin +// highlight-start +// Use a hard-coded API key +val authorizer = ApiKeyAuthorizer("[API_KEY]") +//highlight-end +// or +// highlight-start +// Fetch the API key from some source. This function may be called many times, +// so it should implement appropriate caching internally. +val authorizer = ApiKeyAuthorizer { fetchApiKey() } +//highlight-end +``` + +#### AMAZON COGNITO USER POOLS + +When working directly with AppSync, you must implement the token fetching yourself. + +```kotlin +// highlight-start +// Use your own token fetching. This function may be called many times, +// so it should implement appropriate caching internally. +val authorizer = AuthTokenAuthorizer { + fetchLatestAuthToken() +} +//highlight-end +``` + +#### AWS IAM + +When working directly with AppSync, you must implement the request signing yourself. + +```kotlin +// highlight-start +// Provide an implementation of the signing function. This function should implement the +// AWS Sig-v4 signing logic and return the authorization headers containing the token and signature. +val authorizer = IamAuthorizer { appSyncRequest -> signRequestAndReturnHeaders(appSyncRequest) } +// highlight-end +``` + + + + +The AWS AppSync Events library imports a number of Authorizer classes to match the various authorization strategies that may be used for your Events API. You should choose the appropriate Authorizer type for your authorization strategy. + +* `apiKey` authorization, **APIKeyAuthorizer** +* `identityPool` authorization, **AmplifyIAMAuthorizer** +* `userPool` authorization, **AmplifyUserPoolAuthorizer** + +You can create as many Events clients as necessary if you require multiple authorization types. +#### API KEY + +An `ApiKeyAuthorizer` can provide a hardcoded API key, or fetch the API key from some source: + +```kotlin +// highlight-start +// Use a hard-coded API key +val authorizer = ApiKeyAuthorizer("[API_KEY]") +//highlight-end +// or +// highlight-start +// Fetch the API key from some source. This function may be called many times, +// so it should implement appropriate caching internally. +val authorizer = ApiKeyAuthorizer { fetchApiKey() } +//highlight-end +``` + +#### AMAZON COGNITO USER POOLS + +```kotlin +// highlight-start +// Using the provided Amplify UserPool Authorizer +val authorizer = AmplifyUserPoolAuthorizer() +//highlight-end +// or +// highlight-start +// Use your own token fetching. This function may be called many times, +// so it should implement appropriate caching internally. +val authorizer = AuthTokenAuthorizer { + fetchLatestAuthToken() +} +//highlight-end +``` + +#### AWS IAM + +```kotlin +// highlight-start +// Using the provided Amplify IAM Authorizer +val authorizer = AmplifyIamAuthorizer("{REGION}") +//highlight-end +// or +// highlight-start +// Write your own IAM signing. +// Provide an implementation of the signing function. This function should implement the +// AWS Sig-v4 signing logic and return the authorization headers containing the token and signature. +val authorizer = IamAuthorizer { appSyncRequest -> signRequestAndReturnHeaders(appSyncRequest) } +//highlight-end +``` + + + + + + ## Connect to an Event API without an existing Amplify backend Before you begin, you will need: @@ -40,6 +208,12 @@ Before you begin, you will need: - An Event API created via the AWS Console - Take note of: HTTP endpoint, region, API Key + +Thats it! Skip to [Client Library Usage Guide](#client-library-usage-guide). + + + + ```tsx title="src/App.tsx" import type { EventsChannel } from 'aws-amplify/data'; import { useState, useEffect, useRef } from 'react'; @@ -110,6 +284,7 @@ export default function App() { ); } ``` + ## Add an Event API to an existing Amplify backend @@ -124,6 +299,7 @@ Before you begin, you will need: First, we'll add a new Event API to our backend definition. + ```ts title="amplify/backend.ts" import { defineBackend } from '@aws-amplify/backend'; import { auth } from './auth/resource'; @@ -204,6 +380,79 @@ backend.addOutput({ }); // highlight-end ``` + + + +```ts title="amplify/backend.ts" +import { defineBackend } from '@aws-amplify/backend'; +import { auth } from './auth/resource'; +// highlight-start +// import CDK resources: +import { + CfnApi, + CfnChannelNamespace, + AuthorizationType, +} from 'aws-cdk-lib/aws-appsync'; +import { Policy, PolicyStatement } from 'aws-cdk-lib/aws-iam'; +// highlight-end + +const backend = defineBackend({ + auth, +}); + +// highlight-start +// create a new stack for our Event API resources: +const customResources = backend.createStack('custom-resources'); + +// add a new Event API to the stack: +const cfnEventAPI = new CfnApi(customResources, 'CfnEventAPI', { + name: 'my-event-api', + eventConfig: { + authProviders: [ + { + authType: AuthorizationType.USER_POOL, + cognitoConfig: { + awsRegion: customResources.region, + // configure Event API to use the Cognito User Pool provisioned by Amplify: + userPoolId: backend.auth.resources.userPool.userPoolId, + }, + }, + ], + // configure the User Pool as the auth provider for Connect, Publish, and Subscribe operations: + connectionAuthModes: [{ authType: AuthorizationType.USER_POOL }], + defaultPublishAuthModes: [{ authType: AuthorizationType.USER_POOL }], + defaultSubscribeAuthModes: [{ authType: AuthorizationType.USER_POOL }], + }, +}); + +// create a default namespace for our Event API: +const namespace = new CfnChannelNamespace( + customResources, + 'CfnEventAPINamespace', + { + apiId: cfnEventAPI.attrApiId, + name: 'default', + } +); + +// attach a policy to the authenticated user role in our User Pool to grant access to the Event API: +backend.auth.resources.authenticatedUserIamRole.attachInlinePolicy( + new Policy(customResources, 'AppSyncEventPolicy', { + statements: [ + new PolicyStatement({ + actions: [ + 'appsync:EventConnect', + 'appsync:EventSubscribe', + 'appsync:EventPublish', + ], + resources: [`${cfnEventAPI.attrApiArn}/*`, `${cfnEventAPI.attrApiArn}`], + }), + ], + }) +); +// highlight-end +``` + ### Deploy Backend @@ -213,6 +462,7 @@ To test your changes, deploy your Amplify Sandbox. npx ampx sandbox ``` + ### Connect your frontend application After the sandbox deploys, connect your frontend application to the Event API. We'll be using the [Amplify Authenticator component](https://ui.docs.amplify.aws/react/connected-components/authenticator) to sign in to our Cognito User Pool. @@ -283,3 +533,307 @@ export default function App() { ); } ``` + + + +## Client Library Usage Guide + +### Create the Events class + +You can find your endpoint in the AWS AppSync Events console. It should start with `https` and end with `/event`. + +```kotlin +val endpoint = "https://abcdefghijklmnopqrstuvwxyz.appsync-api.us-east-1.amazonaws.com/event" +val events = Events(endpoint) +``` + +### Using the REST Client +An `EventsRestClient` can be created to publish event(s) over REST. It accepts a publish authorizer that will be used by default for any publish calls within the client. + +#### Creating the REST Client +``` kotlin +val events: Events // Your configured Events +val restClient = events.createRestClient( + publishAuthorizer = ApiKeyAuthorizer("da2-abcdefghijklmnopqrstuvwxyz") +) +``` + +Additionally, you can pass custom options to the Rest Client. Current capabilities include modifying the `OkHttp.Builder` the `EventsRestClient` uses, and enabling client library logs. +See [Collecting Client Library Logs](#collecting-client-library-logs) for the AndroidLogger class. + +``` kotlin +val restClient = events.createRestClient( + publishAuthorizer = ApiKeyAuthorizer("da2-abcdefghijklmnopqrstuvwxyz"), + options = Events.Options.Rest( + loggerProvider = { namespace -> AndroidLogger(namespace, logLevel) }, + okHttpConfigurationProvider = { okHttpBuilder -> + // update OkHttp.Builder used by EventsRestClient + } + ) +) +``` + +#### Publish a Single Event +```kotlin +val restClient: EventsRestClient // Your configured EventsRestClient +// List of kotlinx.serialization.json.[JsonElement, JsonPrimitive, JsonArray, JsonObject] +val jsonEvent = JsonObject(mapOf("some" to JsonPrimitive("data"))) +val publishResult = restClient.publish( + channelName = "default/channel", + event = jsonEvent +) +when(publishResult) { + is PublishResult.Response -> { + publishResult.successfulEvents // inspect successful events + publishResult.failedEvents // inspect failed events + } + is PublishResult.Failure -> { + result.error // publish failure, inspect error + } +} +``` + +#### Publish multiple events +You can publish up to 5 events at a time. + +```kotlin +val restClient: EventsRestClient // Your configured EventsRestClient +// List of kotlinx.serialization.json.[JsonElement, JsonPrimitive, JsonArray, JsonObject] +val jsonEvents = listOf( + JsonObject(mapOf("some" to JsonPrimitive("data1"))), + JsonObject(mapOf("some" to JsonPrimitive("data2"))) +) +val publishResult = restClient.publish( + channelName = "default/channel", + events = jsonEvents +) +when(publishResult) { + is PublishResult.Response -> { + publishResult.successfulEvents // inspect successful events + publishResult.failedEvents // inspect failed events + } + is PublishResult.Failure -> { + result.error // publish failure, inspect error + } +} +``` + +#### Publish with a different authorizer +```kotlin +restClient.publish( + channelName = "default/channel", + event = JsonObject(mapOf("some" to JsonPrimitive("data"))), + authorizer = ApiKeyAuthorizer("da2-abcdefghijklmnopqrstuvwxyz") +) +``` + +### Using the WebSocket Client +An `EventsWebSocketClient` can be created to publish and subscribe to channels. The WebSocket connection is managed by the library and connects on the first subscribe or publish operation. Once connected, the WebSocket will remain open. You should explicitly disconnect the client when you no longer need to subscribe or publish to channels. + +#### Creating the WebSocket Client +```kotlin +val events: Events // Your configured Events +val apiKeyAuthorizer = ApiKeyAuthorizer("da2-abcdefghijklmnopqrstuvwxyz") +val webSocketClient = events.createWebSocketClient( + connectAuthorizer = apiKeyAuthorizer, // used to connect the websocket + subscribeAuthorizer = apiKeyAuthorizer, // used for subscribe calls + publishAuthorizer = apiKeyAuthorizer // used for publish calls +) +``` + +Additionally, you can pass custom options to the WebSocket Client. Current capabilities include modifying the `OkHttp.Builder` the `EventsWebSocketClient` uses, and enabling client library logs. +See [Collecting Client Library Logs](#collecting-client-library-logs) for the AndroidLogger class. + +```kotlin +val events: Events // Your configured Events +val apiKeyAuthorizer = ApiKeyAuthorizer("da2-abcdefghijklmnopqrstuvwxyz") +val webSocketClient = events.createWebSocketClient( + connectAuthorizer = apiKeyAuthorizer, // used to connect the websocket + subscribeAuthorizer = apiKeyAuthorizer, // used for subscribe calls + publishAuthorizer = apiKeyAuthorizer // used for publish calls, + options = Events.Options.WebSocket( + loggerProvider = { namespace -> AndroidLogger(namespace, logLevel) }, + okHttpConfigurationProvider = { okHttpBuilder -> + // update OkHttpBuilder used by EventsWebSocketClient + } + ) +) +``` + +#### Publish a Single Event +```kotlin +val webSocketClient: EventsWebSocketClient // Your configured EventsWebSocketClient +// kotlinx.serialization.json.[JsonElement, JsonPrimitive, JsonArray, JsonObject] +val jsonEvent = JsonObject(mapOf("some" to JsonPrimitive("data"))) +val publishResult = webSocketClient.publish( + channelName = "default/channel", + event = jsonEvent +) +when(publishResult) { + is PublishResult.Response -> { + publishResult.successfulEvents // inspect successful events + publishResult.failedEvents // inspect failed events + } + is PublishResult.Failure -> { + result.error // publish failure, inspect error + } +} +``` + +#### Publish multiple Events +You can publish up to 5 events at a time. + +```kotlin +val webSocketClient: EventsWebSocketClient // Your configured EveEventsWebSocketClientntsRestClient +// List of kotlinx.serialization.json.[JsonElement, JsonPrimitive, JsonArray, JsonObject] +val jsonEvents = listOf( + JsonObject(mapOf("some" to JsonPrimitive("data1"))), + JsonObject(mapOf("some" to JsonPrimitive("data2"))) +) +val publishResult = webSocketClient.publish( + channelName = "default/channel", + events = jsonEvents +) +when(publishResult) { + is PublishResult.Response -> { + publishResult.successfulEvents // inspect successful events + publishResult.failedEvents // inspect failed events + } + is PublishResult.Failure -> { + result.error // publish failure, inspect error + } +} +``` + +#### Publish with a different authorizer +```kotlin +val webSocketClient: EventsWebSocketClient // Your configured EventsWebSocketClient +val jsonEvent = JsonObject(mapOf("some" to JsonPrimitive("data"))) +val publishResult = webSocketClient.publish( + channelName = "default/channel", + event = jsonEvent, + authorizer = ApiKeyAuthorizer("da2-abcdefghijklmnopqrstuvwxyz") +) +when(publishResult) { + is PublishResult.Response -> { + publishResult.successfulEvents // inspect successful events + publishResult.failedEvents // inspect failed events + } + is PublishResult.Failure -> { + result.error // publish failure, inspect error + } +} +``` + +#### Subscribing to a channel. + +When subscribing to a channel, you can subscribe to a specific namespace/channel (ex: "default/channel"), or you can specify a wildcard (`*`) at the end of a channel path to receive events published to all channels that match (ex: "default/*"). + +Choosing the proper Coroutine Scope for the subscription Flow is critical. You should choose a scope to live for the lifetime in which you need the subscription. For example, if you want a subscription to be tied to a screen, you should consider using `viewModelScope` from an AndroidX `ViewModel`. The subscription Flow would persist configuration changes and be cancelled when `onCleared()` is triggered on the ViewModel. +When the Flow's Corutine Scope is cancelled, the library will unsubscribe from the channel. + +```kotlin +coroutineScope.launch { + val subscription: Flow = webSocketClient.subscribe("default/channel").onCompletion { + // Subscription has been unsubscribed + }.catch { throwable -> + // Subsciption encountered an error and has been unsubscribed + // See throwable for cause + } + + // The subscription is not sent until the Flow is collected. + subscription.collect { eventsMessage -> + // Returns a JsonElement type. + // Use Kotlin Serialization to convert into your preferred data structure. + val jsonData = eventsMessage.data + } +} +``` + +#### Subscribing to a channel with a different authorizer. + +```kotlin +coroutineScope.launch { + val subscription: Flow = webSocketClient.subscribe( + channelName = "default/channel". + authorizer = ApiKeyAuthorizer("da2-abcdefghijklmnopqrstuvwxyz") + ).onCompletion { + // Subscription has been unsubscribed + }.catch { throwable -> + // Subsciption encountered an error and has been unsubscribed + // See throwable for cause + } + + // The subscription is not sent until the Flow is collected. + subscription.collect { eventsMessage -> + // Returns a JsonElement type. + val jsonData = eventsMessage.data + // Use Kotlin Serialization to convert into your preferred data structure. + } +} +``` + +#### Disconnecting the WebSocket +When you are done using the WebSocket and do not intend to call publish/subscribe on the client, you should disconnect the WebSocket. This will unsubscribe all channels. + +```kotlin +val webSocketClient: EventsWebSocketClient // Your configured EventsWebSocketClient +// set flushEvents to true if you want to wait for any pending publish operations to post to the WebSocket +// set flushEvents to false to immediately disconnect, discarding any pending posts to the WebSocet +webSocketClient.disconnect(flushEvents = true) // or false to immediately disconnect +``` + +### Collecting Client Library Logs + +In the Rest Client and WebSocket Client examples, we demonstrated logging to a custom logger. Here is an example of a custom logger that writes logs to Android's Logcat. You are free to implement your own `Logger` type. + +```kotlin +class AndroidLogger( + private val namespace: String, + override val thresholdLevel: LogLevel +) : Logger { + + override fun error(message: String) { + if (!thresholdLevel.above(LogLevel.ERROR)) { + Log.e(namespace, message) + } + } + + override fun error(message: String, error: Throwable?) { + if (!thresholdLevel.above(LogLevel.ERROR)) { + Log.e(namespace, message, error) + } + } + + override fun warn(message: String) { + if (!thresholdLevel.above(LogLevel.WARN)) { + Log.w(namespace, message) + } + } + + override fun warn(message: String, issue: Throwable?) { + if (!thresholdLevel.above(LogLevel.WARN)) { + Log.w(namespace, message, issue) + } + } + + override fun info(message: String) { + if (!thresholdLevel.above(LogLevel.INFO)) { + Log.i(namespace, message) + } + } + + override fun debug(message: String) { + if (!thresholdLevel.above(LogLevel.DEBUG)) { + Log.d(namespace, message) + } + } + + override fun verbose(message: String) { + if (!thresholdLevel.above(LogLevel.VERBOSE)) { + Log.v(namespace, message) + } + } +} +``` + From 901caca1e33a242506297c5daa67f0503220decd Mon Sep 17 00:00:00 2001 From: tjroach Date: Wed, 14 May 2025 12:55:44 -0400 Subject: [PATCH 2/3] set variable for AppSync Android Libs --- src/components/MDXComponents/MDXCode.tsx | 4 ++++ src/constants/versions.ts | 3 ++- .../data/connect-event-api/index.mdx | 16 ++++++++-------- 3 files changed, 14 insertions(+), 9 deletions(-) diff --git a/src/components/MDXComponents/MDXCode.tsx b/src/components/MDXComponents/MDXCode.tsx index 9212561120d..9622166332b 100644 --- a/src/components/MDXComponents/MDXCode.tsx +++ b/src/components/MDXComponents/MDXCode.tsx @@ -31,6 +31,10 @@ const addVersions = (code: string) => { /ANDROID_AUTHENTICATOR_VERSION/g, versions.ANDROID_AUTHENTICATOR_VERSION ); + code = code.replace( + /ANDROID_APPSYNC_SDK_VERSION/g, + versions.ANDROID_APPSYNC_SDK_VERSION + ); return code; }; diff --git a/src/constants/versions.ts b/src/constants/versions.ts index dacf5ba9a81..6d4685b7456 100644 --- a/src/constants/versions.ts +++ b/src/constants/versions.ts @@ -6,5 +6,6 @@ export const versions = { ANDROID_V1_KOTLIN_VERSION: '0.22.8', ANDROID_SDK_VERSION: '2.76.0', KOTLIN_SDK_VERSION: '1.3.31', - ANDROID_AUTHENTICATOR_VERSION: '1.4.0' + ANDROID_AUTHENTICATOR_VERSION: '1.4.0', + ANDROID_APPSYNC_SDK_VERSION: '1.0.0' }; diff --git a/src/pages/[platform]/build-a-backend/data/connect-event-api/index.mdx b/src/pages/[platform]/build-a-backend/data/connect-event-api/index.mdx index 18040139e25..23c5d9d77c3 100644 --- a/src/pages/[platform]/build-a-backend/data/connect-event-api/index.mdx +++ b/src/pages/[platform]/build-a-backend/data/connect-event-api/index.mdx @@ -54,7 +54,7 @@ Add the `aws-sdk-appsync-events` dependency to your app/build.gradle.kts file. dependencies { // highlight-start // Adds the AWS AppSync Events library without adding any Amplify dependencies - implementation("com.amazonaws:aws-sdk-appsync-events:1.0.0") + implementation("com.amazonaws:aws-sdk-appsync-events:ANDROID_APPSYNC_SDK_VERSION") // highlight-end } ``` @@ -69,9 +69,9 @@ Add the `aws-sdk-appsync-events` and `aws-sdk-appsync-amplify` dependencies to y dependencies { // highlight-start // Adds the AWS AppSync Events library - implementation("com.amazonaws:aws-sdk-appsync-events:1.0.0") + implementation("com.amazonaws:aws-sdk-appsync-events:ANDROID_APPSYNC_SDK_VERSION") // Contains AppSync Authorizers which connect to Amplify Auth - implementation("com.amazonaws:aws-sdk-appsync-amplify:1.0.0") + implementation("com.amazonaws:aws-sdk-appsync-amplify:ANDROID_APPSYNC_SDK_VERSION") // highlight-end } ``` @@ -684,7 +684,7 @@ when(publishResult) { You can publish up to 5 events at a time. ```kotlin -val webSocketClient: EventsWebSocketClient // Your configured EveEventsWebSocketClientntsRestClient +val webSocketClient: EventsWebSocketClient // Your configured EventsWebSocketClient // List of kotlinx.serialization.json.[JsonElement, JsonPrimitive, JsonArray, JsonObject] val jsonEvents = listOf( JsonObject(mapOf("some" to JsonPrimitive("data1"))), @@ -730,14 +730,14 @@ when(publishResult) { When subscribing to a channel, you can subscribe to a specific namespace/channel (ex: "default/channel"), or you can specify a wildcard (`*`) at the end of a channel path to receive events published to all channels that match (ex: "default/*"). Choosing the proper Coroutine Scope for the subscription Flow is critical. You should choose a scope to live for the lifetime in which you need the subscription. For example, if you want a subscription to be tied to a screen, you should consider using `viewModelScope` from an AndroidX `ViewModel`. The subscription Flow would persist configuration changes and be cancelled when `onCleared()` is triggered on the ViewModel. -When the Flow's Corutine Scope is cancelled, the library will unsubscribe from the channel. +When the Flow's Coroutine Scope is cancelled, the library will unsubscribe from the channel. ```kotlin coroutineScope.launch { val subscription: Flow = webSocketClient.subscribe("default/channel").onCompletion { // Subscription has been unsubscribed }.catch { throwable -> - // Subsciption encountered an error and has been unsubscribed + // Subscription encountered an error and has been unsubscribed // See throwable for cause } @@ -760,7 +760,7 @@ coroutineScope.launch { ).onCompletion { // Subscription has been unsubscribed }.catch { throwable -> - // Subsciption encountered an error and has been unsubscribed + // Subscription encountered an error and has been unsubscribed // See throwable for cause } @@ -779,7 +779,7 @@ When you are done using the WebSocket and do not intend to call publish/subscrib ```kotlin val webSocketClient: EventsWebSocketClient // Your configured EventsWebSocketClient // set flushEvents to true if you want to wait for any pending publish operations to post to the WebSocket -// set flushEvents to false to immediately disconnect, discarding any pending posts to the WebSocet +// set flushEvents to false to immediately disconnect, discarding any pending posts to the WebSocket webSocketClient.disconnect(flushEvents = true) // or false to immediately disconnect ``` From a3f94fa4935450b427f955749b56262c40755250 Mon Sep 17 00:00:00 2001 From: tjroach Date: Wed, 14 May 2025 16:03:30 -0400 Subject: [PATCH 3/3] pr comments --- .../data/connect-event-api/index.mdx | 73 ++++++++++--------- 1 file changed, 38 insertions(+), 35 deletions(-) diff --git a/src/pages/[platform]/build-a-backend/data/connect-event-api/index.mdx b/src/pages/[platform]/build-a-backend/data/connect-event-api/index.mdx index 23c5d9d77c3..b852a1efd74 100644 --- a/src/pages/[platform]/build-a-backend/data/connect-event-api/index.mdx +++ b/src/pages/[platform]/build-a-backend/data/connect-event-api/index.mdx @@ -40,9 +40,8 @@ AWS AppSync Events lets you create secure and performant serverless WebSocket AP Learn more about AWS AppSync Events by visiting the [Developer Guide](https://docs.aws.amazon.com/appsync/latest/eventapi/event-api-welcome.html). -## Install the AWS AppSync Events library - +## Install the AWS AppSync Events library @@ -93,6 +92,7 @@ The AWS AppSync Events library imports a number of Authorizer classes to match t * `userPool` authorization, **AuthTokenAuthorizer** You can create as many Events clients as necessary if you require multiple authorization types. + #### API KEY An `ApiKeyAuthorizer` can be used with a hardcoded API key or by fetching the key from some source.: @@ -146,6 +146,7 @@ The AWS AppSync Events library imports a number of Authorizer classes to match t * `userPool` authorization, **AmplifyUserPoolAuthorizer** You can create as many Events clients as necessary if you require multiple authorization types. + #### API KEY An `ApiKeyAuthorizer` can provide a hardcoded API key, or fetch the API key from some source: @@ -165,35 +166,24 @@ val authorizer = ApiKeyAuthorizer { fetchApiKey() } #### AMAZON COGNITO USER POOLS +The `AmplifyUserPoolAuthorizer` uses your configured Amplify instance to fetch AWS Cognito UserPool tokens and attach to the request. + ```kotlin // highlight-start // Using the provided Amplify UserPool Authorizer val authorizer = AmplifyUserPoolAuthorizer() //highlight-end -// or -// highlight-start -// Use your own token fetching. This function may be called many times, -// so it should implement appropriate caching internally. -val authorizer = AuthTokenAuthorizer { - fetchLatestAuthToken() -} -//highlight-end ``` #### AWS IAM +The `AmplifyIAMAuthorizer` uses your configured Amplify instance to sign a request with the IAM SigV4 protocol. + ```kotlin // highlight-start // Using the provided Amplify IAM Authorizer val authorizer = AmplifyIamAuthorizer("{REGION}") //highlight-end -// or -// highlight-start -// Write your own IAM signing. -// Provide an implementation of the signing function. This function should implement the -// AWS Sig-v4 signing logic and return the authorization headers containing the token and signature. -val authorizer = IamAuthorizer { appSyncRequest -> signRequestAndReturnHeaders(appSyncRequest) } -//highlight-end ``` @@ -548,9 +538,11 @@ val events = Events(endpoint) ``` ### Using the REST Client + An `EventsRestClient` can be created to publish event(s) over REST. It accepts a publish authorizer that will be used by default for any publish calls within the client. #### Creating the REST Client + ``` kotlin val events: Events // Your configured Events val restClient = events.createRestClient( @@ -574,9 +566,10 @@ val restClient = events.createRestClient( ``` #### Publish a Single Event + ```kotlin val restClient: EventsRestClient // Your configured EventsRestClient -// List of kotlinx.serialization.json.[JsonElement, JsonPrimitive, JsonArray, JsonObject] +// kotlinx.serialization.json.[JsonElement, JsonPrimitive, JsonArray, JsonObject] val jsonEvent = JsonObject(mapOf("some" to JsonPrimitive("data"))) val publishResult = restClient.publish( channelName = "default/channel", @@ -584,16 +577,17 @@ val publishResult = restClient.publish( ) when(publishResult) { is PublishResult.Response -> { - publishResult.successfulEvents // inspect successful events - publishResult.failedEvents // inspect failed events + val successfulEvents = publishResult.successfulEvents // inspect successful events + val failedEvents = publishResult.failedEvents // inspect failed events } is PublishResult.Failure -> { - result.error // publish failure, inspect error + val error = result.error // publish failure, inspect error } } ``` #### Publish multiple events + You can publish up to 5 events at a time. ```kotlin @@ -609,16 +603,17 @@ val publishResult = restClient.publish( ) when(publishResult) { is PublishResult.Response -> { - publishResult.successfulEvents // inspect successful events - publishResult.failedEvents // inspect failed events + val successfulEvents = publishResult.successfulEvents // inspect successful events + val failedEvents = publishResult.failedEvents // inspect failed events } is PublishResult.Failure -> { - result.error // publish failure, inspect error + val error = result.error // publish failure, inspect error } } ``` #### Publish with a different authorizer + ```kotlin restClient.publish( channelName = "default/channel", @@ -628,9 +623,11 @@ restClient.publish( ``` ### Using the WebSocket Client + An `EventsWebSocketClient` can be created to publish and subscribe to channels. The WebSocket connection is managed by the library and connects on the first subscribe or publish operation. Once connected, the WebSocket will remain open. You should explicitly disconnect the client when you no longer need to subscribe or publish to channels. #### Creating the WebSocket Client + ```kotlin val events: Events // Your configured Events val apiKeyAuthorizer = ApiKeyAuthorizer("da2-abcdefghijklmnopqrstuvwxyz") @@ -661,6 +658,7 @@ val webSocketClient = events.createWebSocketClient( ``` #### Publish a Single Event + ```kotlin val webSocketClient: EventsWebSocketClient // Your configured EventsWebSocketClient // kotlinx.serialization.json.[JsonElement, JsonPrimitive, JsonArray, JsonObject] @@ -671,16 +669,17 @@ val publishResult = webSocketClient.publish( ) when(publishResult) { is PublishResult.Response -> { - publishResult.successfulEvents // inspect successful events - publishResult.failedEvents // inspect failed events + val successfulEvents = publishResult.successfulEvents // inspect successful events + val failedEvents = publishResult.failedEvents // inspect failed events } is PublishResult.Failure -> { - result.error // publish failure, inspect error + val error = result.error // publish failure, inspect error } } ``` #### Publish multiple Events + You can publish up to 5 events at a time. ```kotlin @@ -696,16 +695,17 @@ val publishResult = webSocketClient.publish( ) when(publishResult) { is PublishResult.Response -> { - publishResult.successfulEvents // inspect successful events - publishResult.failedEvents // inspect failed events + val successfulEvents = publishResult.successfulEvents // inspect successful events + val failedEvents = publishResult.failedEvents // inspect failed events } is PublishResult.Failure -> { - result.error // publish failure, inspect error + val error = result.error // publish failure, inspect error } } ``` #### Publish with a different authorizer + ```kotlin val webSocketClient: EventsWebSocketClient // Your configured EventsWebSocketClient val jsonEvent = JsonObject(mapOf("some" to JsonPrimitive("data"))) @@ -716,11 +716,11 @@ val publishResult = webSocketClient.publish( ) when(publishResult) { is PublishResult.Response -> { - publishResult.successfulEvents // inspect successful events - publishResult.failedEvents // inspect failed events + val successfulEvents = publishResult.successfulEvents // inspect successful events + val failedEvents = publishResult.failedEvents // inspect failed events } is PublishResult.Failure -> { - result.error // publish failure, inspect error + val error = result.error // publish failure, inspect error } } ``` @@ -734,6 +734,7 @@ When the Flow's Coroutine Scope is cancelled, the library will unsubscribe from ```kotlin coroutineScope.launch { + // subscribe returns a cold Flow. The subscription is established once a terminal operator is invoked. val subscription: Flow = webSocketClient.subscribe("default/channel").onCompletion { // Subscription has been unsubscribed }.catch { throwable -> @@ -741,7 +742,7 @@ coroutineScope.launch { // See throwable for cause } - // The subscription is not sent until the Flow is collected. + // collect starts the subscription subscription.collect { eventsMessage -> // Returns a JsonElement type. // Use Kotlin Serialization to convert into your preferred data structure. @@ -754,6 +755,7 @@ coroutineScope.launch { ```kotlin coroutineScope.launch { + // subscribe returns a cold Flow. The subscription is established once a terminal operator is invoked. val subscription: Flow = webSocketClient.subscribe( channelName = "default/channel". authorizer = ApiKeyAuthorizer("da2-abcdefghijklmnopqrstuvwxyz") @@ -764,7 +766,7 @@ coroutineScope.launch { // See throwable for cause } - // The subscription is not sent until the Flow is collected. + // collect starts the subscription subscription.collect { eventsMessage -> // Returns a JsonElement type. val jsonData = eventsMessage.data @@ -774,6 +776,7 @@ coroutineScope.launch { ``` #### Disconnecting the WebSocket + When you are done using the WebSocket and do not intend to call publish/subscribe on the client, you should disconnect the WebSocket. This will unsubscribe all channels. ```kotlin