@allanfsouza/aether-sdk 2.4.3 → 2.4.5

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.
package/dist/auth.d.ts CHANGED
@@ -25,7 +25,6 @@ export interface Session {
25
25
  export declare class AuthModule {
26
26
  private client;
27
27
  private http;
28
- private refreshToken;
29
28
  private currentUser;
30
29
  constructor(client: PlataformaClient, http: AxiosInstance);
31
30
  login(email: string, password: string): Promise<LoginResponse>;
@@ -43,7 +42,13 @@ export declare class AuthModule {
43
42
  getSessions(): Promise<Session[]>;
44
43
  forgotPassword(email: string): Promise<any>;
45
44
  resetPassword(token: string, newPassword: string): Promise<any>;
45
+ /**
46
+ * @deprecated Use client.getRefreshToken() - mantido para compatibilidade
47
+ */
46
48
  getRefreshToken(): string | null;
49
+ /**
50
+ * @deprecated Use client.setRefreshToken() - mantido para compatibilidade
51
+ */
47
52
  setRefreshToken(token: string | null): void;
48
53
  /**
49
54
  * Alias para login, retornando { user, error } padrão do Showcase
@@ -82,10 +87,16 @@ export declare class AuthModule {
82
87
  }>;
83
88
  /**
84
89
  * Recupera a sessão atual (User + Token).
85
- * Se não tiver logado, retorna null ou tenta validar o token.
90
+ * Primeiro tenta memória, depois localStorage.
91
+ * Se não encontrar, retorna null.
86
92
  */
87
93
  getSession(): Promise<{
88
94
  user: User;
89
95
  access_token: string;
90
96
  } | null>;
97
+ /**
98
+ * Retorna o usuário atual (sem validar token).
99
+ * Útil para acesso síncrono aos dados do usuário.
100
+ */
101
+ getCurrentUser(): User | null;
91
102
  }
package/dist/auth.js CHANGED
@@ -1,14 +1,16 @@
1
1
  export class AuthModule {
2
2
  constructor(client, http) {
3
- this.refreshToken = null;
4
- // Armazena o user localmente para acesso rápido via getSession
3
+ // Armazena o user em memória para acesso rápido
4
+ // Persistência fica no PlataformaClient (localStorage)
5
5
  this.currentUser = null;
6
6
  this.client = client;
7
7
  this.http = http;
8
+ // Restaura user do localStorage se existir
9
+ this.currentUser = this.client.getUser();
8
10
  }
9
- // =================================================================
10
- // MÉTODOS ORIGINAIS (MANTIDOS)
11
- // =================================================================
11
+ // ==========================================================================
12
+ // MÉTODOS PRINCIPAIS
13
+ // ==========================================================================
12
14
  async login(email, password) {
13
15
  try {
14
16
  const { data } = await this.http.post("/auth/login", {
@@ -16,15 +18,17 @@ export class AuthModule {
16
18
  password,
17
19
  });
18
20
  if (data.accessToken) {
21
+ // Persiste automaticamente via PlataformaClient
19
22
  this.client.setToken(data.accessToken);
20
- this.refreshToken = data.refreshToken;
23
+ this.client.setRefreshToken(data.refreshToken);
24
+ this.client.setUser(data.user);
21
25
  this.currentUser = data.user;
22
26
  }
23
27
  return data;
24
28
  }
25
29
  catch (e) {
26
- this.client.setToken(null);
27
- this.refreshToken = null;
30
+ // Limpa tudo em caso de erro
31
+ this.client.clearSession();
28
32
  this.currentUser = null;
29
33
  throw e;
30
34
  }
@@ -32,9 +36,11 @@ export class AuthModule {
32
36
  async register(credentials) {
33
37
  try {
34
38
  const { data } = await this.http.post("/auth/register", credentials);
35
- // Se o register retornar token, configura:
39
+ // Se o register retornar token (auto-login), configura sessão
36
40
  if (data.accessToken) {
37
41
  this.client.setToken(data.accessToken);
42
+ this.client.setRefreshToken(data.refreshToken);
43
+ this.client.setUser(data.user);
38
44
  this.currentUser = data.user;
39
45
  }
40
46
  return data;
@@ -44,21 +50,28 @@ export class AuthModule {
44
50
  }
45
51
  }
46
52
  async refresh() {
47
- if (!this.refreshToken) {
53
+ // Tenta pegar do localStorage primeiro, depois da memória
54
+ const refreshToken = this.client.getRefreshToken() || this.getRefreshToken();
55
+ if (!refreshToken) {
48
56
  throw new Error("Nenhum refresh token disponível");
49
57
  }
50
58
  try {
51
59
  const { data } = await this.http.post("/auth/refresh", {
52
- refreshToken: this.refreshToken,
60
+ refreshToken,
53
61
  });
54
62
  if (data.accessToken) {
55
63
  this.client.setToken(data.accessToken);
64
+ // Se vier novo refresh token, atualiza
65
+ if (data.refreshToken) {
66
+ this.client.setRefreshToken(data.refreshToken);
67
+ }
56
68
  }
57
69
  return data;
58
70
  }
59
71
  catch (e) {
60
- this.client.setToken(null);
61
- this.refreshToken = null;
72
+ // Token expirado - limpa sessão
73
+ this.client.clearSession();
74
+ this.currentUser = null;
62
75
  throw e;
63
76
  }
64
77
  }
@@ -66,18 +79,17 @@ export class AuthModule {
66
79
  return `${this.client.apiUrl}/v1/auth/google`;
67
80
  }
68
81
  async logout() {
69
- if (this.refreshToken) {
82
+ const refreshToken = this.client.getRefreshToken();
83
+ if (refreshToken) {
70
84
  try {
71
- await this.http.post("/auth/logout", {
72
- refreshToken: this.refreshToken,
73
- });
85
+ await this.http.post("/auth/logout", { refreshToken });
74
86
  }
75
- catch (e) {
76
- // Ignora erro de logout
87
+ catch {
88
+ // Ignora erro de logout no servidor
77
89
  }
78
90
  }
79
- this.client.setToken(null);
80
- this.refreshToken = null;
91
+ // Limpa tudo (memória + localStorage)
92
+ this.client.clearSession();
81
93
  this.currentUser = null;
82
94
  }
83
95
  async logoutAll() {
@@ -85,8 +97,7 @@ export class AuthModule {
85
97
  await this.http.post("/auth/logout-all");
86
98
  }
87
99
  finally {
88
- this.client.setToken(null);
89
- this.refreshToken = null;
100
+ this.client.clearSession();
90
101
  this.currentUser = null;
91
102
  }
92
103
  }
@@ -105,15 +116,21 @@ export class AuthModule {
105
116
  });
106
117
  return data;
107
118
  }
119
+ /**
120
+ * @deprecated Use client.getRefreshToken() - mantido para compatibilidade
121
+ */
108
122
  getRefreshToken() {
109
- return this.refreshToken;
123
+ return this.client.getRefreshToken();
110
124
  }
125
+ /**
126
+ * @deprecated Use client.setRefreshToken() - mantido para compatibilidade
127
+ */
111
128
  setRefreshToken(token) {
112
- this.refreshToken = token;
129
+ this.client.setRefreshToken(token);
113
130
  }
114
- // =================================================================
115
- // NOVOS MÉTODOS (COMPATIBILIDADE COM SHOWCASE / SUPABASE STYLE)
116
- // =================================================================
131
+ // ==========================================================================
132
+ // MÉTODOS COMPATÍVEIS COM SUPABASE / SHOWCASE STYLE
133
+ // ==========================================================================
117
134
  /**
118
135
  * Alias para login, retornando { user, error } padrão do Showcase
119
136
  */
@@ -134,7 +151,7 @@ export class AuthModule {
134
151
  const data = await this.register({
135
152
  email: credentials.email,
136
153
  password: credentials.password,
137
- name: credentials.data?.name || credentials.email.split('@')[0],
154
+ name: credentials.data?.name || credentials.email.split("@")[0],
138
155
  });
139
156
  return { user: data.user, error: null };
140
157
  }
@@ -151,20 +168,51 @@ export class AuthModule {
151
168
  }
152
169
  /**
153
170
  * Recupera a sessão atual (User + Token).
154
- * Se não tiver logado, retorna null ou tenta validar o token.
171
+ * Primeiro tenta memória, depois localStorage.
172
+ * Se não encontrar, retorna null.
155
173
  */
156
174
  async getSession() {
157
175
  const token = this.client.getToken();
158
176
  if (!token)
159
177
  return null;
160
- // Se já temos o user em memória, retorna.
161
- // O ideal seria validar o token no backend (/auth/me), mas vamos simplificar.
178
+ // Tenta memória primeiro
162
179
  if (this.currentUser) {
163
180
  return {
164
181
  user: this.currentUser,
165
- access_token: token
182
+ access_token: token,
183
+ };
184
+ }
185
+ // Tenta localStorage
186
+ const savedUser = this.client.getUser();
187
+ if (savedUser) {
188
+ this.currentUser = savedUser;
189
+ return {
190
+ user: savedUser,
191
+ access_token: token,
192
+ };
193
+ }
194
+ // Última opção: valida token no backend
195
+ try {
196
+ const { data } = await this.http.get("/auth/me");
197
+ this.currentUser = data.user;
198
+ this.client.setUser(data.user);
199
+ return {
200
+ user: data.user,
201
+ access_token: token,
166
202
  };
167
203
  }
168
- return null;
204
+ catch {
205
+ // Token inválido/expirado - limpa sessão
206
+ this.client.clearSession();
207
+ this.currentUser = null;
208
+ return null;
209
+ }
210
+ }
211
+ /**
212
+ * Retorna o usuário atual (sem validar token).
213
+ * Útil para acesso síncrono aos dados do usuário.
214
+ */
215
+ getCurrentUser() {
216
+ return this.currentUser || this.client.getUser();
169
217
  }
170
218
  }
@@ -19,49 +19,16 @@ export declare class QueryBuilder<T> {
19
19
  private limitVal?;
20
20
  private offsetVal?;
21
21
  constructor(collectionRef: CollectionReference<T>);
22
- /**
23
- * Adiciona um filtro de igualdade.
24
- */
25
22
  eq(column: keyof T & string, value: any): this;
26
- /**
27
- * Adiciona um filtro de desigualdade ($ne).
28
- */
29
23
  neq(column: keyof T & string, value: any): this;
30
- /**
31
- * Adiciona um filtro maior que ($gt).
32
- */
33
24
  gt(column: keyof T & string, value: number | string): this;
34
- /**
35
- * Adiciona um filtro maior ou igual ($gte).
36
- */
37
25
  gte(column: keyof T & string, value: number | string): this;
38
- /**
39
- * Adiciona um filtro menor que ($lt).
40
- */
41
26
  lt(column: keyof T & string, value: number | string): this;
42
- /**
43
- * Adiciona um filtro menor ou igual ($lte).
44
- */
45
27
  lte(column: keyof T & string, value: number | string): this;
46
- /**
47
- * Adiciona um filtro LIKE ($like).
48
- */
49
28
  like(column: keyof T & string, value: string): this;
50
- /**
51
- * Define a ordenação.
52
- */
53
29
  order(column: keyof T & string, direction?: "ASC" | "DESC"): this;
54
- /**
55
- * Define o limite de registros.
56
- */
57
30
  limit(count: number): this;
58
- /**
59
- * Define o deslocamento (paginação).
60
- */
61
31
  offset(count: number): this;
62
- /**
63
- * Executa a query e retorna os resultados.
64
- */
65
32
  get(): Promise<T[]>;
66
33
  }
67
34
  /**
@@ -91,18 +58,50 @@ export declare class DatabaseModule {
91
58
  /**
92
59
  * Seleciona uma coleção de dados.
93
60
  * [PREMIUM] Suporta Generics <T> para tipagem forte.
94
- * * @example client.db.collection<Product>('products')
61
+ * @example client.db.collection<Product>('products')
95
62
  */
96
63
  collection<T = any>(collectionName: string): CollectionReference<T>;
64
+ /**
65
+ * [NOVO] Método compatível com estilo 'Supabase/Drizzle'.
66
+ * É um alias para .collection() mas retorna interface compatível com o Showcase.
67
+ * @example client.db.from('posts').select('*')
68
+ */
69
+ from<T = any>(tableName: string): {
70
+ select: (columns?: string) => Promise<{
71
+ data: T[];
72
+ error: null;
73
+ } | {
74
+ data: null;
75
+ error: any;
76
+ }>;
77
+ insert: (data: T) => Promise<{
78
+ data: Awaited<T>;
79
+ error: null;
80
+ } | {
81
+ data: null;
82
+ error: any;
83
+ }>;
84
+ update: (data: Partial<T> & {
85
+ id?: string;
86
+ }) => Promise<{
87
+ data: Awaited<T>;
88
+ error: null;
89
+ } | {
90
+ data: null;
91
+ error: any;
92
+ }>;
93
+ delete: () => Promise<{
94
+ data: null;
95
+ error: string;
96
+ }>;
97
+ };
97
98
  /**
98
99
  * Executa múltiplas operações em uma única transação.
99
- * Se uma falhar, todas são revertidas.
100
100
  */
101
101
  batch(operations: BatchOperation[]): Promise<any[]>;
102
102
  }
103
103
  /**
104
104
  * Referência a uma coleção específica.
105
- * O <T> define o formato dos dados (ex: interface User).
106
105
  */
107
106
  export declare class CollectionReference<T> {
108
107
  private client;
@@ -110,45 +109,13 @@ export declare class CollectionReference<T> {
110
109
  private collectionName;
111
110
  private wsUrl;
112
111
  constructor(client: PlataformaClient, http: AxiosInstance, name: string);
113
- /**
114
- * Inicia o QueryBuilder.
115
- * Atalho para .eq()
116
- */
117
112
  eq(column: keyof T & string, value: any): QueryBuilder<T>;
118
- /**
119
- * Inicia o QueryBuilder.
120
- * Atalho para .gt()
121
- */
122
113
  gt(column: keyof T & string, value: number | string): QueryBuilder<T>;
123
- /**
124
- * Retorna uma nova instância do QueryBuilder.
125
- */
126
114
  query(): QueryBuilder<T>;
127
- /**
128
- * Lista documentos da coleção com filtros opcionais.
129
- * @param options Filtros e Ordenação
130
- */
131
115
  list(options?: ListOptions<T>): Promise<T[]>;
132
- /**
133
- * Busca um documento pelo ID.
134
- */
135
116
  get(id: string): Promise<T>;
136
- /**
137
- * Cria um novo documento.
138
- * O Partial<T> permite criar sem passar campos gerados (como id, createdAt).
139
- */
140
117
  create(newData: Partial<T>): Promise<T>;
141
- /**
142
- * Atualiza um documento existente.
143
- */
144
118
  update(id: string, updates: Partial<T>): Promise<T>;
145
- /**
146
- * Deleta um documento.
147
- */
148
119
  delete(id: string): Promise<boolean>;
149
- /**
150
- * Inscreve-se para mudanças em tempo real.
151
- * O callback recebe os dados já tipados como T.
152
- */
153
120
  subscribe(callback: (action: "create" | "update" | "delete", data: T) => void): () => void;
154
121
  }
package/dist/database.js CHANGED
@@ -1,4 +1,5 @@
1
- import WebSocket from "ws";
1
+ // Ajuste para WebSocket funcionar no Browser (Vite) e Node
2
+ const WS = typeof window !== "undefined" ? window.WebSocket : global.WebSocket || null;
2
3
  /**
3
4
  * Builder para construção fluente de queries.
4
5
  */
@@ -7,79 +8,46 @@ export class QueryBuilder {
7
8
  this.filter = {};
8
9
  this.collectionRef = collectionRef;
9
10
  }
10
- /**
11
- * Adiciona um filtro de igualdade.
12
- */
13
11
  eq(column, value) {
14
12
  this.filter[column] = value;
15
13
  return this;
16
14
  }
17
- /**
18
- * Adiciona um filtro de desigualdade ($ne).
19
- */
20
15
  neq(column, value) {
21
16
  this.filter[column] = { ...this.filter[column], $ne: value };
22
17
  return this;
23
18
  }
24
- /**
25
- * Adiciona um filtro maior que ($gt).
26
- */
27
19
  gt(column, value) {
28
20
  this.filter[column] = { ...this.filter[column], $gt: value };
29
21
  return this;
30
22
  }
31
- /**
32
- * Adiciona um filtro maior ou igual ($gte).
33
- */
34
23
  gte(column, value) {
35
24
  this.filter[column] = { ...this.filter[column], $gte: value };
36
25
  return this;
37
26
  }
38
- /**
39
- * Adiciona um filtro menor que ($lt).
40
- */
41
27
  lt(column, value) {
42
28
  this.filter[column] = { ...this.filter[column], $lt: value };
43
29
  return this;
44
30
  }
45
- /**
46
- * Adiciona um filtro menor ou igual ($lte).
47
- */
48
31
  lte(column, value) {
49
32
  this.filter[column] = { ...this.filter[column], $lte: value };
50
33
  return this;
51
34
  }
52
- /**
53
- * Adiciona um filtro LIKE ($like).
54
- */
55
35
  like(column, value) {
56
36
  this.filter[column] = { ...this.filter[column], $like: value };
57
37
  return this;
58
38
  }
59
- /**
60
- * Define a ordenação.
61
- */
62
39
  order(column, direction = "ASC") {
63
40
  this.sort = { field: column, order: direction };
64
41
  return this;
65
42
  }
66
- /**
67
- * Define o limite de registros.
68
- */
69
43
  limit(count) {
70
44
  this.limitVal = count;
71
45
  return this;
72
46
  }
73
- /**
74
- * Define o deslocamento (paginação).
75
- */
76
47
  offset(count) {
77
48
  this.offsetVal = count;
78
49
  return this;
79
50
  }
80
- /**
81
- * Executa a query e retorna os resultados.
82
- */
83
51
  async get() {
84
52
  return this.collectionRef.list({
85
53
  filter: this.filter,
@@ -100,14 +68,63 @@ export class DatabaseModule {
100
68
  /**
101
69
  * Seleciona uma coleção de dados.
102
70
  * [PREMIUM] Suporta Generics <T> para tipagem forte.
103
- * * @example client.db.collection<Product>('products')
71
+ * @example client.db.collection<Product>('products')
104
72
  */
105
73
  collection(collectionName) {
106
74
  return new CollectionReference(this.client, this.http, collectionName);
107
75
  }
76
+ /**
77
+ * [NOVO] Método compatível com estilo 'Supabase/Drizzle'.
78
+ * É um alias para .collection() mas retorna interface compatível com o Showcase.
79
+ * @example client.db.from('posts').select('*')
80
+ */
81
+ from(tableName) {
82
+ const ref = this.collection(tableName);
83
+ return {
84
+ // Mapeia .select() para .list()
85
+ select: async (columns = "*") => {
86
+ try {
87
+ // Nota: O parametro 'columns' poderia ser enviado ao backend se suportado
88
+ const data = await ref.list();
89
+ return { data, error: null };
90
+ }
91
+ catch (err) {
92
+ return { data: null, error: err.response?.data || err.message };
93
+ }
94
+ },
95
+ // Mapeia .insert() para .create()
96
+ insert: async (data) => {
97
+ try {
98
+ const res = await ref.create(data);
99
+ return { data: res, error: null };
100
+ }
101
+ catch (err) {
102
+ return { data: null, error: err.response?.data || err.message };
103
+ }
104
+ },
105
+ // Mapeia .update() para .update() (Requer ID no payload ou logica extra)
106
+ update: async (data) => {
107
+ try {
108
+ if (!data.id)
109
+ throw new Error("ID é obrigatório para update via .from()");
110
+ const { id, ...updates } = data;
111
+ const res = await ref.update(id, updates);
112
+ return { data: res, error: null };
113
+ }
114
+ catch (err) {
115
+ return { data: null, error: err.response?.data || err.message };
116
+ }
117
+ },
118
+ // Mapeia .delete()
119
+ delete: async () => {
120
+ // O showcase não passou argumentos no delete, o que é perigoso.
121
+ // Aqui retornamos erro ou implementamos delete por query se suportado.
122
+ return { data: null, error: "Delete via .from() requer implementação de filtros" };
123
+ }
124
+ };
125
+ }
108
126
  /**
109
127
  * Executa múltiplas operações em uma única transação.
110
- * Se uma falhar, todas são revertidas.
111
128
  */
112
129
  async batch(operations) {
113
130
  const { data } = await this.http.post("/db/batch", { operations });
@@ -116,7 +133,6 @@ export class DatabaseModule {
116
133
  }
117
134
  /**
118
135
  * Referência a uma coleção específica.
119
- * O <T> define o formato dos dados (ex: interface User).
120
136
  */
121
137
  export class CollectionReference {
122
138
  constructor(client, http, name) {
@@ -127,93 +143,69 @@ export class CollectionReference {
127
143
  const protocol = client.apiUrl.startsWith("https") ? "wss" : "ws";
128
144
  this.wsUrl = client.apiUrl.replace(/^https?/, protocol);
129
145
  }
130
- /**
131
- * Inicia o QueryBuilder.
132
- * Atalho para .eq()
133
- */
146
+ // --- Atalhos de Query ---
134
147
  eq(column, value) {
135
148
  return new QueryBuilder(this).eq(column, value);
136
149
  }
137
- /**
138
- * Inicia o QueryBuilder.
139
- * Atalho para .gt()
140
- */
141
150
  gt(column, value) {
142
151
  return new QueryBuilder(this).gt(column, value);
143
152
  }
144
- // ... Outros atalhos podem ser adicionados conforme necessidade ...
145
- /**
146
- * Retorna uma nova instância do QueryBuilder.
147
- */
148
153
  query() {
149
154
  return new QueryBuilder(this);
150
155
  }
151
- /**
152
- * Lista documentos da coleção com filtros opcionais.
153
- * @param options Filtros e Ordenação
154
- */
156
+ // --- Operações CRUD ---
155
157
  async list(options) {
156
158
  const params = {};
157
- // Converte os objetos do SDK para strings que o Backend entende
158
159
  if (options?.filter) {
159
160
  params.filter = JSON.stringify(options.filter);
160
161
  }
161
162
  if (options?.sort) {
162
- // Backend espera formato array: ["campo", "DESC"]
163
163
  params.sort = JSON.stringify([options.sort.field, options.sort.order]);
164
164
  }
165
- // TODO: Backend precisa implementar limit/offset na rota GET
166
- // if (options?.limit) params.limit = options.limit;
167
- // if (options?.offset) params.offset = options.offset;
165
+ if (options?.limit)
166
+ params.limit = options.limit;
167
+ if (options?.offset)
168
+ params.offset = options.offset;
168
169
  const { data } = await this.http.get(`/db/${this.collectionName}`, {
169
170
  params,
170
171
  });
171
172
  return data.data;
172
173
  }
173
- /**
174
- * Busca um documento pelo ID.
175
- */
176
174
  async get(id) {
177
175
  const { data } = await this.http.get(`/db/${this.collectionName}/${id}`);
178
176
  return data.data;
179
177
  }
180
- /**
181
- * Cria um novo documento.
182
- * O Partial<T> permite criar sem passar campos gerados (como id, createdAt).
183
- */
184
178
  async create(newData) {
185
179
  const { data } = await this.http.post(`/db/${this.collectionName}`, newData);
186
180
  return data.data;
187
181
  }
188
- /**
189
- * Atualiza um documento existente.
190
- */
191
182
  async update(id, updates) {
192
183
  const { data } = await this.http.put(`/db/${this.collectionName}/${id}`, updates);
193
184
  return data.data;
194
185
  }
195
- /**
196
- * Deleta um documento.
197
- */
198
186
  async delete(id) {
199
187
  await this.http.delete(`/db/${this.collectionName}/${id}`);
200
188
  return true;
201
189
  }
202
- /**
203
- * Inscreve-se para mudanças em tempo real.
204
- * O callback recebe os dados já tipados como T.
205
- */
190
+ // --- Realtime ---
206
191
  subscribe(callback) {
192
+ if (!WS) {
193
+ console.warn("[SDK] WebSocket não disponível neste ambiente.");
194
+ return () => { };
195
+ }
207
196
  const token = this.client.getToken();
208
197
  const projectId = this.client.projectId;
209
198
  if (!token || !projectId) {
210
199
  console.warn("[SDK] Realtime falhou: Token ou ProjectId ausentes.");
211
200
  return () => { };
212
201
  }
202
+ // URL correta de subscribe
213
203
  const url = `${this.wsUrl}/v1/db/subscribe/${this.collectionName}?token=${token}&projectId=${projectId}`;
214
204
  let ws = null;
215
205
  try {
216
- ws = new WebSocket(url);
206
+ ws = new WS(url);
207
+ if (!ws)
208
+ return () => { };
217
209
  ws.onopen = () => {
218
210
  // Conectado
219
211
  };
@@ -229,10 +221,12 @@ export class CollectionReference {
229
221
  // Erro silencioso de parse
230
222
  }
231
223
  };
232
- // Mantém a conexão viva (Heartbeat)
224
+ // Heartbeat
233
225
  const pingInterval = setInterval(() => {
234
- if (ws?.readyState === WebSocket.OPEN)
226
+ // [CORREÇÃO] Adicionada verificação explicita 'ws &&' para evitar erro 'possibly null'
227
+ if (ws && ws.readyState === 1) { // 1 = OPEN
235
228
  ws.send("ping");
229
+ }
236
230
  }, 30000);
237
231
  return () => {
238
232
  clearInterval(pingInterval);