Publishers #

Publishers describe a service that receives and processes events generated server-side by various Hiro systems, such as achievements, economy, teams, and more. They act as listeners that respond to events, enabling developers to implement workflows like custom analytics tracking, custom rewards, or integrating with external services.

  • Publishers can receive events in batches as they are generated by Hiro systems.
  • Each Publisher may choose to process or ignore each event as it sees fit.
  • Publishers don’t accumulate (i.e. buffer) events.
  • Publisher implementations must safely handle concurrent calls and manage any errors or retries internally, as callers will not repeat calls in case of errors.

Use cases #

Publishers can be used to extend the capabilities of existing Hiro systems with custom workflows that go beyond the standard mechanisms.

To see an example of the Publisher in action, refer to the personalizer_satori.go implementation from the public Hiro library.

Extending existhng Hiro systems #

The Rewards system is a powerful feature that enables you to reward players in-game with currencies, energies, and other items. However, there are specific situations where Publishers can complement the Rewards system by enabling options that aren’t directly supported, such as updating player stats when certain events occur.

Here’s an example of using a Publisher to grant experience points when achievements are claimed:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
func (c *ExpRewardPublisher) Send(ctx context.Context, logger runtime.Logger,
    nk runtime.NakamaModule, userID string, events []*hiro.PublisherEvent) {
    for _, event := range events {
        // Only process achievement claim events
        if event.Name == "achievementClaimed" {
            // Find the achievement to check if it should reward exp.
            // Errors not shown but remember to implement proper error handling in production
            achievements, repeatedAchievements, err := c.systems.GetAchievementsSystem().GetAchievements(ctx, logger, nk, userID)

            // Look for the achievement in both regular and repeated collections
            achievement, ok := FindAchievement(event.SourceId, achievements, repeatedAchievements)

            // Check if this achievement is configured to reward exp via AdditionalProperties
            // This approach allows us to define exp rewards without modifying the Rewards system
            expStr, ok := achievement.AdditionalProperties["reward_exp"]
            if ok {
                // Award the experience points by updating the player's exp stat
                exp, err := strconv.ParseInt(expStr, 10, 64)

                // Update the player's exp stat through the Stats system
                privateStats := []*hiro.StatUpdate{{
                    Name:     "exp",
                    Value:    exp,
                    Operator: hiro.StatUpdateOperator_STAT_UPDATE_OPERATOR_DELTA,
                }}
            }
        }
    }
}

Custom event monitoring and logging #

Publishers can be used to monitor specific events for debugging or admin purposes:

1
2
3
4
5
6
7
func (l *CustomEventLogger) Send(ctx context.Context, logger runtime.Logger,
    nk runtime.NakamaModule, userID string, events []*hiro.PublisherEvent) {
    for _, event := range events {
        logger.Info("Event received: %v from user: %v",
            event.Name, userID)
    }
}

Publisher interface #

The Publisher interface defines two methods that must be implemented:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
type Publisher interface {
    // Authenticate is called every time a user authenticates with Hiro.
    // The 'created' flag is true if this is a newly created user account.
    Authenticate(ctx context.Context, logger runtime.Logger,
        nk runtime.NakamaModule, userID string, created bool)

    // Send is called when there are one or more events generated.
    Send(ctx context.Context, logger runtime.Logger,
        nk runtime.NakamaModule, userID string, events []*PublisherEvent)
}

PublisherEvent structure #

The PublisherEvent structure contains information about an event:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
type PublisherEvent struct {
    Name      string            // Event name
    Id        string            // Unique event identifier
    Timestamp int64             // Unix timestamp
    Metadata  map[string]string // Additional event data
    Value     string            // Event value
    System    System            // The Hiro system that generated this event
    SourceId  string            // Identifier of the event source
    Source    any               // Configuration of the event source
}