A Package to send requests to HTTP/REST services.
Genesys Cloud CX Client Library in GO
Have a look at the examples/ folder for complete examples on how to use this library.
You first start by creating a gcloudcx.Client
that will allow to send requests to Genesys Cloud:
log := logger.Create("gcloudcx")
client := gcloudcx.NewClient(&gcloudcx.ClientOptions{
DeploymentID: "123abc0981234i0df8g0",
Logger: log,
})
You can also pass a context.Context with a logger.Logger:
log := logger.Create("gcloudcx")
client := gcloudcx.NewClient(&gcloudcx.ClientOptions{
Context: log.ToContext(context.Background())
DeploymentID: "123abc0981234i0df8g0",
})
This makes cascading logs into contexts a lot easier.
You can choose the authorization grant right away as well:
log := logger.Create("gcloudcx")
client := gcloudcx.NewClient(&gcloudcx.ClientOptions{
DeploymentID: "123abc0981234i0df8g0",
Logger: log,
}).SetAuthorizationGrant(&gcloudcx.AuthorizationCodeGrant{
ClientID: "hlkjshdgpiuy123387",
Secret: "879e8ugspojdgj",
RedirectURL: "http://my.acme.com/token",
})
Or,
log := logger.Create("gcloudcx")
client := gcloudcx.NewClient(&gcloudcx.ClientOptions{
DeploymentID: "123abc0981234i0df8g0",
Logger: log,
}).SetAuthorizationGrant(&gcloudcx.ClientCredentialsGrant{
ClientID: "jklsdufg89u9j234",
Secret: "sdfgjlskdfjglksdfjg",
})
As of today, Authorization Code and Client Credentials grants are implemented.
In the case of the Authorization Code, the best is to run a Webserver in your code and to handle the authentication requests in the router. The library provides two helpers to manage the authentication:
AuthorizeHandler()
that can be used to ensure a page has an authenticated client,LoggedInHandler()
that can be used in the RedirectURL to process the results of the authentication.
They can be used like this (using the gorilla/mux router, for example):
router := mux.NewRouter()
// This route is used as the RedirectURL of the client
router.Methods("GET").Path("/token").Handler(client.LoggedInHandler()(myhandler()))
authorizedRouter := router.PathPrefix("/").Subrouter()
authorizedRouter.Use(client.AuthorizeHandler())
// This is the main route of this application, we want a fully functional gcloudcx.Client
authorizedRouter.Methods("GET").Path("/").HandlerFunc(mainRouteHandler)
In you HttpHandler, the client will be available from the request's context:
func mainRouteHandler(w http.ResponseWriter, r *http.Request) {
client, err := gcloudcx.ClientFromContext(r.Context())
if err != nil {
core.RespondWithError(w, http.StatusServiceUnavailable, err)
return
}
// Let's get my organization here, as an example...
organization, err := client.GetMyOrganization(r.Context())
if err != nil {
core.RespondWithError(w, http.StatusServiceUnavailable, err)
return
}
core.RespondWithJSON(w, http.StatusOK, struct {
OrgName string `json:"organization"`
}{
OrgName: organization.String(),
})
}
When using the Client Credential grant, you can configure the grant to tell when the token gets updated, allowing you to store it and re-use it in the future:
var TokenUpdateChan = make(chan gcloudcx.UpdatedAccessToken)
func main() {
// ...
defer close(TokenUpdateChan)
go func() {
for {
data, opened := <-TokenUpdateChan
if !opened {
return
}
log.Printf("Received Token: %s\n", data.Token)
myID, ok := data.CustomData.(string)
if (!ok) {
log.Printf("Shoot....\n")
} else {
log.Printf("myID: %s\n", myID)
}
}
}()
client := gcloudcx.NewClient(&gcloudcx.ClientOptions{
// ...
}).SetAuthorizationGrant(&gcloudcx.ClientCredentialsGrant{
ClientID: "1234",
Secret: "s3cr3t",
TokenUpdated: TokenUpdateChan,
CustomData: "myspecialID",
Token: savedToken,
})
// do stuff with the client, etc.
}
As you can see, you can even pass some custom data (interface{}
, so anything really) to the grant and that data will be passed back to the func
that handles the chan
.
All functions that will end up calling the Genesys Cloud API must use a context as their first argument.
This allow developers to, eventually, control timeouts, and other things.
A useful pattern is to add a logger to the context with various records, when the library function executes, it will send its logs to that logger, thus using all the records that were set:
log := logger.Create("MYAPP").Record("coolId", myID)
user := client.GetMyUser(log.ToContext(someContext))
In the logs, you will see the value of coolId
in every line produced by client.GetMyUser.
If the context does not contain a logger, the client.Logger is used.
The library provides a Fetch
function that will fetch a resource from the Genesys Cloud API.
// Fetch a user by its ID
user, err := gcloudcx.Fetch[gcloud.User](context, client, userID) // userID is a uuid.UUID
You can use FetchBy
to fetch a resource by a specific field:
integration, err := gcloudcx.FetchBy(context, client, func (integration gcloudcx.OpenMessagingIntegration) bool {
return integration.Name == "My Integration"
})
Note: This method can be rather slow as it fetches all the resources of the type and then filters them.
You can also add query criteria to the FetchBy
function:
recipient, err := gcloudcx.FetchBy(
context,
client,
func (recipient gcloudcx.Recipient) bool {
return recipient.Name == "My Recipient"
},
gcloudcx.Query{
"messengerType": "open",
"pageSize": 100,
},
)
Finally, you can use FetchAll
to fetch all the resources of a type:
integrations, err := gcloudcx.FetchAll[gcloudcx.OpenMessagingIntegration](context, client)
Again, some query criteria can be added:
integrations, err := gcloudcx.FetchAll[gcloudcx.OpenMessagingIntegration](context, client, gcloudcx.Query{
"pageSize": 100,
})
You can also create a resource without fetching it:
user := gcloud.New[gcloud.User](context, client, id, log)
This will create a resource and set its ID, client, and log properly as needed.
The Genesys Cloud Notification API is accessible via the NotificationChannel
and NotificationTopic
types.
Processing incoming Notification Topics is simply done in a loop by reading the TopicReceived
Go chan.
Here is a quick example:
user, err := client.GetMyUser(context.Background())
if err != nil {
log.Errorf("Failed to retrieve my User", err)
panic(err)
}
notificationChannel, err := client.CreateNotificationChannel(context.Background())
if err != nil {
log.Errorf("Failed to create a notification channel", err)
panic(err)
}
topics, err := config.NotificationChannel.Subscribe(
context.Background(),
gcloudcx.UserPresenceTopic{}.With(user),
)
if err != nil {
log.Errorf("Failed to subscribe to topics", err)
panic(err)
}
log.Infof("Subscribed to topics: [%s]", strings.Join(topics, ","))
// Call the Genesys Cloud Notification Topic loop
go func() {
for {
select {
case receivedTopic := notificationChannel.TopicReceived:
if receivedTopic == nil {
log.Infof("Terminating Topic message loop")
return
}
switch topic := receivedTopic.(type) {
case gcloudcx.UserPresenceTopic:
log.Infof("User %s, Presence: %s", topic.User, topic.Presence)
default:
log.Warnf("Unknown topic: %s", topic)
}
case <-time.After(30 * time.Second):
// This timer makes sure the for loop does not execute too quickly
// when no topic is received for a while
log.Debugf("Nothing in the last 30 seconds")
}
}
}()
Responses canbe fetched, like any other resource, via the Fetch
function:
response, err := gcloudcx.Fetch[gcloudcx.Response](context, client, responseID)
Or via the dedicated FetchByFilter
function:
response, err = gcloudcx.ResponseManagementResponse{}.FetchByFilters(
context,
client,
gcloudcx.ResponseManagementQueryFilter{
Name: "name", Operator: "EQUALS", Values: []string{response.Name},
},
)
This calls the query API of Genesys Cloud, which is more efficient than fetching all the responses and then filtering them (FetchBy
method).
Responses can apply substitutions to provide the final text that can be sent to the user. Custom substitutions are provided as a map[string]string
to the func and default substitutions are provided by the resource itself.
text, err := response.ApplySubstitutions(context, "text/plain", map[string]string{
"firstName": "John",
"lastName": "Doe",
})
The second argument allows to specify the content type of the text to be returned, that content type has to be present in the resource, otherwise an error is returned. text/plain
and text/html
are supported by the Genesys Cloud API as of today.
ApplySubstitutions
supports both the Genesys Cloud substitutions ({{substitutionId}}
) and the Go Template system, which is far more powerful. See the Go Template documentation for more information. On top of the basic features, ApplySubstitutions
also provides the functions from the Sprig library, which is a collection of useful functions for templates. See the Sprig documentation for more information.
For example the following response will return the text Hello John Doe
:
response := gcloudcx.ResponseManagementResponse{ // This is a fake response, just to show the template
Texts: []gcloudcx.ResponseManagementContent{
{
ContentType: "text/plain",
Content: `Hello {{firstName}} {{lastName}}`, // Genesys Cloud substitutions
}
},
}
text, err := response.ApplySubstitutions(context, "text/plain", map[string]string{
"firstName": "John",
"lastName": "Doe",
})
assert.Equal(t, "Hello John Doe", text)
To get the same result with Go Templates:
response := gcloudcx.ResponseManagementResponse{ // This is a fake response, just to show the template
Texts: []gcloudcx.ResponseManagementContent{
{
ContentType: "text/plain",
Content: `Hello {{.firstName}} {{.lastName}}`, // Go Template substitutions
}
},
}
text, err := response.ApplySubstitutions(context, "text/plain", map[string]string{
"firstName": "John",
"lastName": "Doe",
})
assert.Equal(t, "Hello John Doe", text)
You can mix both approaches:
response := gcloudcx.ResponseManagementResponse{ // This is a fake response, just to show the template
Texts: []gcloudcx.ResponseManagementContent{
{
ContentType: "text/plain",
Content: `
Hello {{.firstName}} {{.lastName}}, you are on {{location}}! # Genesys Cloud and Go substitutions
{{if eq .location "Earth"}}You are on the right place!{{end}} # Go Template condition
And you are visiting {{ default "Atlantis" .country }}. # Sprig function
`,
}
},
Substitutions: []gcloudcx.ResponseManagementSubstitution{{
ID: "location",
Description: "The location of the person to greet",
Default: "Earth",
}},
}
text, err := response.ApplySubstitutions(context, "text/plain", map[string]string{
"firstName": "John",
"lastName": "Doe",
})
This library implements only a very small set of Genesys Cloud CX's API at the moment, but I keep adding stuff...