Skip to content Search Apps Documentation Source Content File Folder Download Copy Actions State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

Community realm. Read the code first. Community realm, deployed by its author. Read the code before you interact or send coins.

gnotif.gno

3.29 Kb · 131 lines
  1// Package gnotif is a registry of notification triggers. Each trigger names
  2// a realm and an event type to watch, and the text of the notification that
  3// gnotif servers send when a matching event lands on chain.
  4package gnotif
  5
  6import (
  7	"chain"
  8	"strconv"
  9	"strings"
 10
 11	"gno.land/p/nt/avl/v0"
 12	"gno.land/p/nt/seqid/v0"
 13)
 14
 15// Trigger is one declared notification rule. Filter holds comma-separated
 16// key=value pairs; Title, Body and Link are templates where {key} stands for
 17// the value of the event's attribute key.
 18type Trigger struct {
 19	ID, Target, Event, Filter, Param, Title, Body, Link string
 20	Declarer address
 21	Verified bool
 22}
 23
 24const (
 25	targetPrefix   = "gno.land/r/"
 26	maxTarget      = 256
 27	maxName        = 64
 28	maxFilterPairs = 8
 29	maxBody        = 255
 30	maxLink        = 256
 31)
 32
 33var (
 34	triggers avl.Tree // id → *Trigger
 35	lastID   seqid.ID
 36)
 37
 38// Declare stores a trigger and returns its id. The trigger is verified when
 39// its caller is the target realm itself.
 40func Declare(cur realm, target, event, filter, param, title, body, link string) string {
 41	validate(target, event, filter, param, title, body, link)
 42	caller := cur.Previous()
 43	tr := &Trigger{
 44		ID:       lastID.Next().String(),
 45		Target:   target,
 46		Event:    event,
 47		Filter:   filter,
 48		Param:    param,
 49		Title:    title,
 50		Body:     body,
 51		Link:     link,
 52		Declarer: caller.Address(),
 53		Verified: caller.IsCode() && caller.PkgPath() == target,
 54	}
 55	triggers.Set(tr.ID, tr)
 56	chain.Emit("TriggerDeclared",
 57		"id", tr.ID,
 58		"target", tr.Target,
 59		"event", tr.Event,
 60		"filter", tr.Filter,
 61		"param", tr.Param,
 62		"title", tr.Title,
 63		"body", tr.Body,
 64		"link", tr.Link,
 65		"declarer", tr.Declarer.String(),
 66		"verified", strconv.FormatBool(tr.Verified),
 67	)
 68	return tr.ID
 69}
 70
 71// Remove deletes a trigger. Only its declarer may remove it.
 72func Remove(cur realm, id string) {
 73	v := triggers.Get(id)
 74	if v == nil {
 75		panic("unknown trigger")
 76	}
 77	if v.(*Trigger).Declarer != cur.Previous().Address() {
 78		panic("not the declarer")
 79	}
 80	triggers.Remove(id)
 81	chain.Emit("TriggerRemoved", "id", id)
 82}
 83
 84func validate(target, event, filter, param, title, body, link string) {
 85	switch {
 86	case !strings.HasPrefix(target, targetPrefix) || len(target) > maxTarget:
 87		panic("invalid target")
 88	case !sized(event, 1, maxName):
 89		panic("invalid event")
 90	case !validFilter(filter):
 91		panic("invalid filter")
 92	case !sized(param, 0, maxName):
 93		panic("invalid param")
 94	case !sized(title, 1, maxName):
 95		panic("invalid title")
 96	case !sized(body, 0, maxBody):
 97		panic("invalid body")
 98	case !validLink(link):
 99		panic("invalid link")
100	}
101}
102
103func sized(s string, lo, hi int) bool {
104	return len(s) >= lo && len(s) <= hi
105}
106
107func validFilter(filter string) bool {
108	if filter == "" {
109		return true
110	}
111	pairs := strings.Split(filter, ",")
112	if len(pairs) > maxFilterPairs {
113		return false
114	}
115	for _, pair := range pairs {
116		key, value, ok := strings.Cut(pair, "=")
117		if !ok || !sized(key, 1, maxName) || !sized(value, 1, maxName) || strings.Contains(value, "=") {
118			return false
119		}
120	}
121	return true
122}
123
124// validLink accepts only a path, so a click always stays on the dapp's origin.
125// "//host" and "/\host" start with a slash yet resolve to another host.
126func validLink(link string) bool {
127	return sized(link, 1, maxLink) &&
128		strings.HasPrefix(link, "/") &&
129		!strings.HasPrefix(link, "//") &&
130		!strings.HasPrefix(link, "/\\")
131}