@pixelume/sdk 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,238 @@
1
+ import { parseCloudSaveEnvelope, parseEmptyResponse, parseSaveList, parseSaveMetadata, parseSaveMetadataEnvelope, } from '../../contracts/index.js';
2
+ import { requireGameId } from '../../core/config.js';
3
+ import { PixelumeConfigError, PixelumeHttpError, PixelumeSaveConflictError, isPixelumeError, } from '../../core/errors.js';
4
+ /** Same rules as the API (`pixelume-api/src/domain/save/cloud-save.ts`). */
5
+ const SLOT_ID_PATTERN = /^[a-z0-9][a-z0-9_-]{0,63}$/u;
6
+ const MAX_SCHEMA_VERSION = 1_000_000;
7
+ const MAX_JSON_DEPTH = 32;
8
+ const IDLE = Object.freeze({ pending: 0, pendingWrites: 0, last: null });
9
+ export class SavesModule {
10
+ #session;
11
+ #config;
12
+ #listeners = new Set();
13
+ #activity = IDLE;
14
+ constructor(session, config) {
15
+ this.#session = session;
16
+ this.#config = config;
17
+ }
18
+ get activity() {
19
+ return this.#activity;
20
+ }
21
+ subscribe(listener) {
22
+ this.#listeners.add(listener);
23
+ return () => this.#listeners.delete(listener);
24
+ }
25
+ list(options = {}) {
26
+ return this.#track('list', null, async () => {
27
+ const gameId = this.#gameId();
28
+ const response = await this.#session.authorizedRequest({
29
+ method: 'GET',
30
+ path: `/v1/games/${gameId}/saves`,
31
+ signal: options.signal,
32
+ timeoutMs: options.timeoutMs,
33
+ parse: parseSaveList,
34
+ });
35
+ const { saves, limits, usage } = response.data;
36
+ return Object.freeze({
37
+ gameId: response.data.gameId,
38
+ saves: Object.freeze(saves.map(freezeMetadata)),
39
+ limits: Object.freeze({ ...limits }),
40
+ usage: Object.freeze({ ...usage }),
41
+ });
42
+ });
43
+ }
44
+ load(slotId, options = {}) {
45
+ return this.#track('load', slotId, async () => {
46
+ const gameId = this.#gameId();
47
+ validateSlotId(slotId);
48
+ try {
49
+ const response = await this.#session.authorizedRequest({
50
+ method: 'GET',
51
+ path: `/v1/games/${gameId}/saves/${slotId}`,
52
+ signal: options.signal,
53
+ timeoutMs: options.timeoutMs,
54
+ parse: parseCloudSaveEnvelope,
55
+ });
56
+ const { data, ...metadata } = response.data;
57
+ return Object.freeze({ ...metadata, data: data });
58
+ }
59
+ catch (error) {
60
+ if (isSaveNotFound(error))
61
+ return null;
62
+ throw error;
63
+ }
64
+ });
65
+ }
66
+ save(slotId, write, options = {}) {
67
+ return this.#track('save', slotId, async () => {
68
+ const gameId = this.#gameId();
69
+ validateSlotId(slotId);
70
+ const untrusted = write; // JavaScript callers may pass anything
71
+ const input = (typeof untrusted === 'object' && untrusted !== null ? untrusted : {});
72
+ const schemaVersion = validateSchemaVersion(input.schemaVersion);
73
+ const expectedRevision = validateRevision(input.expectedRevision, 0);
74
+ validateSaveData(input.data);
75
+ try {
76
+ const response = await this.#session.authorizedRequest({
77
+ method: 'PUT',
78
+ path: `/v1/games/${gameId}/saves/${slotId}`,
79
+ json: { schemaVersion, expectedRevision, data: input.data },
80
+ signal: options.signal,
81
+ timeoutMs: options.timeoutMs,
82
+ parse: parseSaveMetadataEnvelope,
83
+ });
84
+ return Object.freeze({
85
+ created: response.status === 201,
86
+ save: freezeMetadata(response.data),
87
+ });
88
+ }
89
+ catch (error) {
90
+ throw toConflict(error, expectedRevision);
91
+ }
92
+ });
93
+ }
94
+ delete(slotId, options) {
95
+ return this.#track('delete', slotId, async () => {
96
+ const gameId = this.#gameId();
97
+ validateSlotId(slotId);
98
+ const untrusted = options;
99
+ const input = (typeof untrusted === 'object' && untrusted !== null ? untrusted : {});
100
+ const expectedRevision = validateRevision(input.expectedRevision, 1);
101
+ try {
102
+ await this.#session.authorizedRequest({
103
+ method: 'DELETE',
104
+ path: `/v1/games/${gameId}/saves/${slotId}`,
105
+ query: { expectedRevision },
106
+ signal: input.signal,
107
+ timeoutMs: input.timeoutMs,
108
+ parse: parseEmptyResponse,
109
+ });
110
+ return 'deleted';
111
+ }
112
+ catch (error) {
113
+ if (isSaveNotFound(error))
114
+ return 'not_found';
115
+ throw toConflict(error, expectedRevision);
116
+ }
117
+ });
118
+ }
119
+ #gameId() {
120
+ const gameId = requireGameId(this.#config, 'Cloud Saves');
121
+ if (this.#config.clientId !== gameId) {
122
+ // The API only accepts tokens issued to the game in the URL (403 FORBIDDEN_GAME).
123
+ throw new PixelumeConfigError('Cloud Saves requires clientId to be equal to gameId');
124
+ }
125
+ return gameId;
126
+ }
127
+ async #track(operation, slotId, run) {
128
+ const write = operation === 'save' || operation === 'delete';
129
+ this.#update({
130
+ pending: this.#activity.pending + 1,
131
+ pendingWrites: this.#activity.pendingWrites + (write ? 1 : 0),
132
+ last: this.#activity.last,
133
+ });
134
+ let outcome = 'success';
135
+ let failure = null;
136
+ try {
137
+ return await run();
138
+ }
139
+ catch (error) {
140
+ outcome = error instanceof PixelumeSaveConflictError ? 'conflict' : 'error';
141
+ failure = isPixelumeError(error) ? error : null;
142
+ throw error;
143
+ }
144
+ finally {
145
+ this.#update({
146
+ pending: this.#activity.pending - 1,
147
+ pendingWrites: this.#activity.pendingWrites - (write ? 1 : 0),
148
+ last: Object.freeze({
149
+ operation,
150
+ slotId: typeof slotId === 'string' ? slotId : null,
151
+ outcome,
152
+ at: Date.now(),
153
+ error: failure,
154
+ }),
155
+ });
156
+ }
157
+ }
158
+ #update(next) {
159
+ const previous = this.#activity;
160
+ this.#activity = Object.freeze(next);
161
+ for (const listener of [...this.#listeners])
162
+ listener(this.#activity, previous);
163
+ }
164
+ }
165
+ function freezeMetadata(metadata) {
166
+ return Object.freeze({ ...metadata });
167
+ }
168
+ function isSaveNotFound(error) {
169
+ return (error instanceof PixelumeHttpError && error.status === 404 && error.code === 'SAVE_NOT_FOUND');
170
+ }
171
+ function toConflict(error, expectedRevision) {
172
+ if (!(error instanceof PixelumeHttpError) || error.status !== 409)
173
+ return error;
174
+ const current = parseSaveMetadata(error.details?.current);
175
+ return new PixelumeSaveConflictError(error, current && freezeMetadata(current), expectedRevision);
176
+ }
177
+ function validateSlotId(slotId) {
178
+ if (typeof slotId !== 'string' || !SLOT_ID_PATTERN.test(slotId)) {
179
+ throw new PixelumeConfigError('slotId must match ^[a-z0-9][a-z0-9_-]{0,63}$ (e.g. "main", "slot-2", "autosave_1")');
180
+ }
181
+ }
182
+ function validateSchemaVersion(value) {
183
+ if (typeof value !== 'number' ||
184
+ !Number.isInteger(value) ||
185
+ value < 1 ||
186
+ value > MAX_SCHEMA_VERSION) {
187
+ throw new PixelumeConfigError(`schemaVersion must be an integer between 1 and ${MAX_SCHEMA_VERSION}`);
188
+ }
189
+ return value;
190
+ }
191
+ function validateRevision(value, minimum) {
192
+ if (typeof value !== 'number' || !Number.isSafeInteger(value) || value < minimum) {
193
+ throw new PixelumeConfigError(minimum === 0
194
+ ? 'expectedRevision must be a non-negative integer (0 creates a new save)'
195
+ : 'expectedRevision must be a positive integer (the current revision of the save)');
196
+ }
197
+ return value;
198
+ }
199
+ /**
200
+ * Rejects values `JSON.stringify` would silently drop or change (undefined, functions, NaN,
201
+ * Dates, class instances…), so what is stored is exactly what the game passed. Also mirrors the
202
+ * API depth limit (root object = level 1, at most 32 levels) and stops on cycles.
203
+ */
204
+ function validateSaveData(value) {
205
+ if (!isPlainObject(value))
206
+ throw new PixelumeConfigError('Save data must be a plain JSON object');
207
+ const visit = (item, level) => {
208
+ if (level > MAX_JSON_DEPTH) {
209
+ throw new PixelumeConfigError(`Save data cannot be nested deeper than ${MAX_JSON_DEPTH} levels (or contains a cycle)`);
210
+ }
211
+ if (item === null || typeof item === 'string' || typeof item === 'boolean')
212
+ return;
213
+ if (typeof item === 'number') {
214
+ if (Number.isFinite(item))
215
+ return;
216
+ throw new PixelumeConfigError('Save data cannot contain NaN or Infinity');
217
+ }
218
+ if (Array.isArray(item)) {
219
+ for (const entry of item)
220
+ visit(entry, level + 1);
221
+ return;
222
+ }
223
+ if (isPlainObject(item)) {
224
+ for (const entry of Object.values(item))
225
+ visit(entry, level + 1);
226
+ return;
227
+ }
228
+ throw new PixelumeConfigError('Save data can contain only plain objects, arrays, strings, finite numbers, booleans and null');
229
+ };
230
+ visit(value, 1);
231
+ }
232
+ function isPlainObject(value) {
233
+ if (typeof value !== 'object' || value === null || Array.isArray(value))
234
+ return false;
235
+ const prototype = Object.getPrototypeOf(value);
236
+ return prototype === Object.prototype || prototype === null;
237
+ }
238
+ //# sourceMappingURL=SavesModule.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SavesModule.js","sourceRoot":"","sources":["../../../src/modules/saves/SavesModule.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n parseCloudSaveEnvelope,\n parseEmptyResponse,\n parseSaveList,\n parseSaveMetadata,\n parseSaveMetadataEnvelope,\n type SaveMetadataWire,\n} from '../../contracts/index.js';\nimport { requireGameId, type ResolvedConfig } from '../../core/config.js';\nimport {\n PixelumeConfigError,\n PixelumeHttpError,\n PixelumeSaveConflictError,\n isPixelumeError,\n type PixelumeError,\n type PixelumeSaveMetadata,\n} from '../../core/errors.js';\nimport type { PixelumeRequestOptions } from '../../core/types.js';\nimport type { SessionManager } from '../auth/SessionManager.js';\n\n/** Same rules as the API (`pixelume-api/src/domain/save/cloud-save.ts`). */\nconst SLOT_ID_PATTERN = /^[a-z0-9][a-z0-9_-]{0,63}$/u;\nconst MAX_SCHEMA_VERSION = 1_000_000;\nconst MAX_JSON_DEPTH = 32;\n\n/** A cloud save with its data. `data` is whatever the game stored: validate it before use. */\nexport interface PixelumeSave<\n T extends object = Record<string, unknown>,\n> extends PixelumeSaveMetadata {\n readonly data: T;\n}\n\nexport interface PixelumeSaveList {\n readonly gameId: string;\n /** Metadata of every live slot of the current player in this game (no data). */\n readonly saves: readonly PixelumeSaveMetadata[];\n /** Limits currently enforced by the API. */\n readonly limits: {\n readonly maxSlotsPerGame: number;\n readonly maxDataBytes: number;\n /** Sum of all games of the player. */\n readonly maxBytesPerPlayer: number;\n };\n readonly usage: { readonly slots: number; readonly playerBytes: number };\n}\n\nexport interface PixelumeSaveWrite<T extends object> {\n /** Version of the game's save format (integer 1–1 000 000), used by the game to migrate. */\n readonly schemaVersion: number;\n /**\n * Revision the game last saw for this slot: `0` creates a new save (fails if one exists);\n * `n` updates only if the slot is still at revision `n`. There is no blind overwrite.\n */\n readonly expectedRevision: number;\n /** A JSON object (plain objects, arrays, strings, finite numbers, booleans, null). */\n readonly data: T;\n}\n\nexport interface PixelumeSaveDeleteOptions extends PixelumeRequestOptions {\n /** Current revision of the save to delete (≥ 1). */\n readonly expectedRevision: number;\n}\n\nexport interface PixelumeSaveResult {\n /** `true` when the slot was created (201), `false` when an existing save was updated (200). */\n readonly created: boolean;\n readonly save: PixelumeSaveMetadata;\n}\n\n/**\n * `deleted`: the save at `expectedRevision` is gone (repeating the same delete also returns\n * `deleted`). `not_found`: there was no save in this slot.\n */\nexport type PixelumeSaveDeleteResult = 'deleted' | 'not_found';\n\nexport type PixelumeSavesOperation = 'list' | 'load' | 'save' | 'delete';\n\n/** Outcome of the last explicit Saves call, for status UIs. Never contains save data. */\nexport interface PixelumeSavesActivity {\n /** Saves calls currently in flight. */\n readonly pending: number;\n /** `save`/`delete` calls currently in flight. */\n readonly pendingWrites: number;\n readonly last: {\n readonly operation: PixelumeSavesOperation;\n readonly slotId: string | null;\n readonly outcome: 'success' | 'conflict' | 'error';\n /** Local time (ms since epoch) when the call settled. */\n readonly at: number;\n readonly error: PixelumeError | null;\n } | null;\n}\n\nexport type PixelumeSavesListener = (\n activity: PixelumeSavesActivity,\n previous: PixelumeSavesActivity,\n) => void;\n\n/**\n * Cloud Saves of the signed-in player for the configured `gameId`. Every call needs a session\n * with the `saves` scope; the API derives the player from the access token, never from input.\n *\n * Saves are unverified, client-supplied personal data: never use them as proof of achievements,\n * rankings or rewards, and never store secrets or personal data in them.\n */\nexport interface PixelumeSavesApi {\n /** Lists slot metadata, limits and usage. */\n list(options?: PixelumeRequestOptions): Promise<PixelumeSaveList>;\n /** Loads one slot, or `null` when it has no save. */\n load<T extends object = Record<string, unknown>>(\n slotId: string,\n options?: PixelumeRequestOptions,\n ): Promise<PixelumeSave<T> | null>;\n /**\n * Creates (`expectedRevision: 0`) or updates a slot. A revision mismatch rejects with\n * `PixelumeSaveConflictError`; the SDK never retries it or overwrites the remote save.\n */\n save<T extends object>(\n slotId: string,\n write: PixelumeSaveWrite<T>,\n options?: PixelumeRequestOptions,\n ): Promise<PixelumeSaveResult>;\n /** Deletes a slot at `expectedRevision`. Conflicts reject with `PixelumeSaveConflictError`. */\n delete(slotId: string, options: PixelumeSaveDeleteOptions): Promise<PixelumeSaveDeleteResult>;\n /** Current activity of explicit Saves calls. */\n readonly activity: PixelumeSavesActivity;\n /** Subscribes to `activity` changes. The returned function removes the listener. */\n subscribe(listener: PixelumeSavesListener): () => void;\n}\n\nconst IDLE: PixelumeSavesActivity = Object.freeze({ pending: 0, pendingWrites: 0, last: null });\n\nexport class SavesModule implements PixelumeSavesApi {\n readonly #session: SessionManager;\n readonly #config: ResolvedConfig;\n readonly #listeners = new Set<PixelumeSavesListener>();\n #activity: PixelumeSavesActivity = IDLE;\n\n constructor(session: SessionManager, config: ResolvedConfig) {\n this.#session = session;\n this.#config = config;\n }\n\n get activity(): PixelumeSavesActivity {\n return this.#activity;\n }\n\n subscribe(listener: PixelumeSavesListener): () => void {\n this.#listeners.add(listener);\n return () => this.#listeners.delete(listener);\n }\n\n list(options: PixelumeRequestOptions = {}): Promise<PixelumeSaveList> {\n return this.#track('list', null, async () => {\n const gameId = this.#gameId();\n const response = await this.#session.authorizedRequest({\n method: 'GET',\n path: `/v1/games/${gameId}/saves`,\n signal: options.signal,\n timeoutMs: options.timeoutMs,\n parse: parseSaveList,\n });\n const { saves, limits, usage } = response.data;\n return Object.freeze({\n gameId: response.data.gameId,\n saves: Object.freeze(saves.map(freezeMetadata)),\n limits: Object.freeze({ ...limits }),\n usage: Object.freeze({ ...usage }),\n });\n });\n }\n\n load<T extends object = Record<string, unknown>>(\n slotId: string,\n options: PixelumeRequestOptions = {},\n ): Promise<PixelumeSave<T> | null> {\n return this.#track('load', slotId, async () => {\n const gameId = this.#gameId();\n validateSlotId(slotId);\n try {\n const response = await this.#session.authorizedRequest({\n method: 'GET',\n path: `/v1/games/${gameId}/saves/${slotId}`,\n signal: options.signal,\n timeoutMs: options.timeoutMs,\n parse: parseCloudSaveEnvelope,\n });\n const { data, ...metadata } = response.data;\n return Object.freeze({ ...metadata, data: data as T });\n } catch (error) {\n if (isSaveNotFound(error)) return null;\n throw error;\n }\n });\n }\n\n save<T extends object>(\n slotId: string,\n write: PixelumeSaveWrite<T>,\n options: PixelumeRequestOptions = {},\n ): Promise<PixelumeSaveResult> {\n return this.#track('save', slotId, async () => {\n const gameId = this.#gameId();\n validateSlotId(slotId);\n const untrusted: unknown = write; // JavaScript callers may pass anything\n const input = (\n typeof untrusted === 'object' && untrusted !== null ? untrusted : {}\n ) as Partial<PixelumeSaveWrite<T>>;\n const schemaVersion = validateSchemaVersion(input.schemaVersion);\n const expectedRevision = validateRevision(input.expectedRevision, 0);\n validateSaveData(input.data);\n try {\n const response = await this.#session.authorizedRequest({\n method: 'PUT',\n path: `/v1/games/${gameId}/saves/${slotId}`,\n json: { schemaVersion, expectedRevision, data: input.data },\n signal: options.signal,\n timeoutMs: options.timeoutMs,\n parse: parseSaveMetadataEnvelope,\n });\n return Object.freeze({\n created: response.status === 201,\n save: freezeMetadata(response.data),\n });\n } catch (error) {\n throw toConflict(error, expectedRevision);\n }\n });\n }\n\n delete(slotId: string, options: PixelumeSaveDeleteOptions): Promise<PixelumeSaveDeleteResult> {\n return this.#track('delete', slotId, async () => {\n const gameId = this.#gameId();\n validateSlotId(slotId);\n const untrusted: unknown = options;\n const input = (\n typeof untrusted === 'object' && untrusted !== null ? untrusted : {}\n ) as Partial<PixelumeSaveDeleteOptions>;\n const expectedRevision = validateRevision(input.expectedRevision, 1);\n try {\n await this.#session.authorizedRequest({\n method: 'DELETE',\n path: `/v1/games/${gameId}/saves/${slotId}`,\n query: { expectedRevision },\n signal: input.signal,\n timeoutMs: input.timeoutMs,\n parse: parseEmptyResponse,\n });\n return 'deleted';\n } catch (error) {\n if (isSaveNotFound(error)) return 'not_found';\n throw toConflict(error, expectedRevision);\n }\n });\n }\n\n #gameId(): string {\n const gameId = requireGameId(this.#config, 'Cloud Saves');\n if (this.#config.clientId !== gameId) {\n // The API only accepts tokens issued to the game in the URL (403 FORBIDDEN_GAME).\n throw new PixelumeConfigError('Cloud Saves requires clientId to be equal to gameId');\n }\n return gameId;\n }\n\n async #track<T>(\n operation: PixelumeSavesOperation,\n slotId: string | null,\n run: () => Promise<T>,\n ): Promise<T> {\n const write = operation === 'save' || operation === 'delete';\n this.#update({\n pending: this.#activity.pending + 1,\n pendingWrites: this.#activity.pendingWrites + (write ? 1 : 0),\n last: this.#activity.last,\n });\n let outcome: 'success' | 'conflict' | 'error' = 'success';\n let failure: PixelumeError | null = null;\n try {\n return await run();\n } catch (error) {\n outcome = error instanceof PixelumeSaveConflictError ? 'conflict' : 'error';\n failure = isPixelumeError(error) ? error : null;\n throw error;\n } finally {\n this.#update({\n pending: this.#activity.pending - 1,\n pendingWrites: this.#activity.pendingWrites - (write ? 1 : 0),\n last: Object.freeze({\n operation,\n slotId: typeof slotId === 'string' ? slotId : null,\n outcome,\n at: Date.now(),\n error: failure,\n }),\n });\n }\n }\n\n #update(next: PixelumeSavesActivity): void {\n const previous = this.#activity;\n this.#activity = Object.freeze(next);\n for (const listener of [...this.#listeners]) listener(this.#activity, previous);\n }\n}\n\nfunction freezeMetadata(metadata: SaveMetadataWire): PixelumeSaveMetadata {\n return Object.freeze({ ...metadata });\n}\n\nfunction isSaveNotFound(error: unknown): boolean {\n return (\n error instanceof PixelumeHttpError && error.status === 404 && error.code === 'SAVE_NOT_FOUND'\n );\n}\n\nfunction toConflict(error: unknown, expectedRevision: number): unknown {\n if (!(error instanceof PixelumeHttpError) || error.status !== 409) return error;\n const current = parseSaveMetadata(error.details?.current);\n return new PixelumeSaveConflictError(error, current && freezeMetadata(current), expectedRevision);\n}\n\nfunction validateSlotId(slotId: unknown): asserts slotId is string {\n if (typeof slotId !== 'string' || !SLOT_ID_PATTERN.test(slotId)) {\n throw new PixelumeConfigError(\n 'slotId must match ^[a-z0-9][a-z0-9_-]{0,63}$ (e.g. \"main\", \"slot-2\", \"autosave_1\")',\n );\n }\n}\n\nfunction validateSchemaVersion(value: unknown): number {\n if (\n typeof value !== 'number' ||\n !Number.isInteger(value) ||\n value < 1 ||\n value > MAX_SCHEMA_VERSION\n ) {\n throw new PixelumeConfigError(\n `schemaVersion must be an integer between 1 and ${MAX_SCHEMA_VERSION}`,\n );\n }\n return value;\n}\n\nfunction validateRevision(value: unknown, minimum: 0 | 1): number {\n if (typeof value !== 'number' || !Number.isSafeInteger(value) || value < minimum) {\n throw new PixelumeConfigError(\n minimum === 0\n ? 'expectedRevision must be a non-negative integer (0 creates a new save)'\n : 'expectedRevision must be a positive integer (the current revision of the save)',\n );\n }\n return value;\n}\n\n/**\n * Rejects values `JSON.stringify` would silently drop or change (undefined, functions, NaN,\n * Dates, class instances…), so what is stored is exactly what the game passed. Also mirrors the\n * API depth limit (root object = level 1, at most 32 levels) and stops on cycles.\n */\nfunction validateSaveData(value: unknown): void {\n if (!isPlainObject(value)) throw new PixelumeConfigError('Save data must be a plain JSON object');\n const visit = (item: unknown, level: number): void => {\n if (level > MAX_JSON_DEPTH) {\n throw new PixelumeConfigError(\n `Save data cannot be nested deeper than ${MAX_JSON_DEPTH} levels (or contains a cycle)`,\n );\n }\n if (item === null || typeof item === 'string' || typeof item === 'boolean') return;\n if (typeof item === 'number') {\n if (Number.isFinite(item)) return;\n throw new PixelumeConfigError('Save data cannot contain NaN or Infinity');\n }\n if (Array.isArray(item)) {\n for (const entry of item as unknown[]) visit(entry, level + 1);\n return;\n }\n if (isPlainObject(item)) {\n for (const entry of Object.values(item)) visit(entry, level + 1);\n return;\n }\n throw new PixelumeConfigError(\n 'Save data can contain only plain objects, arrays, strings, finite numbers, booleans and null',\n );\n };\n visit(value, 1);\n}\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n if (typeof value !== 'object' || value === null || Array.isArray(value)) return false;\n const prototype: unknown = Object.getPrototypeOf(value);\n return prototype === Object.prototype || prototype === null;\n}\n"]}
package/dist/ui/widget.js CHANGED
@@ -183,6 +183,7 @@ export function mountPixelumeWidget(pixelume, options = {}) {
183
183
  return;
184
184
  }
185
185
  if (state.status === 'authenticated') {
186
+ const session = sessionState();
186
187
  const identity = document.createElement('div');
187
188
  identity.className = 'identity';
188
189
  const avatar = document.createElement('span');
@@ -194,6 +195,12 @@ export function mountPixelumeWidget(pixelume, options = {}) {
194
195
  copy.append(textElement(document, 'p', 'title', `Olá, ${state.player.displayName}!`), textElement(document, 'p', 'message', 'Sua conta está conectada a este jogo.'));
195
196
  identity.append(avatar, copy);
196
197
  content.append(identity);
198
+ if (session?.error) {
199
+ content.append(notice(document, 'Não conseguimos falar com a Pixelume agora. Sua conta continua conectada e tentaremos de novo automaticamente.'));
200
+ }
201
+ const sync = syncMessage(savesActivity());
202
+ if (sync)
203
+ content.append(status(document, sync));
197
204
  if (localError)
198
205
  content.append(notice(document, localError));
199
206
  const logout = button(document, 'action secondary', 'Sair da conta Pixelume neste jogo');
@@ -208,10 +215,9 @@ export function mountPixelumeWidget(pixelume, options = {}) {
208
215
  copy.className = 'state-copy';
209
216
  copy.append(textElement(document, 'p', 'title', anonymousTitle(state)), textElement(document, 'p', 'message', anonymousMessage(state)));
210
217
  content.append(copy);
211
- if (state.error || localError) {
212
- content.append(notice(document, localError ??
213
- 'Não foi possível verificar sua conta. Você pode continuar jogando e tentar novamente.'));
214
- }
218
+ const failure = localError ?? anonymousNotice(state);
219
+ if (failure)
220
+ content.append(notice(document, failure));
215
221
  const login = button(document, 'action', 'Entrar com Pixelume');
216
222
  login.type = 'button';
217
223
  login.textContent = pendingAction === 'login' ? 'Abrindo Pixelume…' : 'Entrar com Pixelume';
@@ -223,6 +229,16 @@ export function mountPixelumeWidget(pixelume, options = {}) {
223
229
  localError = null;
224
230
  render(state);
225
231
  });
232
+ // Optional sources (absent in custom/mocked clients): token renewal and explicit Saves calls.
233
+ const auth = pixelume.auth;
234
+ const saves = pixelume.saves;
235
+ const sessionState = () => auth.session ?? null;
236
+ const savesActivity = () => saves?.activity ?? null;
237
+ const rerender = () => {
238
+ render(pixelume.auth.state);
239
+ };
240
+ const unsubscribeSession = typeof auth.subscribeSession === 'function' ? auth.subscribeSession(rerender) : null;
241
+ const unsubscribeSaves = typeof saves?.subscribe === 'function' ? saves.subscribe(rerender) : null;
226
242
  const stopInputPropagation = (event) => {
227
243
  event.stopPropagation();
228
244
  };
@@ -263,6 +279,8 @@ export function mountPixelumeWidget(pixelume, options = {}) {
263
279
  removeDocumentKeydown?.();
264
280
  removeDocumentKeydown = null;
265
281
  unsubscribe();
282
+ unsubscribeSession?.();
283
+ unsubscribeSaves?.();
266
284
  trigger.removeEventListener('click', togglePanel);
267
285
  close.removeEventListener('click', closeFromButton);
268
286
  for (const eventName of isolatedEvents)
@@ -336,6 +354,45 @@ function notice(document, text) {
336
354
  element.setAttribute('role', 'alert');
337
355
  return element;
338
356
  }
357
+ function status(document, text) {
358
+ const element = textElement(document, 'p', 'message sync', text);
359
+ element.setAttribute('role', 'status');
360
+ return element;
361
+ }
362
+ /** Cloud status only after the game explicitly saved or deleted; nothing is simulated. */
363
+ function syncMessage(activity) {
364
+ if (activity === null)
365
+ return null;
366
+ if (activity.pendingWrites > 0)
367
+ return 'Salvando na nuvem…';
368
+ const last = activity.last;
369
+ if (last === null || (last.operation !== 'save' && last.operation !== 'delete'))
370
+ return null;
371
+ if (last.outcome === 'conflict') {
372
+ return 'Há um progresso mais recente na nuvem. O jogo vai decidir o que manter.';
373
+ }
374
+ if (last.outcome === 'error')
375
+ return 'Não foi possível salvar na nuvem agora.';
376
+ const time = new Date(last.at).toLocaleTimeString('pt-BR', {
377
+ hour: '2-digit',
378
+ minute: '2-digit',
379
+ });
380
+ return last.operation === 'save'
381
+ ? `Progresso salvo na nuvem às ${time}.`
382
+ : `Save removido da nuvem às ${time}.`;
383
+ }
384
+ function anonymousNotice(state) {
385
+ const error = state.error;
386
+ if (error === null)
387
+ return null;
388
+ if (error.kind === 'auth' && Reflect.get(error, 'reason') === 'game_unavailable') {
389
+ return 'O acesso deste jogo à Pixelume está indisponível no momento. Você pode continuar jogando.';
390
+ }
391
+ // An ended session is already explained by the "expired" title and message.
392
+ if (state.reason === 'expired')
393
+ return null;
394
+ return 'Não foi possível verificar sua conta. Você pode continuar jogando e tentar novamente.';
395
+ }
339
396
  function renderLoading(document, content, status) {
340
397
  const labels = {
341
398
  uninitialized: 'Preparando sua conta…',
@@ -1 +1 @@
1
- {"version":3,"file":"widget.js","sourceRoot":"","sources":["../../src/ui/widget.ts"],"names":[],"mappings":"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","sourcesContent":["import type { PixelumeClient } from '../core/PixelumeClient.js';\nimport type { PixelumeAuthState } from '../modules/auth/AuthModule.js';\nimport { WIDGET_STYLES } from './styles.js';\n\nexport type PixelumeWidgetPosition = 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';\n\nexport interface PixelumeWidgetOptions {\n /** Corner used by the widget. Defaults to `bottom-right`. */\n readonly position?: PixelumeWidgetPosition;\n /** Distance from the selected edges in CSS pixels. Defaults to `16`. */\n readonly offset?: number;\n /** Mount target. Defaults to `document.body`; custom targets should establish a containing block. */\n readonly container?: HTMLElement;\n /** Visual treatment. `pixel` is currently the only supported theme. */\n readonly theme?: 'pixel';\n /** Stacking order for the host. Defaults to `2147483000`. */\n readonly zIndex?: number;\n}\n\nexport interface PixelumeWidgetController {\n /** The Shadow DOM host created by the widget. */\n readonly element: HTMLElement;\n /** Hides the widget and closes its panel without changing authentication state. */\n hide(): void;\n /** Shows a previously hidden widget. */\n show(): void;\n /** Removes DOM/listeners/subscriptions. Safe to call more than once. */\n destroy(): void;\n}\n\nconst DEFAULT_POSITION: PixelumeWidgetPosition = 'bottom-right';\nconst DEFAULT_OFFSET = 16;\nconst DEFAULT_Z_INDEX = 2_147_483_000;\nconst POSITIONS: readonly PixelumeWidgetPosition[] = [\n 'top-left',\n 'top-right',\n 'bottom-left',\n 'bottom-right',\n];\n\ninterface MountedWidget {\n readonly container: HTMLElement;\n readonly controller: PixelumeWidgetController;\n}\n\nconst widgetsByClient = new WeakMap<object, MountedWidget>();\nconst widgetsByContainer = new WeakMap<HTMLElement, object>();\nlet nextWidgetId = 1;\n\n/**\n * Mounts the optional Pixelume account widget. Importing this module alone has no DOM side effects.\n * Repeating the call with the same client and container returns the existing controller.\n */\nexport function mountPixelumeWidget(\n pixelume: PixelumeClient,\n options: PixelumeWidgetOptions = {},\n): PixelumeWidgetController {\n assertClient(pixelume);\n const document = resolveDocument(options.container);\n const container = options.container ?? document.body;\n\n const mounted = widgetsByClient.get(pixelume);\n if (mounted) {\n if (mounted.container === container) return mounted.controller;\n throw new Error('This Pixelume client already has a widget mounted in another container');\n }\n const containerOwner = widgetsByContainer.get(container);\n if (containerOwner) {\n throw new Error('This container already has a Pixelume widget mounted');\n }\n\n const position = resolvePosition(options.position);\n const offset = resolveNumber('offset', options.offset, DEFAULT_OFFSET, 0, 256);\n const zIndex = resolveNumber('zIndex', options.zIndex, DEFAULT_Z_INDEX, 0, 2_147_483_647);\n const theme: unknown = options.theme;\n if (theme !== undefined && theme !== 'pixel') {\n throw new Error('Pixelume widget theme must be \"pixel\"');\n }\n\n const host = document.createElement('div');\n host.dataset.pixelumeWidget = '';\n host.dataset.position = position;\n host.style.setProperty('--pixelume-offset', `${offset}px`);\n host.style.setProperty('--pixelume-z-index', String(zIndex));\n if (options.container) host.dataset.contained = '';\n\n const shadow = host.attachShadow({ mode: 'open' });\n const style = document.createElement('style');\n style.textContent = WIDGET_STYLES;\n\n const root = document.createElement('div');\n root.className = 'root';\n\n const panelId = `pixelume-widget-panel-${nextWidgetId++}`;\n const trigger = button(document, 'trigger', 'Abrir conta Pixelume');\n trigger.type = 'button';\n trigger.setAttribute('aria-expanded', 'false');\n trigger.setAttribute('aria-controls', panelId);\n const triggerMark = document.createElement('span');\n triggerMark.className = 'mark';\n triggerMark.setAttribute('aria-hidden', 'true');\n const triggerLabel = document.createElement('span');\n triggerLabel.className = 'trigger-label';\n triggerLabel.textContent = 'Pixelume';\n const presence = document.createElement('span');\n presence.className = 'presence';\n presence.setAttribute('aria-hidden', 'true');\n trigger.append(triggerMark, triggerLabel, presence);\n\n const panel = document.createElement('section');\n panel.className = 'panel';\n panel.id = panelId;\n panel.hidden = true;\n panel.setAttribute('aria-label', 'Conta Pixelume');\n panel.setAttribute('aria-live', 'polite');\n\n const header = document.createElement('div');\n header.className = 'panel-header';\n const brand = document.createElement('span');\n brand.className = 'brand';\n const brandMark = document.createElement('span');\n brandMark.className = 'mark';\n brandMark.setAttribute('aria-hidden', 'true');\n const brandText = document.createElement('span');\n brandText.textContent = 'Pixelume';\n brand.append(brandMark, brandText);\n const close = button(document, 'close', 'Fechar painel da conta');\n close.type = 'button';\n close.textContent = '×';\n close.setAttribute('aria-hidden', 'false');\n header.append(brand, close);\n\n const content = document.createElement('div');\n content.className = 'content';\n panel.append(header, content);\n root.append(trigger, panel);\n shadow.append(style, root);\n\n let destroyed = false;\n let panelOpen = false;\n let pendingAction: 'login' | 'logout' | null = null;\n let localError: string | null = null;\n let removeDocumentKeydown: (() => void) | null = null;\n\n const onDocumentKeydown = (event: KeyboardEvent): void => {\n if (event.key !== 'Escape' || !panelOpen) return;\n event.preventDefault();\n event.stopPropagation();\n closePanel(true);\n };\n\n const openPanel = (): void => {\n if (destroyed || panelOpen) return;\n panelOpen = true;\n panel.hidden = false;\n trigger.setAttribute('aria-expanded', 'true');\n document.addEventListener('keydown', onDocumentKeydown, true);\n removeDocumentKeydown = () => {\n document.removeEventListener('keydown', onDocumentKeydown, true);\n };\n close.focus();\n };\n\n function closePanel(restoreFocus: boolean): void {\n if (!panelOpen) return;\n panelOpen = false;\n panel.hidden = true;\n trigger.setAttribute('aria-expanded', 'false');\n removeDocumentKeydown?.();\n removeDocumentKeydown = null;\n if (restoreFocus && !host.hidden) trigger.focus();\n }\n\n const togglePanel = (): void => {\n if (panelOpen) closePanel(true);\n else openPanel();\n };\n\n const runLogin = async (): Promise<void> => {\n if (destroyed || pendingAction) return;\n pendingAction = 'login';\n localError = null;\n render(pixelume.auth.state);\n try {\n await pixelume.auth.login();\n } catch {\n localError =\n 'Não foi possível abrir a entrada agora. Você pode continuar jogando e tentar novamente.';\n } finally {\n pendingAction = null;\n render(pixelume.auth.state);\n }\n };\n\n const runLogout = async (): Promise<void> => {\n if (destroyed || pendingAction) return;\n pendingAction = 'logout';\n localError = null;\n render(pixelume.auth.state);\n try {\n await pixelume.auth.logout();\n } catch {\n localError =\n 'Você saiu deste jogo, mas não foi possível confirmar a revogação no servidor. Tente novamente mais tarde.';\n } finally {\n pendingAction = null;\n render(pixelume.auth.state);\n }\n };\n\n const render = (state: PixelumeAuthState): void => {\n if (destroyed) return;\n content.replaceChildren();\n content.removeAttribute('aria-busy');\n const authenticated = state.status === 'authenticated';\n trigger.toggleAttribute('data-authenticated', authenticated);\n trigger.setAttribute(\n 'aria-label',\n authenticated\n ? `Abrir conta Pixelume. Conectado como ${state.player.displayName}`\n : 'Abrir conta Pixelume',\n );\n\n if (\n state.status === 'uninitialized' ||\n state.status === 'redirecting' ||\n state.status === 'exchanging'\n ) {\n renderLoading(document, content, state.status);\n return;\n }\n\n if (state.status === 'authenticated') {\n const identity = document.createElement('div');\n identity.className = 'identity';\n const avatar = document.createElement('span');\n avatar.className = 'avatar';\n avatar.setAttribute('aria-hidden', 'true');\n avatar.textContent = playerInitial(state.player.displayName);\n const copy = document.createElement('div');\n copy.className = 'state-copy';\n copy.append(\n textElement(document, 'p', 'title', `Olá, ${state.player.displayName}!`),\n textElement(document, 'p', 'message', 'Sua conta está conectada a este jogo.'),\n );\n identity.append(avatar, copy);\n content.append(identity);\n if (localError) content.append(notice(document, localError));\n const logout = button(document, 'action secondary', 'Sair da conta Pixelume neste jogo');\n logout.type = 'button';\n logout.textContent = pendingAction === 'logout' ? 'Saindo…' : 'Sair deste jogo';\n logout.disabled = pendingAction !== null;\n logout.addEventListener('click', () => void runLogout());\n content.append(logout);\n return;\n }\n\n const copy = document.createElement('div');\n copy.className = 'state-copy';\n copy.append(\n textElement(document, 'p', 'title', anonymousTitle(state)),\n textElement(document, 'p', 'message', anonymousMessage(state)),\n );\n content.append(copy);\n if (state.error || localError) {\n content.append(\n notice(\n document,\n localError ??\n 'Não foi possível verificar sua conta. Você pode continuar jogando e tentar novamente.',\n ),\n );\n }\n const login = button(document, 'action', 'Entrar com Pixelume');\n login.type = 'button';\n login.textContent = pendingAction === 'login' ? 'Abrindo Pixelume…' : 'Entrar com Pixelume';\n login.disabled = pendingAction !== null;\n login.addEventListener('click', () => void runLogin());\n content.append(login);\n };\n\n const unsubscribe = pixelume.auth.subscribe((state) => {\n localError = null;\n render(state);\n });\n\n const stopInputPropagation = (event: Event): void => {\n event.stopPropagation();\n };\n const closeFromButton = (): void => {\n closePanel(true);\n };\n const isolatedEvents = [\n 'click',\n 'pointerdown',\n 'pointerup',\n 'touchstart',\n 'touchend',\n 'keydown',\n 'keyup',\n ] as const;\n for (const eventName of isolatedEvents) host.addEventListener(eventName, stopInputPropagation);\n trigger.addEventListener('click', togglePanel);\n close.addEventListener('click', closeFromButton);\n\n const controller: PixelumeWidgetController = Object.freeze({\n element: host,\n hide(): void {\n if (destroyed) return;\n closePanel(false);\n host.hidden = true;\n },\n show(): void {\n if (destroyed) return;\n host.hidden = false;\n },\n destroy(): void {\n if (destroyed) return;\n destroyed = true;\n closePanel(false);\n removeDocumentKeydown?.();\n removeDocumentKeydown = null;\n unsubscribe();\n trigger.removeEventListener('click', togglePanel);\n close.removeEventListener('click', closeFromButton);\n for (const eventName of isolatedEvents)\n host.removeEventListener(eventName, stopInputPropagation);\n host.remove();\n widgetsByClient.delete(pixelume);\n widgetsByContainer.delete(container);\n },\n });\n\n render(pixelume.auth.state);\n container.append(host);\n widgetsByClient.set(pixelume, { container, controller });\n widgetsByContainer.set(container, pixelume);\n return controller;\n}\n\nfunction assertClient(value: unknown): asserts value is PixelumeClient {\n if (typeof value !== 'object' || value === null)\n throw new TypeError('Pixelume widget requires a client');\n const auth = Reflect.get(value, 'auth') as unknown;\n if (typeof auth !== 'object' || auth === null)\n throw new TypeError('Pixelume widget requires client.auth');\n for (const method of ['login', 'logout', 'subscribe'] as const) {\n if (typeof Reflect.get(auth, method) !== 'function') {\n throw new TypeError(`Pixelume widget requires client.auth.${method}()`);\n }\n }\n}\n\nfunction resolveDocument(container: HTMLElement | undefined): Document {\n if (container) {\n if (typeof container.attachShadow !== 'function') {\n throw new TypeError('Pixelume widget container must be an HTMLElement');\n }\n return container.ownerDocument;\n }\n const candidate = Reflect.get(globalThis, 'document') as unknown;\n if (typeof candidate !== 'object' || candidate === null || !('createElement' in candidate)) {\n throw new Error('Pixelume widget can only be mounted in a browser document');\n }\n return candidate as Document;\n}\n\nfunction resolvePosition(value: PixelumeWidgetPosition | undefined): PixelumeWidgetPosition {\n if (value === undefined) return DEFAULT_POSITION;\n if (!POSITIONS.includes(value)) {\n throw new Error(`Pixelume widget position must be one of: ${POSITIONS.join(', ')}`);\n }\n return value;\n}\n\nfunction resolveNumber(\n name: string,\n value: number | undefined,\n fallback: number,\n minimum: number,\n maximum: number,\n): number {\n if (value === undefined) return fallback;\n if (!Number.isInteger(value) || value < minimum || value > maximum) {\n throw new Error(`Pixelume widget ${name} must be an integer from ${minimum} to ${maximum}`);\n }\n return value;\n}\n\nfunction button(document: Document, className: string, label: string): HTMLButtonElement {\n const element = document.createElement('button');\n element.className = className;\n element.setAttribute('aria-label', label);\n return element;\n}\n\nfunction textElement(\n document: Document,\n tag: 'p',\n className: string,\n text: string,\n): HTMLParagraphElement {\n const element = document.createElement(tag);\n element.className = className;\n element.textContent = text;\n return element;\n}\n\nfunction notice(document: Document, text: string): HTMLParagraphElement {\n const element = textElement(document, 'p', 'notice', text);\n element.setAttribute('role', 'alert');\n return element;\n}\n\nfunction renderLoading(\n document: Document,\n content: HTMLElement,\n status: 'uninitialized' | 'redirecting' | 'exchanging',\n): void {\n const labels = {\n uninitialized: 'Preparando sua conta…',\n redirecting: 'Abrindo o Pixelume Auth…',\n exchanging: 'Concluindo a entrada…',\n } as const;\n content.setAttribute('aria-busy', 'true');\n const visual = document.createElement('div');\n visual.className = 'loading';\n visual.setAttribute('aria-hidden', 'true');\n for (let index = 0; index < 2; index += 1) {\n const line = document.createElement('span');\n line.className = 'loading-line';\n visual.append(line);\n }\n const label = textElement(document, 'p', 'sr-only', labels[status]);\n content.append(visual, label);\n}\n\nfunction playerInitial(displayName: string): string {\n return Array.from(displayName.trim())[0]?.toLocaleUpperCase('pt-BR') ?? 'P';\n}\n\nfunction anonymousTitle(state: Extract<PixelumeAuthState, { status: 'anonymous' }>): string {\n if (state.reason === 'logged_out') return 'Você saiu deste jogo';\n if (state.reason === 'expired') return 'Sua sessão expirou';\n if (state.reason === 'cancelled') return 'Entrada cancelada';\n return 'Sua conta Pixelume';\n}\n\nfunction anonymousMessage(state: Extract<PixelumeAuthState, { status: 'anonymous' }>): string {\n if (state.reason === 'logged_out') return 'Entre novamente quando quiser.';\n if (state.reason === 'expired') return 'Entre novamente para continuar com sua conta.';\n if (state.reason === 'cancelled')\n return 'Você pode continuar como visitante ou tentar novamente.';\n return 'Entre pelo portal seguro da Pixelume. Sua senha nunca passa pelo jogo.';\n}\n"]}
1
+ {"version":3,"file":"widget.js","sourceRoot":"","sources":["../../src/ui/widget.ts"],"names":[],"mappings":"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","sourcesContent":["import type { PixelumeClient } from '../core/PixelumeClient.js';\nimport type { PixelumeAuthState } from '../modules/auth/AuthModule.js';\nimport type { PixelumeSessionState } from '../modules/auth/SessionManager.js';\nimport type { PixelumeSavesActivity } from '../modules/saves/SavesModule.js';\nimport { WIDGET_STYLES } from './styles.js';\n\nexport type PixelumeWidgetPosition = 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';\n\nexport interface PixelumeWidgetOptions {\n /** Corner used by the widget. Defaults to `bottom-right`. */\n readonly position?: PixelumeWidgetPosition;\n /** Distance from the selected edges in CSS pixels. Defaults to `16`. */\n readonly offset?: number;\n /** Mount target. Defaults to `document.body`; custom targets should establish a containing block. */\n readonly container?: HTMLElement;\n /** Visual treatment. `pixel` is currently the only supported theme. */\n readonly theme?: 'pixel';\n /** Stacking order for the host. Defaults to `2147483000`. */\n readonly zIndex?: number;\n}\n\nexport interface PixelumeWidgetController {\n /** The Shadow DOM host created by the widget. */\n readonly element: HTMLElement;\n /** Hides the widget and closes its panel without changing authentication state. */\n hide(): void;\n /** Shows a previously hidden widget. */\n show(): void;\n /** Removes DOM/listeners/subscriptions. Safe to call more than once. */\n destroy(): void;\n}\n\nconst DEFAULT_POSITION: PixelumeWidgetPosition = 'bottom-right';\nconst DEFAULT_OFFSET = 16;\nconst DEFAULT_Z_INDEX = 2_147_483_000;\nconst POSITIONS: readonly PixelumeWidgetPosition[] = [\n 'top-left',\n 'top-right',\n 'bottom-left',\n 'bottom-right',\n];\n\ninterface MountedWidget {\n readonly container: HTMLElement;\n readonly controller: PixelumeWidgetController;\n}\n\nconst widgetsByClient = new WeakMap<object, MountedWidget>();\nconst widgetsByContainer = new WeakMap<HTMLElement, object>();\nlet nextWidgetId = 1;\n\n/**\n * Mounts the optional Pixelume account widget. Importing this module alone has no DOM side effects.\n * Repeating the call with the same client and container returns the existing controller.\n */\nexport function mountPixelumeWidget(\n pixelume: PixelumeClient,\n options: PixelumeWidgetOptions = {},\n): PixelumeWidgetController {\n assertClient(pixelume);\n const document = resolveDocument(options.container);\n const container = options.container ?? document.body;\n\n const mounted = widgetsByClient.get(pixelume);\n if (mounted) {\n if (mounted.container === container) return mounted.controller;\n throw new Error('This Pixelume client already has a widget mounted in another container');\n }\n const containerOwner = widgetsByContainer.get(container);\n if (containerOwner) {\n throw new Error('This container already has a Pixelume widget mounted');\n }\n\n const position = resolvePosition(options.position);\n const offset = resolveNumber('offset', options.offset, DEFAULT_OFFSET, 0, 256);\n const zIndex = resolveNumber('zIndex', options.zIndex, DEFAULT_Z_INDEX, 0, 2_147_483_647);\n const theme: unknown = options.theme;\n if (theme !== undefined && theme !== 'pixel') {\n throw new Error('Pixelume widget theme must be \"pixel\"');\n }\n\n const host = document.createElement('div');\n host.dataset.pixelumeWidget = '';\n host.dataset.position = position;\n host.style.setProperty('--pixelume-offset', `${offset}px`);\n host.style.setProperty('--pixelume-z-index', String(zIndex));\n if (options.container) host.dataset.contained = '';\n\n const shadow = host.attachShadow({ mode: 'open' });\n const style = document.createElement('style');\n style.textContent = WIDGET_STYLES;\n\n const root = document.createElement('div');\n root.className = 'root';\n\n const panelId = `pixelume-widget-panel-${nextWidgetId++}`;\n const trigger = button(document, 'trigger', 'Abrir conta Pixelume');\n trigger.type = 'button';\n trigger.setAttribute('aria-expanded', 'false');\n trigger.setAttribute('aria-controls', panelId);\n const triggerMark = document.createElement('span');\n triggerMark.className = 'mark';\n triggerMark.setAttribute('aria-hidden', 'true');\n const triggerLabel = document.createElement('span');\n triggerLabel.className = 'trigger-label';\n triggerLabel.textContent = 'Pixelume';\n const presence = document.createElement('span');\n presence.className = 'presence';\n presence.setAttribute('aria-hidden', 'true');\n trigger.append(triggerMark, triggerLabel, presence);\n\n const panel = document.createElement('section');\n panel.className = 'panel';\n panel.id = panelId;\n panel.hidden = true;\n panel.setAttribute('aria-label', 'Conta Pixelume');\n panel.setAttribute('aria-live', 'polite');\n\n const header = document.createElement('div');\n header.className = 'panel-header';\n const brand = document.createElement('span');\n brand.className = 'brand';\n const brandMark = document.createElement('span');\n brandMark.className = 'mark';\n brandMark.setAttribute('aria-hidden', 'true');\n const brandText = document.createElement('span');\n brandText.textContent = 'Pixelume';\n brand.append(brandMark, brandText);\n const close = button(document, 'close', 'Fechar painel da conta');\n close.type = 'button';\n close.textContent = '×';\n close.setAttribute('aria-hidden', 'false');\n header.append(brand, close);\n\n const content = document.createElement('div');\n content.className = 'content';\n panel.append(header, content);\n root.append(trigger, panel);\n shadow.append(style, root);\n\n let destroyed = false;\n let panelOpen = false;\n let pendingAction: 'login' | 'logout' | null = null;\n let localError: string | null = null;\n let removeDocumentKeydown: (() => void) | null = null;\n\n const onDocumentKeydown = (event: KeyboardEvent): void => {\n if (event.key !== 'Escape' || !panelOpen) return;\n event.preventDefault();\n event.stopPropagation();\n closePanel(true);\n };\n\n const openPanel = (): void => {\n if (destroyed || panelOpen) return;\n panelOpen = true;\n panel.hidden = false;\n trigger.setAttribute('aria-expanded', 'true');\n document.addEventListener('keydown', onDocumentKeydown, true);\n removeDocumentKeydown = () => {\n document.removeEventListener('keydown', onDocumentKeydown, true);\n };\n close.focus();\n };\n\n function closePanel(restoreFocus: boolean): void {\n if (!panelOpen) return;\n panelOpen = false;\n panel.hidden = true;\n trigger.setAttribute('aria-expanded', 'false');\n removeDocumentKeydown?.();\n removeDocumentKeydown = null;\n if (restoreFocus && !host.hidden) trigger.focus();\n }\n\n const togglePanel = (): void => {\n if (panelOpen) closePanel(true);\n else openPanel();\n };\n\n const runLogin = async (): Promise<void> => {\n if (destroyed || pendingAction) return;\n pendingAction = 'login';\n localError = null;\n render(pixelume.auth.state);\n try {\n await pixelume.auth.login();\n } catch {\n localError =\n 'Não foi possível abrir a entrada agora. Você pode continuar jogando e tentar novamente.';\n } finally {\n pendingAction = null;\n render(pixelume.auth.state);\n }\n };\n\n const runLogout = async (): Promise<void> => {\n if (destroyed || pendingAction) return;\n pendingAction = 'logout';\n localError = null;\n render(pixelume.auth.state);\n try {\n await pixelume.auth.logout();\n } catch {\n localError =\n 'Você saiu deste jogo, mas não foi possível confirmar a revogação no servidor. Tente novamente mais tarde.';\n } finally {\n pendingAction = null;\n render(pixelume.auth.state);\n }\n };\n\n const render = (state: PixelumeAuthState): void => {\n if (destroyed) return;\n content.replaceChildren();\n content.removeAttribute('aria-busy');\n const authenticated = state.status === 'authenticated';\n trigger.toggleAttribute('data-authenticated', authenticated);\n trigger.setAttribute(\n 'aria-label',\n authenticated\n ? `Abrir conta Pixelume. Conectado como ${state.player.displayName}`\n : 'Abrir conta Pixelume',\n );\n\n if (\n state.status === 'uninitialized' ||\n state.status === 'redirecting' ||\n state.status === 'exchanging'\n ) {\n renderLoading(document, content, state.status);\n return;\n }\n\n if (state.status === 'authenticated') {\n const session = sessionState();\n const identity = document.createElement('div');\n identity.className = 'identity';\n const avatar = document.createElement('span');\n avatar.className = 'avatar';\n avatar.setAttribute('aria-hidden', 'true');\n avatar.textContent = playerInitial(state.player.displayName);\n const copy = document.createElement('div');\n copy.className = 'state-copy';\n copy.append(\n textElement(document, 'p', 'title', `Olá, ${state.player.displayName}!`),\n textElement(document, 'p', 'message', 'Sua conta está conectada a este jogo.'),\n );\n identity.append(avatar, copy);\n content.append(identity);\n if (session?.error) {\n content.append(\n notice(\n document,\n 'Não conseguimos falar com a Pixelume agora. Sua conta continua conectada e tentaremos de novo automaticamente.',\n ),\n );\n }\n const sync = syncMessage(savesActivity());\n if (sync) content.append(status(document, sync));\n if (localError) content.append(notice(document, localError));\n const logout = button(document, 'action secondary', 'Sair da conta Pixelume neste jogo');\n logout.type = 'button';\n logout.textContent = pendingAction === 'logout' ? 'Saindo…' : 'Sair deste jogo';\n logout.disabled = pendingAction !== null;\n logout.addEventListener('click', () => void runLogout());\n content.append(logout);\n return;\n }\n\n const copy = document.createElement('div');\n copy.className = 'state-copy';\n copy.append(\n textElement(document, 'p', 'title', anonymousTitle(state)),\n textElement(document, 'p', 'message', anonymousMessage(state)),\n );\n content.append(copy);\n const failure = localError ?? anonymousNotice(state);\n if (failure) content.append(notice(document, failure));\n const login = button(document, 'action', 'Entrar com Pixelume');\n login.type = 'button';\n login.textContent = pendingAction === 'login' ? 'Abrindo Pixelume…' : 'Entrar com Pixelume';\n login.disabled = pendingAction !== null;\n login.addEventListener('click', () => void runLogin());\n content.append(login);\n };\n\n const unsubscribe = pixelume.auth.subscribe((state) => {\n localError = null;\n render(state);\n });\n // Optional sources (absent in custom/mocked clients): token renewal and explicit Saves calls.\n const auth = pixelume.auth as Partial<typeof pixelume.auth>;\n const saves = (pixelume as Partial<Pick<PixelumeClient, 'saves'>>).saves;\n const sessionState = (): PixelumeSessionState | null => auth.session ?? null;\n const savesActivity = (): PixelumeSavesActivity | null => saves?.activity ?? null;\n const rerender = (): void => {\n render(pixelume.auth.state);\n };\n const unsubscribeSession =\n typeof auth.subscribeSession === 'function' ? auth.subscribeSession(rerender) : null;\n const unsubscribeSaves =\n typeof saves?.subscribe === 'function' ? saves.subscribe(rerender) : null;\n\n const stopInputPropagation = (event: Event): void => {\n event.stopPropagation();\n };\n const closeFromButton = (): void => {\n closePanel(true);\n };\n const isolatedEvents = [\n 'click',\n 'pointerdown',\n 'pointerup',\n 'touchstart',\n 'touchend',\n 'keydown',\n 'keyup',\n ] as const;\n for (const eventName of isolatedEvents) host.addEventListener(eventName, stopInputPropagation);\n trigger.addEventListener('click', togglePanel);\n close.addEventListener('click', closeFromButton);\n\n const controller: PixelumeWidgetController = Object.freeze({\n element: host,\n hide(): void {\n if (destroyed) return;\n closePanel(false);\n host.hidden = true;\n },\n show(): void {\n if (destroyed) return;\n host.hidden = false;\n },\n destroy(): void {\n if (destroyed) return;\n destroyed = true;\n closePanel(false);\n removeDocumentKeydown?.();\n removeDocumentKeydown = null;\n unsubscribe();\n unsubscribeSession?.();\n unsubscribeSaves?.();\n trigger.removeEventListener('click', togglePanel);\n close.removeEventListener('click', closeFromButton);\n for (const eventName of isolatedEvents)\n host.removeEventListener(eventName, stopInputPropagation);\n host.remove();\n widgetsByClient.delete(pixelume);\n widgetsByContainer.delete(container);\n },\n });\n\n render(pixelume.auth.state);\n container.append(host);\n widgetsByClient.set(pixelume, { container, controller });\n widgetsByContainer.set(container, pixelume);\n return controller;\n}\n\nfunction assertClient(value: unknown): asserts value is PixelumeClient {\n if (typeof value !== 'object' || value === null)\n throw new TypeError('Pixelume widget requires a client');\n const auth = Reflect.get(value, 'auth') as unknown;\n if (typeof auth !== 'object' || auth === null)\n throw new TypeError('Pixelume widget requires client.auth');\n for (const method of ['login', 'logout', 'subscribe'] as const) {\n if (typeof Reflect.get(auth, method) !== 'function') {\n throw new TypeError(`Pixelume widget requires client.auth.${method}()`);\n }\n }\n}\n\nfunction resolveDocument(container: HTMLElement | undefined): Document {\n if (container) {\n if (typeof container.attachShadow !== 'function') {\n throw new TypeError('Pixelume widget container must be an HTMLElement');\n }\n return container.ownerDocument;\n }\n const candidate = Reflect.get(globalThis, 'document') as unknown;\n if (typeof candidate !== 'object' || candidate === null || !('createElement' in candidate)) {\n throw new Error('Pixelume widget can only be mounted in a browser document');\n }\n return candidate as Document;\n}\n\nfunction resolvePosition(value: PixelumeWidgetPosition | undefined): PixelumeWidgetPosition {\n if (value === undefined) return DEFAULT_POSITION;\n if (!POSITIONS.includes(value)) {\n throw new Error(`Pixelume widget position must be one of: ${POSITIONS.join(', ')}`);\n }\n return value;\n}\n\nfunction resolveNumber(\n name: string,\n value: number | undefined,\n fallback: number,\n minimum: number,\n maximum: number,\n): number {\n if (value === undefined) return fallback;\n if (!Number.isInteger(value) || value < minimum || value > maximum) {\n throw new Error(`Pixelume widget ${name} must be an integer from ${minimum} to ${maximum}`);\n }\n return value;\n}\n\nfunction button(document: Document, className: string, label: string): HTMLButtonElement {\n const element = document.createElement('button');\n element.className = className;\n element.setAttribute('aria-label', label);\n return element;\n}\n\nfunction textElement(\n document: Document,\n tag: 'p',\n className: string,\n text: string,\n): HTMLParagraphElement {\n const element = document.createElement(tag);\n element.className = className;\n element.textContent = text;\n return element;\n}\n\nfunction notice(document: Document, text: string): HTMLParagraphElement {\n const element = textElement(document, 'p', 'notice', text);\n element.setAttribute('role', 'alert');\n return element;\n}\n\nfunction status(document: Document, text: string): HTMLParagraphElement {\n const element = textElement(document, 'p', 'message sync', text);\n element.setAttribute('role', 'status');\n return element;\n}\n\n/** Cloud status only after the game explicitly saved or deleted; nothing is simulated. */\nfunction syncMessage(activity: PixelumeSavesActivity | null): string | null {\n if (activity === null) return null;\n if (activity.pendingWrites > 0) return 'Salvando na nuvem…';\n const last = activity.last;\n if (last === null || (last.operation !== 'save' && last.operation !== 'delete')) return null;\n if (last.outcome === 'conflict') {\n return 'Há um progresso mais recente na nuvem. O jogo vai decidir o que manter.';\n }\n if (last.outcome === 'error') return 'Não foi possível salvar na nuvem agora.';\n const time = new Date(last.at).toLocaleTimeString('pt-BR', {\n hour: '2-digit',\n minute: '2-digit',\n });\n return last.operation === 'save'\n ? `Progresso salvo na nuvem às ${time}.`\n : `Save removido da nuvem às ${time}.`;\n}\n\nfunction anonymousNotice(\n state: Extract<PixelumeAuthState, { status: 'anonymous' }>,\n): string | null {\n const error = state.error;\n if (error === null) return null;\n if (error.kind === 'auth' && Reflect.get(error, 'reason') === 'game_unavailable') {\n return 'O acesso deste jogo à Pixelume está indisponível no momento. Você pode continuar jogando.';\n }\n // An ended session is already explained by the \"expired\" title and message.\n if (state.reason === 'expired') return null;\n return 'Não foi possível verificar sua conta. Você pode continuar jogando e tentar novamente.';\n}\n\nfunction renderLoading(\n document: Document,\n content: HTMLElement,\n status: 'uninitialized' | 'redirecting' | 'exchanging',\n): void {\n const labels = {\n uninitialized: 'Preparando sua conta…',\n redirecting: 'Abrindo o Pixelume Auth…',\n exchanging: 'Concluindo a entrada…',\n } as const;\n content.setAttribute('aria-busy', 'true');\n const visual = document.createElement('div');\n visual.className = 'loading';\n visual.setAttribute('aria-hidden', 'true');\n for (let index = 0; index < 2; index += 1) {\n const line = document.createElement('span');\n line.className = 'loading-line';\n visual.append(line);\n }\n const label = textElement(document, 'p', 'sr-only', labels[status]);\n content.append(visual, label);\n}\n\nfunction playerInitial(displayName: string): string {\n return Array.from(displayName.trim())[0]?.toLocaleUpperCase('pt-BR') ?? 'P';\n}\n\nfunction anonymousTitle(state: Extract<PixelumeAuthState, { status: 'anonymous' }>): string {\n if (state.reason === 'logged_out') return 'Você saiu deste jogo';\n if (state.reason === 'expired') return 'Sua sessão expirou';\n if (state.reason === 'cancelled') return 'Entrada cancelada';\n return 'Sua conta Pixelume';\n}\n\nfunction anonymousMessage(state: Extract<PixelumeAuthState, { status: 'anonymous' }>): string {\n if (state.reason === 'logged_out') return 'Entre novamente quando quiser.';\n if (state.reason === 'expired') return 'Entre novamente para continuar com sua conta.';\n if (state.reason === 'cancelled')\n return 'Você pode continuar como visitante ou tentar novamente.';\n return 'Entre pelo portal seguro da Pixelume. Sua senha nunca passa pelo jogo.';\n}\n"]}
package/dist/version.d.ts CHANGED
@@ -1,2 +1,2 @@
1
1
  /** Version of the SDK package. Kept in sync with `package.json` (checked by tests and by the release workflow). */
2
- export declare const SDK_VERSION = "0.3.0";
2
+ export declare const SDK_VERSION = "0.4.0";
package/dist/version.js CHANGED
@@ -1,3 +1,3 @@
1
1
  /** Version of the SDK package. Kept in sync with `package.json` (checked by tests and by the release workflow). */
2
- export const SDK_VERSION = '0.3.0';
2
+ export const SDK_VERSION = '0.4.0';
3
3
  //# sourceMappingURL=version.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"version.js","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AAAA,mHAAmH;AACnH,MAAM,CAAC,MAAM,WAAW,GAAG,OAAO,CAAC","sourcesContent":["/** Version of the SDK package. Kept in sync with `package.json` (checked by tests and by the release workflow). */\nexport const SDK_VERSION = '0.3.0';\n"]}
1
+ {"version":3,"file":"version.js","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AAAA,mHAAmH;AACnH,MAAM,CAAC,MAAM,WAAW,GAAG,OAAO,CAAC","sourcesContent":["/** Version of the SDK package. Kept in sync with `package.json` (checked by tests and by the release workflow). */\nexport const SDK_VERSION = '0.4.0';\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pixelume/sdk",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "SDK TypeScript da Pixelume para jogos web: core headless, SSO com PKCE e widget oficial opcional.",
5
5
  "license": "UNLICENSED",
6
6
  "author": "Erickson Nascimento",