PageSourceSearch

https://webcade.fun/tg.js?v=1025436e

js webcade.fun collected 2026-10-08 08:26:50 UTC 8,772 bytes, 167 lines download raw bytes

1// Running inside Telegram.
2//
3// A Mini App is an ordinary web page in Telegram's WebView with one extra
4// thing: a signed blob naming the user. That blob replaces the entire wallet
5// handshake for anything that does not move money — which, for a duel, is all
6// of it. Somebody taps a link in a group chat and is playing,
7// with no wallet, no extension, no signature prompt, and no app install.
8//
9// The page is otherwise the same page. Everything here is a no-op in a normal
10// browser, so /duel keeps working exactly as it does today.
11(() => {
12  const tg = window.Telegram && window.Telegram.WebApp;
13  // initData is the test, not the presence of the SDK: the script can load in
14  // a normal browser, where it exists but has nothing signed to offer.
15  const inTelegram = !!(tg && typeof tg.initData === 'string' && tg.initData.length > 0);
16  // On the element rather than the body: this file runs in <head>, before
17  // there is a body to mark. Lets the stylesheet drop the explanatory copy
18  // that reads as clutter on a phone-sized Mini App.
19  if (inTelegram) document.documentElement.classList.add('in-tg');
20
21  if (tg) {
22    // ready() tells Telegram to drop the loading shroud; expand() takes the
23    // full sheet height, because a cabinet in a half-height sheet is unplayable.
24    try { tg.ready(); } catch (_) {}
25    try { tg.expand(); } catch (_) {}
26    // A DRAG IS NOT A DISMISSAL.
27    //
28    // Telegram reads a vertical swipe inside a Mini App as "close this", and it
29    // reads it BEFORE the page does. Every cabinet here is played with a thumb
30    // on a stick in the lower half of the screen, which is the same gesture —
31    // so the fighter would not move, or the app would slide shut mid-match, and
32    // the page could do nothing about it. Its own handlers are already correct:
33    // touch-action is none and every pointer event is preventDefault'ed. The
34    // gesture never reached them.
35    //
36    // Telegram added this in Bot API 7.7 for exactly this reason. Guarded twice
37    // — the method is absent on older clients, and calling an unsupported one
38    // throws — because a player on an old build should get the old behaviour
39    // rather than a page that stops at the first line of its own setup.
40    //
41    // Not paired with lockOrientation, deliberately: that pins the CURRENT
42    // orientation, so calling it for a landscape game would lock whoever opened
43    // it in portrait into portrait. The rotate gate in orient.js asks properly
44    // and clears when they turn the phone.
45    try {
46      if (typeof tg.disableVerticalSwipes === 'function') tg.disableVerticalSwipes();
47    } catch (_) { /* older client: the swipe stays Telegram's */ }
48    // Telegram's own back button, so leaving a duel does not mean closing the
49    // app — a person who taps the system back arrow and lands outside is gone.
50    try {
51      if (tg.BackButton && location.pathname.startsWith('/duel/')) {
52        tg.BackButton.show();
53        tg.BackButton.onClick(() => { location.href = '/duel'; });
54      }
55    } catch (_) {}
56  }
57
58  const unsafe = (tg && tg.initDataUnsafe) || {};
59  const user = unsafe.user || null;
60
61  window.ARCADE_TG = {
62    inTelegram,
63    // The signed payload. Sent to the server, which re-derives the HMAC — this
64    // value is never trusted here, and the id below is display only.
65    initData: inTelegram ? tg.initData : null,
66    id: inTelegram && user ? 'tg:' + user.id : null,
67    name: user
68      ? (user.username || [user.first_name, user.last_name].filter(Boolean).join(' ').trim() || null)
69      : null,
70    // t.me/<bot>/<app>?startapp=7 arrives here as "7" — which is a duel id,
71    // and is the whole sharing model. A NAME rather than a number is a
72    // destination instead; see DESTINATIONS below.
73    startParam: unsafe.start_param || null,
74    // A small thing that makes a web page feel like an app.
75    buzz(kind = 'light') {
76      try { tg.HapticFeedback.impactOccurred(kind); } catch (_) {}
77    },
78  };
79
80  // Who is making this request — answered in one place, so no page has to know
81  // which kind of player it is holding. Returns the auth fields to merge into
82  // a request body, or null when nobody is signed in at all.
83  //
84  // In Telegram this never prompts and never fails; in a browser it is the
85  // wallet session token exactly as before.
86  window.ARCADE_AUTH = async (opts) => {
87    if (inTelegram) {
88      // A Telegram player who has proved a wallet IS that wallet — same
89      // allowance, same line on the board as when they play on the website.
90      // Without this they would be two players who happen to be one person.
91      const P = window.ARCADE_PHANTOM;
92      if (P) {
93        try {
94          const w = await P.walletAuth();
95          // The wallet says WHO; initData still says WHERE. Merging the
96          // identity is the point — one allowance, one line on the board —
97          // but it also means the server cannot tell from the identity alone
98          // that a request came from inside the Mini App. tgProof carries that
99          // separately. Nothing on the server asks it today: the one thing
100          // that ever cared was the wagering gate, and wagering is gone. It is
101          // kept because the answer to "where is this from" is cheap to send
102          // and expensive to reconstruct once a page has stopped sending it.
103          if (w) return { ...w, tgProof: tg.initData };
104        } catch (_) { /* fall back */ }
105      }
106      return { initData: tg.initData };
107    }
108    const w = window.ARCADE_WALLET;
109    if (!w || !w.publicKey) return null;
110    return { player: w.publicKey, token: await w.token(opts) };
111  };
112
113  // The current player's id, for comparing against ids the server sends back.
114  // Who the arcade thinks you are. Set by whichever identity is in force, so
115  // a page can show it without knowing which kind it got.
116  let proved = null;
117  window.ARCADE_SET_PROVED = pk => { proved = pk || null; };
118  window.ARCADE_ME = () => (inTelegram
119    ? (proved || window.ARCADE_TG.id)
120    : (window.ARCADE_WALLET && window.ARCADE_WALLET.publicKey) || null);
121
122  // ------------------------------------------------------- deep destinations
123  //
124  // A Mini App has exactly ONE address. Whatever BotFather was given is where
125  // every launch lands, however the person got there, so `startapp` is the only
126  // thing a link can say about where it meant to go — and until now the only
127  // thing anything said with it was a duel id.
128  //
129  // That is fine while every launch wants the same page. It stopped being fine
130  // the moment a card in a group chat said "someone is looking for a fight":
131  // tapping it dropped the player on the duel screen, where there is no fight
132  // and no way to find the person who was waiting. A button that does not open
133  // the thing it advertises is worse than no button.
134  //
135  // So a start param that is a NAME from this table is a destination, and the
136  // page it names is opened before anything else on the landing page has run.
137  // Deliberately a table and not a path: `startapp` is attacker-supplied text
138  // from a link anyone can forge and paste into a chat, and treating it as a
139  // URL would make every one of those an open redirect wearing the arcade's
140  // name. A name that is not in this table is not a destination and is left
141  // alone — duel ids, referral codes and Phantom's connect ids all still
142  // arrive through it and are read by whoever owns them.
143  const DESTINATIONS = {
144    brawl: '/brawl',        // CRYPTO SMASH — the versus cabinet, and the queue
145    smash: '/brawl',        // what the marquee calls it
146  };
147
148  // The hash travels WITH the redirect, and that is not decoration. Telegram
149  // hands initData over in the launch URL's fragment; the SDK caches it in
150  // sessionStorage and restores it on later same-origin pages, which is what
151  // /duel's back button already leans on — but a cache that is one private-mode
152  // browser away from being empty is a thin thing to hang an identity on when
153  // carrying the fragment forward costs nothing. It never leaves the browser:
154  // a fragment is not sent to the server.
155  if (inTelegram) {
156    const dest = DESTINATIONS[String(unsafe.start_param || '').toLowerCase()];
157    // Already there — which is what the SECOND run of this file looks like,
158    // on the page we just opened. Without this it would replace itself with
159    // itself forever.
160    if (dest && location.pathname !== dest) {
161      // replace(), not assign(): the launch page is not somewhere the player
162      // chose to be, so Telegram's back button should leave the app rather
163      // than walk back to a screen they never asked for.
164      location.replace(dest + location.search + location.hash);
165    }
166  }
167})();

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.