social-kit / test / types / consumer.ts
  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
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
/**
 * TS smoke-compile of the exported API — exercises exactly the constructs the
 * nine suite apps use (game1's `new GameLb` + `attachSocial` behind a dynamic
 * import with an inline `import("ardegazu-social-kit").GameLb` annotation,
 * home's SocialAgent/FriendStore/"./net" node, lb-ui's weekly/all-time fold,
 * the /join chips, the t-translator injection), plus the rest of the surface.
 * Compiled by `npm run check`; never executed. This is the tripwire for both
 * `.d.ts` drift and advanced-rename breakage.
 */
import { homeChip, joinPasteChip } from "ardegazu-social-kit/join";
import {
  // home's import list (social.ts / social-share.ts / lb-ui.ts)
  FriendStore,
  SocialAgent,
  SUITE_APPS,
  buildFriendLink,
  enText,
  mkT,
  foldProfileSync,
  sanitizeProfileSync,
  inviteToast,
  mintRoomUrl,
  parseFriendLink,
  ReceiptSet,
  aggregate,
  fetchWeekly,
  isoWeek,
  startLbGossip,
  // protocol surface
  SOCIAL_SALT,
  SOC_BEACON_MS,
  SOC_STALE_MS,
  ENV_DEFAULT_TTL_MS,
  INVITE_TTL_MS,
  SEEN_ENV_CAP,
  BLOCKED_CAP,
  PENDING_UI_CAP,
  RECEIPTS_CAP,
  AP2P_DESCRIPTOR_URL,
  MAILBOX_URL_FALLBACK,
  MAILBOX_CREDS_URL_FALLBACK,
  canon,
  sha256,
  sha256B64url,
  hkdfId,
  hkdfAesKey,
  mailboxEndpoints,
  MailboxAuth,
  MailboxClient,
  mailboxFor,
  buildEnvelope,
  openEnvelope,
  SeenRing,
  pairChannel,
  selfChannel,
  inboxRoomId,
  envCtx,
  sanitizeFriend,
  applyFriendEnvelope,
  MIN_SIGNERS,
  roomHash,
  receiptHash,
  signReceipt,
  verifyReceipt,
  lbRoomId,
  lbTopic,
  depositReceipt,
  attachSocial,
} from "ardegazu-social-kit";
import type {
  AppDef,
  AttachSocialOpts,
  BlockedEntry,
  Channel,
  DepositResult,
  EnvType,
  EnvelopeInner,
  Friend,
  FriendLink,
  InviteMsg,
  LbRow,
  MailboxEndpoints,
  PresenceInfo,
  ProfileSyncBlob,
  PubsubLike,
  Receipt,
  ReceiptBase,
  ReplayResult,
  SelfCrypto,
  SocBlob,
  SocialT,
  SocialTextKey,
  VerifyOpts,
} from "ardegazu-social-kit";
import { createSocialNode } from "ardegazu-social-kit/net";
import type { RelayInfo, SocialNet, SocialNetConfig } from "ardegazu-social-kit/net";
import { runSocialKitSelfTest } from "ardegazu-social-kit/selftest";
import { Identity, utf8 } from "ardegazu-id-kit";
import { deriveSuiteXKeyPair, signXCert, SUITE_SALT } from "ardegazu-id-kit/xkey";

// inline import() types (the way game1's main.ts annotates without a value import)
type InlineGameLb = import("ardegazu-social-kit").GameLb | null;

// the apps' t-translator injection signature (home/game i18n.ts `tKit`)
const tKit = (k: string, vars?: Record<string, string | number>): string => `${k}${vars ? "" : ""}`;

// game1's i18n catalog declares the kit block as Record<SocialTextKey, string>
const kitBlock: Partial<Record<SocialTextKey, string>> = { "social.toast.join": "hai" };

export async function smoke(): Promise<void> {
  const identity: Identity = await Identity.fromSeed(Identity.newSeed());
  const seed: string = Identity.newSeed();

  // ---- game1 main.ts: dynamic import boundary + GameLb + attachSocial ------
  let lb: InlineGameLb = null;
  const net = {
    myId: "me",
    broadcast: (_frame: unknown): void => {},
    send: (_to: string, _frame: unknown): void => {},
    identityOf: (_id: string): { pub: string } | null => null,
  };
  await import("ardegazu-social-kit")
    .then(async (soc) => {
      lb = new soc.GameLb({
        g: "game1.ardegazu.ro/v3",
        roomId: "room",
        ns: "game1-ardegazu-ro-v3",
        identity,
        net,
        myScore: () => null,
      });
      const consumed: boolean = lb.onFrame("peer", { t: "lbq" });
      await lb.hostMatchEnd([["peer", "nick", 42]]);
      const carried: Receipt[] = lb.receipts.all();
      const agent = await soc.attachSocial({
        ns: "game1-ardegazu-ro-v3",
        app: "game1.ardegazu.ro",
        identity,
        seed,
        myName: () => "ana",
        pubsub: null,
        soc: undefined,
        roomLink: () => "https://game1.ardegazu.ro/#x",
        t: tKit,
      });
      void [consumed, carried, agent];
    })
    .catch(() => {});

  // ---- chat/board lobbies + games: the /join chips -------------------------
  joinPasteChip({ t: tKit });
  homeChip({ t: tKit });
  joinPasteChip();
  homeChip();

  // ---- home social.ts: agent + store + links + presence --------------------
  const store = new FriendStore("home-ns");
  store.onChange = () => {};
  const x = await deriveSuiteXKeyPair(seed);
  const xCert: string = await signXCert(identity, SUITE_SALT, x.pubB64);
  const self: SelfCrypto = { identity, x, xCert, name: "ana" };
  const agent = new SocialAgent({
    ns: "home-ns",
    app: "ardegazu.ro",
    identity,
    seed,
    myName: () => "ana",
    store,
    pubsub: null,
    joinLink: () => null,
    brains: (): Record<string, string> | null => ({ "valley-blocks": "v7@3" }),
    profileSync: {
      get: (): ProfileSyncBlob | null => null,
      apply: (_p: ProfileSyncBlob): void => {},
    },
    events: {
      presence: (pub: string, info: PresenceInfo | null) => void [pub, info],
      invite: (inv: InviteMsg) => inviteToast(inv, { label: "x", t: tKit }),
      request: (f: Friend) => void f,
      accepted: (f: Friend) => void f,
      synced: (n: number) => void n,
    },
  });
  await agent.start();
  agent.syncNow();
  const me: string = agent.myPub;
  const link: string = buildFriendLink("https://ardegazu.ro/", { pub: me, x: x.pubB64, xs: xCert, name: "ana" });
  const parsed: FriendLink | null = await parseFriendLink(link.slice(link.indexOf("#")));
  if (parsed) {
    const sent: boolean = await agent.sendFriendRequest(parsed, "hei");
    void sent;
  }
  const accepted: boolean = await agent.acceptRequest(me);
  const nSent: number = await agent.sendInvite([me], "chat.ardegazu.ro", mintRoomUrl("chat.ardegazu.ro"), enText("social.invite.join", { app: "sueta" }), { k: "join" });
  const online: PresenceInfo | null = agent.presenceOf(me);
  // what the hub renders next to a peer: game id -> brain version, optional
  const peerBrains: Record<string, string> | undefined = online?.brains;
  const firstBrain: string | undefined = peerBrains ? Object.values(peerBrains)[0] : undefined;
  void firstBrain;
  agent.leaveFriendChannel(me);
  await agent.joinFriendChannel(store.list()[0]);
  agent.stop();
  const blob: SocBlob = store.socBlob();
  const changed: number = store.merge(blob);
  const friends: Friend[] = store.friends();
  const blocked: BlockedEntry[] = store.blocked();
  const label: string = friends.length > 0 ? store.labelOf(friends[0]) : "";
  const folded: ProfileSyncBlob | null = sanitizeProfileSync({ name: "a", hue: 1, glyph: null, lang: "ro", ts: 1 });
  if (folded) void foldProfileSync(folded, folded);
  const apps: readonly AppDef[] = SUITE_APPS;

  // apps without a room concept: top-level attachSocial with the chips skipped
  const attachOpts: AttachSocialOpts = {
    ns: "home-ns",
    app: "ardegazu.ro",
    identity,
    seed,
    myName: () => "ana",
    pubsub: null,
    noJoinToggle: true,
    brains: () => null,
  };
  const attached: SocialAgent | null = await attachSocial(attachOpts);
  attached?.stop();

  // ---- home lb-ui.ts: weekly + all-time boards -----------------------------
  const receipts = new ReceiptSet("home-ns");
  const week: string = isoWeek(Date.now());
  const weekly: Receipt[] = await fetchWeekly("game1.ardegazu.ro/v3", week, { maxScore: 10000 });
  const rows: LbRow[] = aggregate([...weekly, ...receipts.all()]);
  const gossip = await startLbGossip({
    pubsub: undefined as unknown as PubsubLike,
    receipts,
    games: ["game1.ardegazu.ro/v3"],
    verify: { maxScore: 10000 } satisfies VerifyOpts,
    onNew: (rec: Receipt) => void rec,
  });
  gossip.stop();

  // ---- home net-share.ts / social.ts: the /net node ------------------------
  const cfg: SocialNetConfig = {
    descriptorUrl: "https://signal.ardegazu.ro/.well-known/ap2p",
    turnUrl: "https://signal.ardegazu.ro/turn-credentials",
    relayFallback: { peerId: "12D3Koo", multiaddrs: ["/dns4/x/tcp/443/wss"] } satisfies RelayInfo,
  };
  const socialNet: SocialNet = await createSocialNode(cfg);
  const pubsub: PubsubLike = socialNet.pubsub;
  const relayUp: boolean = socialNet.relayConnected();
  await socialNet.close();

  // ---- protocol surface ----------------------------------------------------
  const c: string = canon({ a: 1 });
  const h = await sha256(utf8(c));
  const hb: string = await sha256B64url(c);
  const kid: string = await hkdfId(h, SOCIAL_SALT, "test|v1");
  const key: CryptoKey = await hkdfAesKey(h, SOCIAL_SALT, "test|v1");
  const ep: MailboxEndpoints = await mailboxEndpoints();
  const auth = new MailboxAuth(ep.credsUrl, "room");
  const client = new MailboxClient(ep.baseUrl, "room", auth);
  const dep: DepositResult = await client.deposit(utf8("x"));
  const rep: ReplayResult = await client.replay(null, 50);
  const mbx: MailboxClient = await mailboxFor(await inboxRoomId(me));
  const t: EnvType = "freq";
  const built = await buildEnvelope(self, envCtx(me), me, x.pubB64, t, { msg: "hei" }, ENV_DEFAULT_TTL_MS);
  const env: EnvelopeInner | null = await openEnvelope(x, envCtx(me), built.bytes);
  if (env) {
    const applied: boolean = applyFriendEnvelope(store, env, { request: (f: Friend) => void f }, me);
    void applied;
  }
  const ring = new SeenRing("ns:soc:seen-env", SEEN_ENV_CAP);
  ring.add(built.id);
  const seenIt: boolean = ring.has(built.id);
  const chA: Channel = await pairChannel(x, me, me, x.pubB64);
  const chSelf: Channel = await selfChannel(seed);
  const tag: string = await chA.tagOf(me);
  const wire: string = await chA.seal(me, { k: "b" });
  const openedWire = await chSelf.open(wire, [me]);
  const sane: Friend | null = sanitizeFriend({ pub: me });
  const base: ReceiptBase = {
    v: 1,
    g: "game1.ardegazu.ro/v3",
    m: "match",
    r: await roomHash("room"),
    ts: Date.now(),
    n: 2,
    sc: [{ p: me, nm: "ana", s: 1 }],
  };
  const sig = await signReceipt(identity, base);
  const rec: Receipt = { ...base, sig: [sig] };
  const verdict = await verifyReceipt(rec, { maxAgeMs: 8 * 86_400_000 });
  const hash: string = await receiptHash(base);
  const dropRoom: string = await lbRoomId(base.g, week);
  const topic: string = await lbTopic(base.g);
  const deposited: boolean = await depositReceipt(rec);
  const minSigners: number = MIN_SIGNERS;

  // ---- text seam -----------------------------------------------------------
  const tt = mkT(tKit satisfies SocialT);
  const s1: string = tt("social.toast.join");
  const s2: string = enText("social.sheet.here-now", { app: "sueta" });

  // ---- selftest ------------------------------------------------------------
  await runSocialKitSelfTest();

  // keep noUnusedLocals honest
  void [lb, accepted, nSent, online, changed, label, blocked, apps, rows, pubsub, relayUp,
    kid, key, dep, rep, mbx, tag, openedWire, sane, verdict, hash, dropRoom, topic,
    deposited, minSigners, s1, s2, kitBlock, blob, seenIt,
    SOC_BEACON_MS, SOC_STALE_MS, INVITE_TTL_MS, BLOCKED_CAP, PENDING_UI_CAP, RECEIPTS_CAP,
    AP2P_DESCRIPTOR_URL, MAILBOX_URL_FALLBACK, MAILBOX_CREDS_URL_FALLBACK, hb];
}

static mirror of HEAD · about · clone: git clone https://git.ardegazu.ro/social-kit.git