func main() { var deviceToken, filename, password, environment string flag.StringVar(&deviceToken, "d", "", "Device token") flag.StringVar(&filename, "c", "", "Path to p12 certificate file") flag.StringVar(&password, "p", "", "Password for p12 file.") flag.StringVar(&environment, "e", "development", "Environment") flag.Parse() cert, key, err := certificate.Load(filename, password) if err != nil { log.Fatal(err) } service := push.Service{ Client: push.NewClient(certificate.TLS(cert, key)), Host: push.Development, } if environment == "production" { service.Host = push.Production } p := payload.APS{ Alert: payload.Alert{Body: "Hello HTTP/2"}, Badge: badge.New(42), } id, err := service.Push(deviceToken, &push.Headers{}, p) if err != nil { log.Fatal(err) } log.Println("apns-id:", id) }
func main() { var filename, password string flag.StringVar(&filename, "c", "", "Path to p12 certificate file") flag.StringVar(&password, "p", "", "Password for p12 file.") flag.Parse() var err error cert, privateKey, err = certificate.Load(filename, password) if err != nil { log.Fatal(err) } service = push.Service{ Client: push.NewClient(certificate.TLS(cert, privateKey)), Host: push.Production, } r := mux.NewRouter() r.HandleFunc("/", indexHandler).Methods("GET") r.HandleFunc("/request", requestPermissionHandler) r.HandleFunc("/push", pushHandler) r.HandleFunc("/click", clickHandler).Methods("GET") // WebServiceURL endpoints r.HandleFunc("/v1/pushPackages/{websitePushID}", pushPackagesHandler).Methods("POST") r.HandleFunc("/v1/devices/{deviceToken}/registrations/{websitePushID}", registerDeviceHandler).Methods("POST") r.HandleFunc("/v1/devices/{deviceToken}/registrations/{websitePushID}", forgetDeviceHandler).Methods("DELETE") r.HandleFunc("/v1/log", logHandler).Methods("POST") http.ListenAndServe(":5000", r) }
func TestValidCert(t *testing.T) { const name = "../fixtures/cert.p12" _, _, err := certificate.Load(name, "") if err != nil { t.Fatal(err) } }
func TestExpiredCert(t *testing.T) { // TODO: figure out how to test certificate loading and validation in CI const name = "../cert-expired.p12" _, _, err := certificate.Load(name, "") if err != certificate.ErrExpired { t.Fatal("Expected expired cert error, got", err) } }
func TestValidCert(t *testing.T) { // TODO: figure out how to test certificate loading and validation in CI const name = "../cert.p12" _, err := certificate.Load(name, "") if err != nil { t.Fatal(err) } }
func TestNew(t *testing.T) { website := pushpackage.Website{ Name: "Bay Airlines", PushID: "web.com.example.domain", AllowedDomains: []string{"http://domain.example.com"}, URLFormatString: `http://domain.example.com/%@/?flight=%@`, AuthenticationToken: "19f8d7a6e9fb8a7f6d9330dabe", WebServiceURL: "https://example.com/push", } cert, privateKey, err := certificate.Load("../fixtures/cert.p12", "") if err != nil { t.Fatal(err) } buf := new(bytes.Buffer) pkg := pushpackage.New(buf) pkg.EncodeJSON("website.json", website) pkg.File("icon.iconset/[email protected]", "../fixtures/gopher.png") if err := pkg.Sign(cert, privateKey, nil); err != nil { t.Fatal(err) } expected := map[string]string{ "website.json": `{"websiteName":"Bay Airlines","websitePushID":"web.com.example.domain","allowedDomains":["http://domain.example.com"],"urlFormatString":"http://domain.example.com/%@/?flight=%@","authenticationToken":"19f8d7a6e9fb8a7f6d9330dabe","webServiceURL":"https://example.com/push"}`, "manifest.json": `{"icon.iconset/[email protected]":"5d31b7d2ea66ec7087c3789b2c6ca2aad67e459c","website.json":"8225d6cdd71f00888ff576aaab8d7ec4a27553c7"}`, } z, err := zip.NewReader(bytes.NewReader(buf.Bytes()), int64(buf.Len())) for _, f := range z.File { if exp, ok := expected[f.Name]; ok { b, err := zipReadFile(f) if err != nil { t.Fatal(err) } if string(b) != exp { t.Errorf("Unexpected content for %s: %s", f.Name, b) } } else { t.Log(f.Name) } } }
func main() { var filename, password, intermediate string flag.StringVar(&filename, "c", "", "Path to p12 certificate file") flag.StringVar(&password, "p", "", "Password for p12 file.") flag.StringVar(&intermediate, "i", "", "Path to WWDR intermediate .cer file") flag.Parse() cert, privateKey, err := certificate.Load(filename, password) failIfError(err) wwdr, err := loadWWDR(intermediate) failIfError(err) f, err := os.Create("Event.pkpass") failIfError(err) defer f.Close() passFiles := []string{ "pass.json", "background.png", "*****@*****.**", "icon.png", "*****@*****.**", "logo.png", "*****@*****.**", "thumbnail.png", "*****@*****.**", } pkg := pushpackage.New(f) for _, name := range passFiles { pkg.File(name, "./Event.pass/"+name) } err = pkg.Sign(cert, privateKey, wwdr) failIfError(err) }
func main() { var filename string var alert string var badgeInt int var sound string var category string var environmentString string var passphrase string var contentAvailable bool var payloadString string var payloadFile string var tokensFile string app := cli.NewApp() app.Name = "thunderstorm" // app.EnableBashCompletion = true app.Usage = "push TOKEN [...]" app.Version = "0.2.0" app.Commands = []cli.Command{ { Name: "push", Usage: "Sends an Apple Push Notification to specified devices", Flags: []cli.Flag{ cli.StringFlag{ Name: "certificate, c", Value: "", Usage: "Path to certificate (.p12) file", Destination: &filename, }, cli.StringFlag{ Name: "alert, m", Value: "", Usage: "Body of the alert to send in the push notification", Destination: &alert, }, cli.IntFlag{ Name: "badge, b", Usage: "Badge number to set with the push notification", Destination: &badgeInt, }, cli.StringFlag{ Name: "sound, s", Usage: "Sound to play with the notification", Destination: &sound, }, cli.StringFlag{ Name: "category, y", Usage: "Category of notification", Destination: &category, }, cli.StringFlag{ Name: "environment, e", Usage: "Environment to send push notification (production or development (default))", Destination: &environmentString, }, cli.StringFlag{ Name: "passphrase, p", Usage: "Provides the certificate passphrase", Destination: &passphrase, }, cli.BoolFlag{ Name: "content-available, n", Usage: "Indicates content available", Destination: &contentAvailable, }, cli.StringFlag{ Name: "payload, P", Usage: "JSON payload for notifications", Destination: &payloadString, }, cli.StringFlag{ Name: "payload-file, f", Usage: "Path of JSON file containing the payload to be sent", Destination: &payloadFile, }, cli.StringFlag{ Name: "tokens-path, t", Usage: "Path of JSON file containing the tokens", Destination: &tokensFile, }, }, // TODO: support low priority notifications // TODO: support expiration time // TODO: support id // TODO: support apns-collapse-id // TODO: support threads // TODO: support buford features https://github.com/RobotsAndPencils/buford // TODO: use a queue to send push notifications // TODO: support other parameters https://github.com/nomad/houston/blob/master/bin/apn Action: func(c *cli.Context) error { deviceToken := c.Args().First() cert, err := certificate.Load(filename, passphrase) if err != nil { // TODO detect if the error is // "pkcs12: expected exactly two safe bags in the PFX PDU" // and suggest to export the certificate from the keychain selecting // only one row log.Fatal(err) return err } // tls := certificate.TLS(cert, private) certificate.TopicFromCert(cert) commonName := cert.Leaf.Subject.CommonName bundle := strings.Replace(commonName, "Apple Push Services: ", "", 1) var environment = push.Development if environmentString == "production" { environment = push.Production } client, err := push.NewClient(cert) if err != nil { log.Fatal(err) } service := push.Service{ Client: client, Host: environment, } if payloadFile != "" { buffer, err := ioutil.ReadFile(payloadFile) if err == nil { payloadString = string(buffer) } } var p map[string]interface{} if payloadString != "" { error := json.Unmarshal([]byte(payloadString), &p) if error != nil { log.Fatal("Cannot parse payload") return err } } if p == nil { bUint := uint(badgeInt) // TODO: use Alert title, body and action or let it be with paylod // TODO: support mutable-content modifier pay := payload.APS{ Alert: payload.Alert{Body: alert}, Badge: badge.New(bUint), Sound: sound, Category: category, ContentAvailable: contentAvailable, } p = pay.Map() } headers := &push.Headers{ Topic: bundle, } tokensString := "" if tokensFile != "" { buffer, err := ioutil.ReadFile(tokensFile) if err == nil { tokensString = string(buffer) } var tokens []string if tokensString != "" { error := json.Unmarshal([]byte(tokensString), &tokens) if error != nil { log.Fatal("Cannot parse tokens array", error) return error } for _, token := range tokens { send(token, headers, p, &service) } } } else { send(deviceToken, headers, p, &service) } return nil }, }, } app.Run(os.Args) }
func TestMissingFile(t *testing.T) { _, _, err := certificate.Load("hide-and-seek.p12", "") if err == nil { t.Fatal("Expected file not found, got", err) } }