「労働者が自分の仕事をうまくやりたいなら、まず自分の道具を研ぎ澄まさなければなりません。」 - 孔子、「論語。陸霊公」
表紙 > プログラミング > 連絡先を携帯電話と同期するにはどうすればよいですか? Go に CardDAV を実装する!

連絡先を携帯電話と同期するにはどうすればよいですか? Go に CardDAV を実装する!

2024 年 11 月 7 日に公開
ブラウズ:789

How to synchronize your contacts with your phone? Implemeting CardDAV in Go!

あなたが小さな組織やクラブの管理を手伝っており、すべてのメンバーの詳細 (名前、電話番号、電子メールなど) を保存するデータベースがあるとします。
必要なときにどこでもこの最新情報にアクセスできたら便利だと思いませんか? CardDAV を使えばそれが可能です!

CardDAV は、十分にサポートされている連絡先管理のオープン スタンダードです。 iOS の連絡先アプリとネイティブに統合されており、Android では多くのアプリが利用可能です。

CardDAV を実装するサーバー側は、通常とは異なる http メソッド (GET、POST の代わりに PROPFIND、REPORT...) に応答する http サーバーです。幸いなことに、作業を大幅に簡素化する Go モジュールが存在します: github.com/emersion/go-webdav。このライブラリは実装されたバックエンドを想定しており、認証後に HTTP リクエストを処理する標準の http.Handler を提供します。

認証

興味深いことに、このライブラリはユーザー認証に関するヘルプを提供していませんが、Go のコンポーザビリティのおかげで、これは問題になりません。
CardDAV は Basic 認証資格情報を使用します。資格情報がチェックされたら、それらの資格情報をコンテキストに保存できます (後で役立ちます):

package main

import (
    "context"
    "net/http"

    "github.com/emersion/go-webdav/carddav"
)

type (
    ctxKey   struct{}
    ctxValue struct {
        username string
    }
)

func NewCardDAVHandler() http.Handler {
    actualHandler := carddav.Handler{
        Backend: &ownBackend{},
    }

    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        username, password, ok := r.BasicAuth()
        // check username and password: adjust the logic to your system (do NOT store passwords in plaintext)
        if !ok || username != "admin" || password != "s3cr3t" {
            // abort the request handling on failure
            w.Header().Add("WWW-Authenticate", `Basic realm="Please authenticate", charset="UTF-8"`)
            http.Error(w, "HTTP Basic auth is required", http.StatusUnauthorized)
            return
        }

        // user is authenticated: store this info in the context
        ctx := context.WithValue(r.Context(), ctxKey{}, ctxValue{username})
        // delegate the work to the CardDAV handle
        actualHandler.ServeHTTP(w, r.WithContext(ctx))
    })
}

CardDAV インターフェースの実装

ownBackend 構造体は、carddav.Backend インターフェイスを実装する必要があります。これはそれほど薄くはありませんが、それでも管理可能です。

CurrentUserPrincipal と AddressBookHomeSetPath は URL (スラッシュで始まりスラッシュで終わる) を提供する必要があります。通常はユーザー名/連絡先になります。ここは、コンテキスト (使用可能な唯一の引数) からユーザー名を抽出する必要がある場所です:

func currentUsername(ctx context.Context) (string, error) {
    if v, ok := ctx.Value(ctxKey{}).(ctxValue); ok {
        return v.username, nil
    }
    return "", errors.New("not authenticated")
}

type ownBackend struct{}

// must begin and end with a slash
func (b *ownBackend) CurrentUserPrincipal(ctx context.Context) (string, error) {
    username, err := currentUsername(ctx)
    return "/"   url.PathEscape(username)   "/", err
}

// must begin and end with a slash as well
func (b *ownBackend) AddressBookHomeSetPath(ctx context.Context) (string, error) {
    principal, err := b.CurrentUserPrincipal(ctx)
    return principal   "contacts/", err
}

その後、楽しい作業が始まります。AddressBook、GetAddressObject、および ListAddressObjects メソッドを実装する必要があります。

AddressBook は単純な構造体を返します。パスは上記の AddressBookHomeSetPath で始まり、スラッシュで終わる必要があります

GetAddressObject と ListAddressObjects は、(現在認証されているユーザーがそれらの連絡先にアクセスできることを確認するために) 現在のパスを確認し、連絡先を AddressObject として返す必要があります。

アドレスオブジェクト

AddressObject には複数の属性があります。最も重要なのは:

  • この特定の連絡先を識別するためのパス (任意であり、スラッシュで始まります)
  • クライアントが更新が行われたかどうかをすぐに確認できるようにする ETag (忘れると iOS は何も表示しません)
  • VCard を期待するカード

VCard は実際の連絡先データを表すため、連絡先の保存方法に応じて調整する必要がある可能性があります。私の場合、次のように終了しました:

func utf8Field(v string) *vcard.Field {
    return &vcard.Field{
        Value: v,
        Params: vcard.Params{
            "CHARSET": []string{"UTF-8"},
        },
    }
}

func vcardFromUser(u graphqlient.User) vcard.Card {
    c := vcard.Card{}

    c.Set(vcard.FieldFormattedName, utf8Field(u.Firstname " " u.Lastname))
    c.SetName(&vcard.Name{
        Field:      utf8Field(""),
        FamilyName: u.Lastname,
        GivenName:  u.Firstname,
    })
    c.SetRevision(u.UpdatedAt)
    c.SetValue(vcard.FieldUID, u.Extid)

    c.Set(vcard.FieldOrganization, utf8Field(u.Unit))

    // addFields sorts the key to ensure a stable order
    addFields := func(fieldName string, values map[string]string) {
        for _, k := range slices.Sorted(maps.Keys(values)) {
            v := values[k]
            c.Add(fieldName, &vcard.Field{
                Value: v,
                Params: vcard.Params{
                    vcard.ParamType: []string{k   ";CHARSET=UTF-8"}, // hacky but prevent maps ordering issues
                    // "CHARSET":       []string{"UTF-8"},
                },
            })
        }
    }

    addFields(vcard.FieldEmail, u.Emails)
    addFields(vcard.FieldTelephone, u.Phones)

    vcard.ToV4(c)
    return c
}

読み取り専用ショートカットを使用する

一部の方法では連絡先を更新できます。 CardDAV 経由でメンバー リストを更新したくないので、Put メソッドと Delete メソッドに 403 エラーを返します。 return webdav.NewHTTPError(http.StatusForbidden,errors.New("carddav:operation not support"))

ローカルでテストする

iOS では、CardDAV サーバーが https 経由でサービスを提供する必要があります。 openssl (192.168.XXX.XXX を IP アドレスに置き換えます) を使用して自己署名証明書をローカルで生成し、 http.ListenAndServeTLS(addr, "localhost.crt", "localhost.key", NewCardDAVHandler())

openssl req -new -subj "/C=US/ST=Utah/CN=192.168.XXX.XXX" -newkey rsa:2048 -nodes -keyout localhost.key -out localhost.csr
openssl x509 -req -days 365 -in localhost.csr -signkey localhost.key -out localhost.crt

その後、自分の IP アドレスとポートを指す「CardDAV 連絡先アカウント」を追加することで、ローカルで実験できるようになります。

結論

Go での CardDAV サーバーの実装は少し複雑ですが、それだけの価値があることは明らかです。連絡先は、組織のサーバー上にあるデータと自動的に同期されます。

この種のネイティブ統合を可能にする他の優れたプロトコルをご存知ですか?あなたの経験をぜひ共有してください!

リリースステートメント この記事は、https://dev.to/cmdscale/how-to-synchronize-your-contacts-your-phone-phone-implementing-carddav-in-go-9ia?1に再現されています。
最新のチュートリアル もっと>

免責事項: 提供されるすべてのリソースの一部はインターネットからのものです。お客様の著作権またはその他の権利および利益の侵害がある場合は、詳細な理由を説明し、著作権または権利および利益の証拠を提出して、電子メール [email protected] に送信してください。 できるだけ早く対応させていただきます。

Copyright© 2022 湘ICP备2022001581号-3