{
"$type": "site.standard.document",
"content": [
{
"$type": "app.myblog.markdown",
"lang": "ja",
"markdown": "おはようございます。waturaです。note アプリチームで技術投資Daysと称して、**普段の業務でなかなか手がつけられない技術テーマやアイディア、課題に取り組み、その成果を発表することで自身の成長や、プロダクトや業務への活用の機会を設けたい。** をやりました。\n\nアプリ開発で今後役立つことか? **ほぼNO \n**価値のあるものを作ったか?**NO \n**この情報はみんなの役に立つか?**たぶんNO \n**楽しかったか?**YES \n**成長できたか?**たぶんYES?**\n\nという感じでやってみました。\n\n## やったこと\n\n1. AT Protocol上に建てられたサービスのセルフホスト\n \n2. PDSにWebSocketで接続して自前Typeの表示\n \n3. 自前LexiconでPDSで投稿\n \n4. 投稿した内容の取得\n \n5. **おまけ(本体)**\n \n\n## レギュレーション\n\nオレオレレギュレーションです。他のチームメンバーにはそんなレギュレーションはありません。\n\n* Claude Code, CodexのようなAIにコードをかかせない\n \n* AI自体は使ってもいいがコピペしない\n \n* 理解が全然できないコードを写経しない\n \n\nAIは便利な検索ツール的な1年くらい前の世界観でやっていく感じです。 \nさすがに、AIゼロでググるところまで完全に自分でやるよーは辛いので、許可することにしました。 \nエディタとかでも、ほぼ使わない方向で進めました。\n\nなお、やってみた結果、Claude Code使っててもそこまで超加速しなかったのではないかという気がします。\n\n## BlueskyとAT Protocol\n\nBlueskyは分散型SNSの一つです。分散型なのですが、BskyはMastodonと違いオレオレBskyみたいなものはありません。 \nしかし、Bskyの採用している仕組み的にはBskyと同じデータを使った別のSNSを作ったりできます。 \nそれを実現しているのが、Bskyが採用しているAT Protocolというプロトコルです。このプロトコル自体の開発もBluesky社なのでイコールな雰囲気もありますが、基本的にはオープンに使えるプロトコルです。\n\nAT Protoの中にPersonal Data Serverという仕組みがあります。これが、AT Protoを分散型にしている仕組みで、投稿やいいねなどのアクション、データを管理しているサーバーです。Bskyのものを使うことも、自分で建てることもできます。また、Bsky社以外の実装もあります。 \nこのPDSにBskyから接続して使うみたいな感じになっています。\n\n世の中にはすでにPDSとAT Protoを使い作られたサービスがいくつもあります。 \n例\n\n* ブログ Leaflet [https://about.leaflet.pub/](https://about.leaflet.pub/)\n \n* 画像SNS Grain Social [https://grain.social/](https://grain.social/)\n \n* GitHubみたいなコード管理 Tangled [https://tangled.org/](https://tangled.org/)\n \n\nまだまだありますが、また、とりあえず3つだけ。\n\n現時点において、Privateなデータを扱う仕組みがないので、頑張って定義、実装してほしいなと思っています。\n\nちなみにPDSはセルフホストしており、私のBskyハンドルは [@wtr.app](https://bsky.app/profile/wtr.app) です。\n\n## AT Protocol上に建てられたサービスのセルフホストする\n\nTangledというAT Proto上に構築されたGitHub見たいなサービスがあります。とりあえず、これをホストしてみることにしました。 \nTangledにもPDSのような仕組みがあり、Knotと呼ばれます。このKnotがセルフホスト可能で、Tangledから接続して使えるみたいな感じになっています。 \nが、微妙に上手くいかなかったのでメモです。\n\n### .well-known\n\nPDSをルートドメインに設置するのは、推奨されておらず、サブドメイン下に設置するものとなっています。 \nそのあたりの関係もあり、.well-knownにアクセスするとblogのエラー画面が表示されて残念な感じになっていました。 \ntraefikで雑に名指しで転送するように指定しました。 \n(雑に書くとtraefikはやってきた通信を適切なウェブサーバーに振り分けるプログラムです。)\n\n### TangledのアドレスをIPv6で解決してるのに、IPv6では繋がらなかった\n\ndocker の設定がアレだったのか、IPv6で繋がらないので、認証できないという状態になっていました。IPv4使えよという指示をいれて解決しました。\n\n### CloudFlare TunnelでSSHとWebを共存できない\n\nCloudFlare TunnelはWebsocketを使って、80,443ポートとか使えないような環境にあるサーバーに接続できる仕組みです。 \n事前に「このドメイン」にアクセスが来たら、「ここに転送」するみたいなルールを定義しておく感じになっています。\n\n「このドメイン」という仕組みがネックでknot.wtr.appを443接続だったらtraefikで、22だったらSSHにするみたいなことができないようなのです。 \n解決策は、\n\n* 別のアドレスにする\n \n* Cloudflare Tunnelやめる\n \n\nのどっちかという感じでした。 \n残念なことに自宅環境が、ポートは少ないしIPも動的で自由がないのでCloudflare Tunnelか類似のサービスが必須です。ということで、**Tangledをセルフホストするのは諦めました。**\n\n(note社のテックチャレンジ制度でVPS借りるのも考えなくはないです。普通にRPi動かすよりも早いし)\n\n**同一ホスト名でプロトコル別ルーティング実質不可なためTangledをセルフホストするのは諦めました。**\n\nやったこと1つ目はやれなかったこととなりました。\n\n## PDSにWebSocketで接続して自前Typeの表示\n\nPDSがあってFirehoseがあってというところまでは、事前に知っていたのですが、そこから先はよく知らない状態での手探りで進めていきました。\n\n### Firehose\n\nBskyにFirehoseという大量にデータが流れてくる仕組みがあります。Bskyへのデータの書き込みが全部流れてくる系の仕組みです。 \nこれが、Bsky本体だけではなくPDSにもあり、PDSに対するデータの書き込みを垂れ流してみることにしました。\n\nFirehoseはWebsocketを使っていて、更新がソケットを通じてどんどん流れてくるという感じになっています。 \nこのとき、流れてくるデータはCBORというバイナリ形式で、流れてきます。\n\nCBORは複数のCBORをくっつけて流すことができて、Bskyの場合CBORCBORみたいな状態でデータが送られてきます。\n\n一つ目のCBORにヘッダー情報で、二つ目に中身という感じです。 \nやることは、\n\n1. Websocketを購読する\n \n2. CBORを分割する\n \n3. ヘッダーをみて欲しい内容なら2つ目をパースする\n \n4. パースした結果を受かってなんかする(今回はできていない)\n \n\nこのうち1-3の途中まで行いました。3でCARという具体的な中身をパースするところまで時間がたりなくてできていませんでした。\n\n## 自前LexiconでPDSに投稿\n\nAT Protoにはおおむね任意のデータを保存する仕組みがあります。 \nLexiconというデータ構造の定義を作成し、そのデータ構造にしたがって投稿すると、PDSとかにデータが保存されるという仕組みになります。 \nLexiconを使ってデータ構造を定義できるというところで、AT ProtoをつかったBskyではないブログシステムを作ったりできるのです。\n\n[**Lexicon - AT Protocol** _A schema definition language._ _atproto.com_](https://atproto.com/ja/specs/lexicon)[](https://atproto.com/ja/specs/lexicon)\n\nこの仕様書にしたがってデータ構造を定義すればいいようです。blobもあったりするので画像でも動画でもなんでもできなくはないです。出来なくはないだけで、推奨される分けではなさそうですが。。。\n\nというわけで、\n\n[**Quick start guide to building applications on AT Protocol - AT Protocol** _In this guide, we're going to build a simple multi-user app t_ _atproto.com_](https://atproto.com/ja/guides/applications)[](https://atproto.com/ja/guides/applications)\n\nQuick StartにあったLexiconを app.wtr.bsky.status という名前でつくって、bsky.wtr.appに投稿してみました。\n\nwtr.appとして投稿するには、当然認証する必要があります。認証はパスワード認証とOAuth認証があります。今回は、時間がなさすぎるということでID/PasswordでJWTを取得するという手抜きを行ないました。\n\nJWTは \n**/xrpc/com.atproto.server.createSession** にポストでID/Passwordをなげたら帰ってきます。ほかにも、\n\n* did\n \n * twitterとかとちがって、didという文字列が個人を識別するしくみになります\n \n* handle\n \n* email\n \n* emailConfirmed\n \n* accessJwt\n \n* refreshJwt\n \n* active\n \n\nとかが帰ってきます。 \nこのうち、didとjwtが必須なので記録しておきます。\n\nデータの投稿 **/xrpc/com.atproto.repo.createRecord** にjsonをpostしたらPDSに書き込まれます。 \nそのさい、\n\nrepo, collection, record.$type が重要なキーになります。\n\n```\nrepo: did\ncollection: lexiconのid (app.wtr.bsky.status)\nrecord.$type:lexiconのid (app.wtr.bsky.status)\n```\n\nrecord にはlexiconで定義したそのたもろもろのデータ構造をいれてPOSTできます。 \nPOSTしたときに、Websocketをつないでいたら、POSTされたデータが流れてくるのを確認できます。\n\n## 投稿した内容の取得\n\nPOSTができるならGETもほぼ同じです。JWTをヘッダーにいれて、\n\n**/xrpc/com.atproto.repo.listRecords?repo=did:plc:didididididididididididididid&collection=app.wtr.bsky.status**\n\nこれにアクセスすると↑でPOSTしたデータを取得できます。簡単。\n\n**たったこれだけの内容を3日間も書けて行ないました。**\n\n## おまけという名の本題\n\nマルチプラットフォームでうごくライブラリを作るとしたら、Kotlinですかね?FlutterとかReactNativeで全部作る感じですかね? \nnote・TALESアプリではKotlinでマルチプラットフォーム化をやりはじめています。\n\nこういうマルチプラットフォームやるぜ!っていうときに、まったく出てこないが、実は使われまくっているマルチプラットフォームでうごくものがあります。\n\nC言語です。\n\nC言語です。\n\n \n\n \n\n \n\n \n\nC言語ですが、さすがにこの期に及んでCは書きたくないので、Cライブラリとして呼び出せる言語を使ってみました。\n\nZigです。今回Zigを初めてさわったので、「たったこれだけ」をやるのに丸2日もかかってしまいました。 \nそして、SwiftとかKotlinって楽だなぁ。どんだけ楽なんだぁってなりました。 ただ、基本となるデータのやり取りについては、できるようになったのでここからは\n\n今回、Zigをつかって上記のBskyへアクセスするどうのこうのを作りました。\n\nそして、最後の**投稿した内容の取得**は、iOSアプリ上で取得しています。 \nZigでかかれたコードで取得しています。\n\n## 時間がかかった点\n\n* 今どきの言語ならあるでしょ〜っていう機能がない\n \n * DateTimeをらくらくフォーマット\n \n * JSONのデコード・エンコードが大変\n \n* CBORの分割がライブラリにない\n \n * CBORのデコード自体はライブラリがありそれが利用できた\n \n* iOSはtlsを触らせてくれない\n \n * Zigは独自実装しており、iOS標準のものを触らせてくれない\n \n * 独自でpemを持つ必要があるが、ファイルパスはiOS側で提供しないといけない\n \n * pemをメモリに展開しておくことはできるが、それを直接CertManagerには入れられなかった\n \n * 実際使い続けるにはTLSの証明書を自分で持ち続けるのは微妙な気がするので悩ましい\n \n * ネットワーク通信はネイティブ側になげるという方法もあるが、今回はアプリ内にpemを持たせた\n \n* AIの知識が古すぎる\n \n\n今日現在、Zigのバージョンは0.15.2です。そして、0.1xがかわるだけで、マジかよ。。。ってくらいstdの仕様が変わっています。 \n変わりすぎていて,**どうぞサンプルコードです**とかってAIが提示してくるコードはぜんぜんうごきません。0.15ですというコードと0.15.2では。。。ってなったりします。 \nそして、当然のように存在しないstdライブラリの機能を使ってきたりします。 \n利用者がすくなくて、しかも、更新がめっちゃ速い言語だとつらいねーーーっていうのを久しぶりに感じました。\n\n## 心残り\n\n* やったことがしょぼすぎる\n \n* Zigのcomptimeを使えていない\n \n* コードがめっちゃ汚い\n \n* 本業には1mmも関係ない\n \n\n## コード\n\nそれぞれのタスクをこなすにはこの順番で書き下すよね。というコードでしかないです。他の言語と違う箇所は、Allocatorというメモリを確保する機構とdeferを使ったメモリ解放のコーモを入れる箇所ですね。\n\ntest allocatorというメモリ解放を忘れていると、leakしてるよとしっかりワーニングをだしてくれる素晴らしい機能があるので利用しました。\n\n## iOSから呼び出す\n\n### やること\n\n* Zig で関数を export し、ライブラリをビルドする\n \n* Xcodeにビルドした hogehoge.a をいれる\n \n* BridgingHeaderをつくり、exportした関数のシグネチャを書く\n \n* 呼び出す\n \n\n注意点としては、やはりメモリ管理まわりになります。 \n今回の例ではZig側でメモリを確保しています。 \nなので、Swift側にかえってきたあと、使い終わったらZigに渡してfreeしてあげる必要があります。 \nSwift側でもZigみたいにdefer祭にすればいいですね!\n\nなお、XCFrameworkとして、複数のターゲットに向けた \\`.a\\` をまとめてしまえば、楽になります。が、今回技術投資Daysの間に終了しなかったので、そこまではやっていません。\n\n```\n#ifndef BridgingHeader_h\n#define BridgingHeader_h\n\n#include <stdint.h>\n#include <stddef.h>\n\ntypedef struct {\n const uint8_t *ptr;\n size_t len;\n} ByteBuffer;\n\nByteBuffer getStatus(const char *ca_path);\nvoid freeBuffer(ByteBuffer buf);\n\n#endif\n```\n\nZig側では以下の2つのメソッドをexportしていました。\n\n```\nexport fn getStatus(ca_path_ptr: ?[*:0]const u8) ByteBuffer\nexport fn freeBuffer(buf: ByteBuffer) void\n```\n\n前述したように、ZigからiOSの証明書周りにアクセス出来ませんでした。なので、引数としてファイルpathをポインタで受け取るようにしています。\n\n```\nlet caPath = Bundle.main.path(forResource: \"cacert\", ofType: \"pem\")\nlet buffer = getStatus(caPath)\n```\n\nSwiftからZigに渡すときは、これで渡せます。 \nZig側ではこういうかんじで、path\\_ptrをつかったら読込めます。\n\n```\nvar ca_bundle = std.crypto.Certificate.Bundle{};\n\nif (ca_path_ptr) |path_ptr| {\n const ca_path = std.mem.span(path_ptr);\n // try ca_bundle.addCertsFromPem(allocator, pem) みたいなのがほしかった\n try ca_bundle.addCertsFromFilePathAbsolute(allocator, ca_path);\n}\n\n// 1. メモリを確保して生成\nvar client = std.http.Client{\n .allocator = allocator,\n .ca_bundle = ca_bundle,\n};\n// 2. 終わったら開放\ndefer client.deinit();\n\nconst resp = try client.fetch(options);\nif (resp.status.phrase()) |response| {\n // Debug用の表示\n // std.log.debugはXcodeのログのところにも流れてくる。なので両方書いて見ている。\n std.log.debug(\"url: {s}, status: {s}\\n\", .{ url_string, response });\n std.debug.print(\"url: {s}, status: {s}\\n\", .{ url_string, response });\n}\n\nif (resp.status == .ok) {\n return try body.toOwnedSlice();\n} else {\n std.debug.panic(\"error\", .{});\n}\n```\n\n(改めてコードをみたら、 **ca\\_bundle.addCertsFromFilePathAbsolute(allocator, ca\\_path)** をfreeしていなさそうなので、リークしていそうです。)\n\n暇があればもうちょっとZigを触ってみたいと思うものの,ここまでメモリ管理とかきにしないといけない環境で動かすことはないので、普通にもっと大富豪のようにメモリをつかえばいいのではという気もしています。 \n組み込み要素のプログラミングとかやる予定がないですし。\n\n[#最近の学び](https://note.com/intent/post?hashtags=%E6%9C%80%E8%BF%91%E3%81%AE%E5%AD%A6%E3%81%B3&rt=internal&sub_rt=article&d=winter2025) \n[#やってみた](https://note.com/intent/post?hashtags=%E3%82%84%E3%81%A3%E3%81%A6%E3%81%BF%E3%81%9F&rt=internal&sub_rt=article&d=winter2025)\n"
}
],
"path": "/blog/3mmcuyrg2f2et",
"publishedAt": "2025-12-22T00:00:00Z",
"site": "https://wtr.app",
"tags": [
"note"
],
"textContent": "おはようございます。waturaです。note アプリチームで技術投資Daysと称して、普段の業務でなかなか手がつけられない技術テーマやアイディア、課題に取り組み、その成果を発表することで自身の成長や、プロダクトや業務への活用の機会を設けたい。 をやりました。\n\nアプリ開発で今後役立つことか? **ほぼNO**価値のあるものを作ったか?**NO**この情報はみんなの役に立つか?**たぶんNO**楽しかったか?**YES**成長できたか?たぶんYES?\n\nという感じでやってみました。\n\nやったこと\n\nAT Protocol上に建てられたサービスのセルフホスト\n\nPDSにWebSocketで接続して自前Typeの表示\n\n自前LexiconでPDSで投稿\n\n投稿した内容の取得\n\nおまけ(本体)\n\nレギュレーション\n\nオレオレレギュレーションです。他のチームメンバーにはそんなレギュレーションはありません。\n\nClaude Code, CodexのようなAIにコードをかかせない\n\nAI自体は使ってもいいがコピペしない\n\n理解が全然できないコードを写経しない\n\nAIは便利な検索ツール的な1年くらい前の世界観でやっていく感じです。さすがに、AIゼロでググるところまで完全に自分でやるよーは辛いので、許可することにしました。エディタとかでも、ほぼ使わない方向で進めました。\n\nなお、やってみた結果、Claude Code使っててもそこまで超加速しなかったのではないかという気がします。\n\nBlueskyとAT Protocol\n\nBlueskyは分散型SNSの一つです。分散型なのですが、BskyはMastodonと違いオレオレBskyみたいなものはありません。しかし、Bskyの採用している仕組み的にはBskyと同じデータを使った別のSNSを作ったりできます。それを実現しているのが、Bskyが採用しているAT Protocolというプロトコルです。このプロトコル自体の開発もBluesky社なのでイコールな雰囲気もありますが、基本的にはオープンに使えるプロトコルです。\n\nAT Protoの中にPersonal Data Serverという仕組みがあります。これが、AT Protoを分散型にしている仕組みで、投稿やいいねなどのアクション、データを管理しているサーバーです。Bskyのものを使うことも、自分で建てることもできます。また、Bsky社以外の実装もあります。このPDSにBskyから接続して使うみたいな感じになっています。\n\n世の中にはすでにPDSとAT Protoを使い作られたサービスがいくつもあります。例\n\nブログ Leaflet https://about.leaflet.pub/\n\n画像SNS Grain Social https://grain.social/\n\nGitHubみたいなコード管理 Tangled https://tangled.org/\n\nまだまだありますが、また、とりあえず3つだけ。\n\n現時点において、Privateなデータを扱う仕組みがないので、頑張って定義、実装してほしいなと思っています。\n\nちなみにPDSはセルフホストしており、私のBskyハンドルは @wtr.app です。\n\nAT Protocol上に建てられたサービスのセルフホストする\n\nTangledというAT Proto上に構築されたGitHub見たいなサービスがあります。とりあえず、これをホストしてみることにしました。TangledにもPDSのような仕組みがあり、Knotと呼ばれます。このKnotがセルフホスト可能で、Tangledから接続して使えるみたいな感じになっています。が、微妙に上手くいかなかったのでメモです。\n\n.well-known\n\nPDSをルートドメインに設置するのは、推奨されておらず、サブドメイン下に設置するものとなっています。そのあたりの関係もあり、.well-knownにアクセスするとblogのエラー画面が表示されて残念な感じになっていました。traefikで雑に名指しで転送するように指定しました。(雑に書くとtraefikはやってきた通信を適切なウェブサーバーに振り分けるプログラムです。)\n\nTangledのアドレスをIPv6で解決してるのに、IPv6では繋がらなかった\n\ndocker の設定がアレだったのか、IPv6で繋がらないので、認証できないという状態になっていました。IPv4使えよという指示をいれて解決しました。\n\nCloudFlare TunnelでSSHとWebを共存できない\n\nCloudFlare TunnelはWebsocketを使って、80,443ポートとか使えないような環境にあるサーバーに接続できる仕組みです。事前に「このドメイン」にアクセスが来たら、「ここに転送」するみたいなルールを定義しておく感じになっています。\n\n「このドメイン」という仕組みがネックでknot.wtr.appを443接続だったらtraefikで、22だったらSSHにするみたいなことができないようなのです。解決策は、\n\n別のアドレスにする\n\nCloudflare Tunnelやめる\n\nのどっちかという感じでした。残念なことに自宅環境が、ポートは少ないしIPも動的で自由がないのでCloudflare Tunnelか類似のサービスが必須です。ということで、Tangledをセルフホストするのは諦めました。\n\n(note社のテックチャレンジ制度でVPS借りるのも考えなくはないです。普通にRPi動かすよりも早いし)\n\n同一ホスト名でプロトコル別ルーティング実質不可なためTangledをセルフホストするのは諦めました。\n\nやったこと1つ目はやれなかったこととなりました。\n\nPDSにWebSocketで接続して自前Typeの表示\n\nPDSがあってFirehoseがあってというところまでは、事前に知っていたのですが、そこから先はよく知らない状態での手探りで進めていきました。\n\nFirehose\n\nBskyにFirehoseという大量にデータが流れてくる仕組みがあります。Bskyへのデータの書き込みが全部流れてくる系の仕組みです。これが、Bsky本体だけではなくPDSにもあり、PDSに対するデータの書き込みを垂れ流してみることにしました。\n\nFirehoseはWebsocketを使っていて、更新がソケットを通じてどんどん流れてくるという感じになっています。このとき、流れてくるデータはCBORというバイナリ形式で、流れてきます。\n\nCBORは複数のCBORをくっつけて流すことができて、Bskyの場合CBORCBORみたいな状態でデータが送られてきます。\n\n一つ目のCBORにヘッダー情報で、二つ目に中身という感じです。やることは、\n\nWebsocketを購読する\n\nCBORを分割する\n\nヘッダーをみて欲しい内容なら2つ目をパースする\n\nパースした結果を受かってなんかする(今回はできていない)\n\nこのうち1-3の途中まで行いました。3でCARという具体的な中身をパースするところまで時間がたりなくてできていませんでした。\n\n自前LexiconでPDSに投稿\n\nAT Protoにはおおむね任意のデータを保存する仕組みがあります。Lexiconというデータ構造の定義を作成し、そのデータ構造にしたがって投稿すると、PDSとかにデータが保存されるという仕組みになります。Lexiconを使ってデータ構造を定義できるというところで、AT ProtoをつかったBskyではないブログシステムを作ったりできるのです。\n\nLexicon - AT Protocol A schema definition language. atproto.com\n\nこの仕様書にしたがってデータ構造を定義すればいいようです。blobもあったりするので画像でも動画でもなんでもできなくはないです。出来なくはないだけで、推奨される分けではなさそうですが。。。\n\nというわけで、\n\nQuick start guide to building applications on AT Protocol - AT Protocol In this guide, we're going to build a simple multi-user app t atproto.com\n\nQuick StartにあったLexiconを app.wtr.bsky.status という名前でつくって、bsky.wtr.appに投稿してみました。\n\nwtr.appとして投稿するには、当然認証する必要があります。認証はパスワード認証とOAuth認証があります。今回は、時間がなさすぎるということでID/PasswordでJWTを取得するという手抜きを行ないました。\n\nJWTは/xrpc/com.atproto.server.createSession にポストでID/Passwordをなげたら帰ってきます。ほかにも、\n\ndid\n\ntwitterとかとちがって、didという文字列が個人を識別するしくみになります\n\nhandle\n\nemail\n\nemailConfirmed\n\naccessJwt\n\nrefreshJwt\n\nactive\n\nとかが帰ってきます。このうち、didとjwtが必須なので記録しておきます。\n\nデータの投稿 /xrpc/com.atproto.repo.createRecord にjsonをpostしたらPDSに書き込まれます。そのさい、\n\nrepo, collection, record.$type が重要なキーになります。\n\nrepo: did\ncollection: lexiconのid (app.wtr.bsky.status)\nrecord.$type:lexiconのid (app.wtr.bsky.status)\n\nrecord にはlexiconで定義したそのたもろもろのデータ構造をいれてPOSTできます。POSTしたときに、Websocketをつないでいたら、POSTされたデータが流れてくるのを確認できます。\n\n投稿した内容の取得\n\nPOSTができるならGETもほぼ同じです。JWTをヘッダーにいれて、\n\n/xrpc/com.atproto.repo.listRecords?repo=did:plc:didididididididididididididid&collection=app.wtr.bsky.status\n\nこれにアクセスすると↑でPOSTしたデータを取得できます。簡単。\n\nたったこれだけの内容を3日間も書けて行ないました。\n\nおまけという名の本題\n\nマルチプラットフォームでうごくライブラリを作るとしたら、Kotlinですかね?FlutterとかReactNativeで全部作る感じですかね?note・TALESアプリではKotlinでマルチプラットフォーム化をやりはじめています。\n\nこういうマルチプラットフォームやるぜ!っていうときに、まったく出てこないが、実は使われまくっているマルチプラットフォームでうごくものがあります。\n\nC言語です。\n\nC言語です。\n\nC言語ですが、さすがにこの期に及んでCは書きたくないので、Cライブラリとして呼び出せる言語を使ってみました。\n\nZigです。今回Zigを初めてさわったので、「たったこれだけ」をやるのに丸2日もかかってしまいました。そして、SwiftとかKotlinって楽だなぁ。どんだけ楽なんだぁってなりました。 ただ、基本となるデータのやり取りについては、できるようになったのでここからは\n\n今回、Zigをつかって上記のBskyへアクセスするどうのこうのを作りました。\n\nそして、最後の投稿した内容の取得は、iOSアプリ上で取得しています。Zigでかかれたコードで取得しています。\n\n時間がかかった点\n\n今どきの言語ならあるでしょ〜っていう機能がない\n\nDateTimeをらくらくフォーマット\n\nJSONのデコード・エンコードが大変\n\nCBORの分割がライブラリにない\n\nCBORのデコード自体はライブラリがありそれが利用できた\n\niOSはtlsを触らせてくれない\n\nZigは独自実装しており、iOS標準のものを触らせてくれない\n\n独自でpemを持つ必要があるが、ファイルパスはiOS側で提供しないといけない\n\npemをメモリに展開しておくことはできるが、それを直接CertManagerには入れられなかった\n\n実際使い続けるにはTLSの証明書を自分で持ち続けるのは微妙な気がするので悩ましい\n\nネットワーク通信はネイティブ側になげるという方法もあるが、今回はアプリ内にpemを持たせた\n\nAIの知識が古すぎる\n\n今日現在、Zigのバージョンは0.15.2です。そして、0.1xがかわるだけで、マジかよ。。。ってくらいstdの仕様が変わっています。変わりすぎていて,どうぞサンプルコードですとかってAIが提示してくるコードはぜんぜんうごきません。0.15ですというコードと0.15.2では。。。ってなったりします。そして、当然のように存在しないstdライブラリの機能を使ってきたりします。利用者がすくなくて、しかも、更新がめっちゃ速い言語だとつらいねーーーっていうのを久しぶりに感じました。\n\n心残り\n\nやったことがしょぼすぎる\n\nZigのcomptimeを使えていない\n\nコードがめっちゃ汚い\n\n本業には1mmも関係ない\n\nコード\n\nそれぞれのタスクをこなすにはこの順番で書き下すよね。というコードでしかないです。他の言語と違う箇所は、Allocatorというメモリを確保する機構とdeferを使ったメモリ解放のコーモを入れる箇所ですね。\n\ntest allocatorというメモリ解放を忘れていると、leakしてるよとしっかりワーニングをだしてくれる素晴らしい機能があるので利用しました。\n\niOSから呼び出す\n\nやること\n\nZig で関数を export し、ライブラリをビルドする\n\nXcodeにビルドした hogehoge.a をいれる\n\nBridgingHeaderをつくり、exportした関数のシグネチャを書く\n\n呼び出す\n\n注意点としては、やはりメモリ管理まわりになります。今回の例ではZig側でメモリを確保しています。なので、Swift側にかえってきたあと、使い終わったらZigに渡してfreeしてあげる必要があります。Swift側でもZigみたいにdefer祭にすればいいですね!\n\nなお、XCFrameworkとして、複数のターゲットに向けた \\`.a\\` をまとめてしまえば、楽になります。が、今回技術投資Daysの間に終了しなかったので、そこまではやっていません。\n\n#ifndef BridgingHeader_h\n#define BridgingHeader_h\n\n#include <stdint.h>\n#include <stddef.h>\n\ntypedef struct {\n const uint8_t *ptr;\n size_t len;\n} ByteBuffer;\n\nByteBuffer getStatus(const char *ca_path);\nvoid freeBuffer(ByteBuffer buf);\n\n#endif\n\nZig側では以下の2つのメソッドをexportしていました。\n\nexport fn getStatus(ca_path_ptr: ?[*:0]const u8) ByteBuffer\nexport fn freeBuffer(buf: ByteBuffer) void\n\n前述したように、ZigからiOSの証明書周りにアクセス出来ませんでした。なので、引数としてファイルpathをポインタで受け取るようにしています。\n\nlet caPath = Bundle.main.path(forResource: \"cacert\", ofType: \"pem\")\nlet buffer = getStatus(caPath)\n\nSwiftからZigに渡すときは、これで渡せます。Zig側ではこういうかんじで、path\\_ptrをつかったら読込めます。\n\nvar ca_bundle = std.crypto.Certificate.Bundle{};\n\nif (ca_path_ptr) |path_ptr| {\n const ca_path = std.mem.span(path_ptr);\n // try ca_bundle.addCertsFromPem(allocator, pem) みたいなのがほしかった\n try ca_bundle.addCertsFromFilePathAbsolute(allocator, ca_path);\n}\n\n// 1. メモリを確保して生成\nvar client = std.http.Client{\n .allocator = allocator,\n .ca_bundle = ca_bundle,\n};\n// 2. 終わったら開放\ndefer client.deinit();\n\nconst resp = try client.fetch(options);\nif (resp.status.phrase()) |response| {\n // Debug用の表示\n // std.log.debugはXcodeのログのところにも流れてくる。なので両方書いて見ている。\n std.log.debug(\"url: {s}, status: {s}\\n\", .{ url_string, response });\n std.debug.print(\"url: {s}, status: {s}\\n\", .{ url_string, response });\n}\n\nif (resp.status == .ok) {\n return try body.toOwnedSlice();\n} else {\n std.debug.panic(\"error\", .{});\n}\n\n(改めてコードをみたら、 ca\\_bundle.addCertsFromFilePathAbsolute(allocator, ca\\_path) をfreeしていなさそうなので、リークしていそうです。)\n\n暇があればもうちょっとZigを触ってみたいと思うものの,ここまでメモリ管理とかきにしないといけない環境で動かすことはないので、普通にもっと大富豪のようにメモリをつかえばいいのではという気もしています。組み込み要素のプログラミングとかやる予定がないですし。\n\n#最近の学び#やってみた",
"title": "note アプリチーム 技術投資Days 2025",
"updatedAt": "2026-05-20T21:47:20Z"
}