import { TClans, TClanInfo, TClanJoinResult } from './WSAPITypes'; import { WSAPITournaments } from './WSAPITournaments'; /** @group Clans */ export declare class WSAPIClans extends WSAPITournaments { /** * Returns the active clans visible to the current user along with the * user's own membership state (`user_clan_id`, `cooldown_until`, * `join_date`). Use this to power a clan-picker / clan-browser screen. * * The returned `clans[]` is server-sorted by `rating_position` ASC * (1 = highest-ranked). The server has already filtered out archived * clans, clans the user fails segment conditions for, and (when any * clan has a non-zero score) clans with a zero rating score. * * @remarks * **Subscription model (`onUpdate`)** * The callback receives the FULL refreshed `TClans` payload (never a * diff/patch). Each subsequent call to `getClans({ onUpdate })` * REPLACES the prior callback. Pass `onUpdate: undefined` (or omit * it) to keep the prior callback in place; the callback is never * auto-cleared. * * **Update triggers** — note that, unlike most other subscription * methods in the SDK, this callback is **poll-driven only**. There * is NO server push that refreshes the clans list. The callback * fires when: * * 1. The 30-second cache TTL expires and a subsequent `getClans()` * call (from the consumer) triggers a fresh fetch. * * Consequence: clan-membership changes (other users joining your * clan, an operator-driven kick, a clan being archived) surface * only on the next consumer-driven fetch. If your UI needs * near-live state, poll `getClans()` on an interval; or re-call * after a {@link joinClan} resolves to pick up the new * `user_clan_id` immediately. * * **Reading state from the returned payload** * Drive list rendering from `clans[]` (already sorted). Identify * the user's own clan via `clan.clan_id === user_clan_id` * (`null` for clanless users). Detect an active switch-cooldown * with `cooldown_until !== null` — when set, the user cannot join * any clan until the cooldown expires, even clans they could * normally join. The cooldown is **user-level**, not per-clan. * * `cooldown_until` is an ISO 8601 UTC datetime string with no * timezone suffix (`"YYYY-MM-DDTHH:MM:SS"`). Parse as UTC: * `new Date(cooldown_until + 'Z')` or `moment.utc(cooldown_until)`. * Display in the user's local time. The default cooldown period * is configured per label by the operator (typically 7 days). * * `rating_position` is a global rank computed server-side across * all active clans in the label (1 = highest-rated). Because some * clans may be hidden by per-user segment visibility, positions * the user sees may skip (e.g. 1, 3, 7) — don't assume the array * spans a contiguous range. * * The `entry_fee_currency_type_id` enum on each clan uses values * `0` = points, `1` = gems, `2` = diamonds, `3` = free. Compare * `entry_fee_amount` against the user's matching balance (from * {@link getUserProfile}) to drive Join button affordability. * * **Cache TTL**: the SDK caches the response for 30 seconds. Cache * is fully cleared on login / logout. * * **Idempotency**: safe. Read-only. Repeated calls within the * cache window return a deep-cloned cached payload without a * network round-trip. * * **Side effects**: none — pure metadata read. * * **UI guidance**: see [UI Guide — `getClans`](../../docs/ui/clans/UIGuide_getClans.md). * * **Visitor mode**: not supported. * * @param params Optional. Omit to fetch without subscribing. * @param params.onUpdate Callback invoked with the full refreshed * `TClans` payload whenever the 30 s cache * expires and a subsequent fetch lands. * Each call to `getClans` overwrites the * prior callback. * @returns Promise resolving to `TClans`. Empty * `clans` array if no clans are visible. * * @example * ```ts * const result = await window._smartico.api.getClans({ * onUpdate: (refreshed) => { * console.log('[smartico] clans payload refreshed (poll-driven; ~30 s cadence) — re-render the clan list from this payload:', refreshed); * }, * }); * * console.log('[smartico] user is', result.user_clan_id == null ? 'clanless' : 'in clan ' + result.user_clan_id); * * // Cooldown handling — affects ALL clans, not a specific one. * if (result.cooldown_until) { * const cooldownEndsUtc = new Date(result.cooldown_until + 'Z'); * console.log('[smartico] switch-cooldown active — disable all Join buttons until', cooldownEndsUtc.toLocaleString(), * '(local). Render the cooldown end on the disabled button label.'); * } * * // Render clans — already sorted by rating_position ASC. * for (const clan of result.clans) { * const isMine = clan.clan_id === result.user_clan_id; * const isFull = clan.member_count >= clan.capacity_limit; * * if (isMine) { * console.log('[smartico] highlight clan', clan.clan_id, 'as "Your clan"; CTA label "Your clan", no-op'); * } else if (result.cooldown_until) { * console.log('[smartico] clan', clan.clan_id, '— Join blocked by user cooldown; show disabled button with cooldown end date'); * } else if (isFull) { * console.log('[smartico] clan', clan.clan_id, '— Clan full; show disabled "Clan is full" button'); * } else { * const label = clan.entry_fee_amount === 0 || clan.entry_fee_currency_type_id === 3 * ? 'Join free' * : `Join (${clan.entry_fee_amount} ${['Points','Gems','Diamonds'][clan.entry_fee_currency_type_id]})`; * console.log('[smartico] clan', clan.clan_id, '— render enabled Join button labelled', label); * } * } * * // After a successful joinClan(), re-call getClans manually — no push refresh. * // const r = await window._smartico.api.joinClan(targetClanId); * // if (r.errCode === 0) await window._smartico.api.getClans(); * ``` */ getClans({ onUpdate }?: { onUpdate?: (data: TClans) => void; }): Promise; /** * Returns the full detail of a single clan — adds the ranked * `members[]` roster and a fresh `cooldown_until` on top of the same * fields exposed on a `TClan` list entry. Use this to power a clan * detail screen / popup after the user picks an item from * {@link getClans}. * * Each call is a fresh server round-trip — there is no client cache * and no push event refreshes detail state. Re-call to refresh. * * @remarks * **Preconditions** * Pass a valid `clanId` (typically read from * `TClans.clans[i].clan_id` returned by {@link getClans}). The * method works standalone — `getClans()` is not required first — * but is the only stable source of valid IDs. * * **Refresh model** * - **No subscription.** One-shot promise. * - **No client cache.** Every call sends a network request. * - **No push event** refreshes the detail. Member-join, kick, or * contribution-score changes require a fresh `getClanInfo` call. * * **Returned shape — beyond `TClan`** * `TClanInfo` adds two things on top of the same identity / capacity * / fee / rating fields you'd see on a `TClan` list entry: * 1. A ranked `members[]` array — each entry carries `user_id`, * `public_username`, `avatar_id` / `avatar_real_id`, the resolved * `avatar_url`, the member's `position` (rank within this clan), * `contribution_score`, the `is_me` flag identifying the current * user's row, and the optional `clean_ext_user_id`. The server * orders members by score DESC (i.e. `position` ASC); no * client-side re-sort is required. * 2. A fresh `cooldown_until` on the clan info itself. This is the * SAME user-level cooldown as `TClans.cooldown_until` (the * cooldown is global to the user, not per clan) — but `getClans` * returns a value cached for up to 30 s while `getClanInfo` * always returns the current value. If the list-cached cooldown * has just expired, this detail call will reflect it first. * * **Username display**: in the default Smartico UI, member rows use * `public_username`. Some surfaces (e.g. the tournament clan * drill-down) prefer `clean_ext_user_id` as the primary display * with `public_username` as fallback — pick the convention that * matches your product's identity model. * * **Idempotency**: safe. Read-only. * * **Side effects**: none — pure metadata read. * * **UI guidance**: see [UI Guide — `getClanInfo`](../../docs/ui/clans/UIGuide_getClanInfo.md). * * **Visitor mode**: not supported. * * @param clanId The clan ID from `TClans.clans[i].clan_id`. * @returns Promise resolving to `TClanInfo`. Rejects if the * clan ID is invalid or the user lacks visibility * for it. * * @example * ```ts * const result = await window._smartico.api.getClans(); * const clan = result.clans[0]; * * console.log('[smartico] loading detail for clan', clan.clan_id); * const detail = await window._smartico.api.getClanInfo(clan.clan_id); * * console.log('[smartico] render detail with', detail.members.length, 'members'); * * // Find current user's row to power a sticky "me" footer. * const meRow = detail.members.find(m => m.is_me); * if (meRow) { * console.log('[smartico] current user rank in this clan:', meRow.position, * 'contribution:', meRow.contribution_score, '— render sticky my-member footer'); * } * * // Resolve Join CTA state using the detail's cooldown (always fresh). * const isMyClan = detail.clan_id === result.user_clan_id; * const inCooldown = detail.cooldown_until != null; * const isFull = detail.member_count >= detail.capacity_limit; * if (isMyClan) { * console.log('[smartico] this is the user\'s clan — render disabled "Your clan" button'); * } else if (inCooldown) { * const ends = new Date(detail.cooldown_until + 'Z'); * console.log('[smartico] user is in clan-switch cooldown until', ends.toLocaleString(), * '— render disabled cooldown button'); * } else if (isFull) { * console.log('[smartico] clan is full — render disabled "Clan is full" button'); * } else { * console.log('[smartico] joinable — render enabled Join button'); * } * ``` */ getClanInfo(clanId: number): Promise; /** * Joins (or switches into) a clan on behalf of the current user. For * paid clans (`entry_fee_amount > 0`), the user's balance for the * matching currency is debited synchronously before the response * returns. If the user is already in a clan, this is treated as an * atomic clan-switch (deactivate old membership + insert new + debit * fee, all in one transaction — the user is never observably * clanless). * * A successful response (`errCode === 0`) means: membership is * persisted, the fee (if any) has been debited, the user's * switch-cooldown has restarted, and a clan-joined CRM event has * fired server-side. * * @remarks * **Result shape note**: `TClanJoinResult` uses `errCode` / `errMsg` * (camelCase) — different from most other SDK result types in this * library which use `err_code` / `err_message` (snake_case). Branch * on the camelCase keys when reading this method's result. * * **Preconditions** * Read the candidate from {@link getClans} and gate the call on * `clan.member_count < clan.capacity_limit`, `cooldown_until == null` * (on the `TClans` payload — the cooldown is user-level, not * per-clan), and the user's balance for `entry_fee_currency_type_id` * being at least `entry_fee_amount` (free clans bypass this check). * The SDK forwards the call unconditionally — calling without * satisfying these will most likely return one of the error codes * below. * * **Error codes** (in `errCode`, typed as {@link JoinClanErrorCode}) * - `0` (`JOIN_CLAN_OK`) — success; membership persisted, fee * debited, cooldown restarted. * - `1000` (`JOIN_CLAN_INVALID_PARAMETERS`) — request body missing * `clan_id`. Should not occur via the SDK; treat as a programming * error. * - `1001` (`JOIN_CLAN_NOT_FOUND`) — clan doesn't exist for this * label, OR the clan is archived / in draft state. The server * merges "missing" and "archived" into this single code. Refresh * `getClans()` and remove the entry from the UI. * - `1002` (`JOIN_CLAN_FULL`) — capacity reached between the list * fetch and the join click (`member_count >= capacity_limit` at * the server's atomic check). Refresh the list — the button will * become "Clan is full". * - `1003` (`JOIN_CLAN_INSUFFICIENT_FUNDS`) — insufficient balance * for the clan's entry fee. A single code covers all three * currencies (points / gems / diamonds); read * `clan.entry_fee_currency_type_id` to identify which currency * the user is short on. Show an insufficient-balance UI naming * the deficit. * - `1004` (`JOIN_CLAN_SEGMENT_MISMATCH`) — user does not meet the * clan's entry segment or numeric conditions. There is no * client-side pre-guard for this — surface a gating message. * - `1005` (`JOIN_CLAN_USER_IS_NOT_IN_CLAN`) — this method NEVER * returns `1005`. The code is shared with the tournament * registration error space and fires only on * {@link registerInTournament} responses when a clan-based * tournament requires the user to be in a clan first. * - `1006` (`JOIN_CLAN_COOLDOWN_ACTIVE`) — user is inside the * switch-cooldown window from a previous clan join (default * 7 days, configurable per label). Only reachable when the user is * already in a *different* clan (this call is then a clan switch); * a first-time join never returns `1006`. Surface the * `TClans.cooldown_until` expiry in the disabled CTA. * - `1011` (`JOIN_CLAN_JOINED_AFTER_TOURNAMENT_START`) — this * method does NOT return `1011` directly. The code fires on * {@link registerInTournament} responses when the user joined * their current clan after the tournament started AND already * has tournament scores. * - other non-zero — generic server error. Surface `errMsg` if any. * * **Idempotency**: NOT safe — and notably dangerous. Calling * `joinClan` with the user's CURRENT `clanId` does NOT short-circuit * server-side; the call is treated as a clan-switch to the same * clan. If the label's cooldown is non-zero (the default), the * second call returns `1006`. If the cooldown is configured to * zero, the second call will deduct the entry fee AGAIN, deactivate * the membership, and re-insert it. The SDK does NOT enforce an * in-flight lock. Consumers MUST guard the call site against * double-clicks (set a local "joining" flag on click, clear it on * response) AND short-circuit when the user is already in the * target clan. * * **Refresh after success (and after failure)** * The SDK does NOT automatically refresh the {@link getClans} cache * after a join. Manually re-call `getClans()` on success to pick up * the new `user_clan_id`, refreshed `member_count`, and the new * `cooldown_until` value. Until you do, the cached `TClans` payload * is stale. * * **Side effects** (consumer-observable on success) * - Entry-fee currency balance decreases by `entry_fee_amount` * (visible on the next {@link getUserProfile} refresh / via the * user-properties update channel). * - The user's clan membership row is created (or replaced, on a * switch). `member_count` on the new clan increments by 1; on a * switch, the old clan's `member_count` decrements by 1 in the * same transaction. * - The user's switch-cooldown restarts at the join time (the * `TClans.cooldown_until` field on the next list fetch reflects * the new expiry). * - Server-side CRM analytics fire (clan-joined event, currency- * deducted event for paid clans). These surface only as * server-side traces; the SDK consumer observes them indirectly * via balance / membership refresh. * * **UI guidance**: see [UI Guide — `joinClan`](../../docs/ui/clans/UIGuide_joinClan.md). * * **Visitor mode**: not supported. The server rejects the request * for unauthenticated sessions. * * @param clanId The clan ID from `TClans.clans[i].clan_id` (or * `TClanInfo.clan_id`). * @returns `{ errCode, errMsg }`; success when `errCode === 0`. * Note camelCase keys (see "Result shape note" above). * * @example * ```ts * const result = await window._smartico.api.getClans(); * const target = result.clans.find(c => c.clan_id === clanId); * * if (!target) { * console.log('[smartico] clan not in current list — refresh getClans and retry'); * return; * } * if (target.clan_id === result.user_clan_id) { * console.log('[smartico] user is already in this clan — short-circuit; do NOT call joinClan again, the server will treat it as a switch and may re-deduct the fee'); * return; * } * if (result.cooldown_until) { * console.log('[smartico] user is in cooldown — disable Join until', new Date(result.cooldown_until + 'Z').toLocaleString()); * return; * } * if (target.member_count >= target.capacity_limit) { * console.log('[smartico] clan is full — disable Join'); * return; * } * * console.log('[smartico] join starting — set in-flight flag, show loading dots on Join button, keep modal open'); * const r = await window._smartico.api.joinClan(target.clan_id); * console.log('[smartico] join response received — clear in-flight flag'); * * if (r.errCode === 0) { * console.log('[smartico] joined successfully — refresh getClans to pick up the new user_clan_id, member_count, and cooldown_until'); * await window._smartico.api.getClans(); * } else if (r.errCode === 1002) { * console.error('[smartico] clan is full — refresh list, hide the Join CTA on the now-full clan'); * } else if (r.errCode === 1003) { * const currencyLabel = ['points', 'gems', 'diamonds'][target.entry_fee_currency_type_id] ?? 'currency'; * console.error('[smartico] insufficient', currencyLabel, '— need', target.entry_fee_amount); * } else if (r.errCode === 1004) { * console.error('[smartico] user does not meet segment / entry conditions — show a gating message'); * } else if (r.errCode === 1006) { * console.error('[smartico] cooldown active — surface the cooldown_until from getClans:', result.cooldown_until); * } else if (r.errCode === 1001) { * console.error('[smartico] clan not found or archived — refresh list and remove from UI'); * } else { * console.error('[smartico] join failed — show a generic error toast with this message:', r.errMsg); * } * ``` */ joinClan(clanId: number): Promise; protected updateClans(): Promise; }