|
7 | 7 | GetAuthTokenOptions, |
8 | 8 | GetAuthTokenResult, |
9 | 9 | OnDisconnect as OnDisconnectBase, QueryOptions, |
| 10 | + Query as QueryBase, |
10 | 11 | User |
11 | 12 | } from "./firebase"; |
12 | 13 | import { |
@@ -1384,6 +1385,191 @@ firebase.update = (path, val) => { |
1384 | 1385 | } |
1385 | 1386 | }); |
1386 | 1387 | }; |
| 1388 | +firebase.webQuery = (path: string): QueryBase => { |
| 1389 | + if (!firebase.initialized) { |
| 1390 | + console.error("Please run firebase.init() before firebase.query()"); |
| 1391 | + throw new Error("FirebaseApp is not initialized. Make sure you run firebase.init() first"); |
| 1392 | + } |
| 1393 | + const dbRef: FIRDatabaseReference = FIRDatabase.database().reference().child(path); |
| 1394 | + return new Query(dbRef, path); |
| 1395 | +}; |
| 1396 | + |
| 1397 | +class Query implements QueryBase { |
| 1398 | + private query: FIRDatabaseQuery; // Keep track of internal query state allowing us to chain filter/range/limit |
| 1399 | + private static eventListenerMap: Map<string, Array<any>> = new Map(); // A map to keep track all all the listeners attached for the specified eventType |
| 1400 | + |
| 1401 | + constructor(private dbRef: FIRDatabaseReference, private path: string) { } |
| 1402 | + |
| 1403 | + on(eventType: string, callback: (a: any, b?: string) => any): Promise<any> { |
| 1404 | + const onValueEvent = result => { |
| 1405 | + if (result.error) { |
| 1406 | + callback(result); // CAREFUL before we were calling result.error! |
| 1407 | + } else { |
| 1408 | + callback({ |
| 1409 | + key: result.key, |
| 1410 | + val: () => result.value, |
| 1411 | + exists: () => !!result.value |
| 1412 | + }); |
| 1413 | + } |
| 1414 | + }; |
| 1415 | + return new Promise((resolve, reject) => { |
| 1416 | + try { |
| 1417 | + if (eventType === "value" || eventType === "child_added" || eventType === "child_changed" |
| 1418 | + || eventType === "child_removed" || eventType === "child_moved") { |
| 1419 | + // This.query may not exist if we call on without any sorts |
| 1420 | + const firDatabaseHandle = this.query ? this.attachEventObserver(this.query, eventType, onValueEvent) : |
| 1421 | + this.attachEventObserver(this.dbRef, eventType, onValueEvent); |
| 1422 | + if (!Query.eventListenerMap.has(eventType)) { |
| 1423 | + Query.eventListenerMap.set(eventType, []); |
| 1424 | + } |
| 1425 | + Query.eventListenerMap.get(eventType).push(firDatabaseHandle); // We need to keep track of the listeners to fully remove them when calling off |
| 1426 | + } else { |
| 1427 | + reject("Invalid eventType. Use one of the following: 'value', 'child_added', 'child_changed', 'child_removed', or 'child_moved'"); |
| 1428 | + return; |
| 1429 | + } |
| 1430 | + resolve(); |
| 1431 | + } catch (ex) { |
| 1432 | + console.log("Error in firebase.addValueEventListener: " + ex); |
| 1433 | + reject(ex); |
| 1434 | + } |
| 1435 | + }); |
| 1436 | + } |
| 1437 | + |
| 1438 | + once(eventType: string): Promise<any> { |
| 1439 | + return new Promise((resolve, reject) => { |
| 1440 | + firebase.getValue(this.path).then(result => { |
| 1441 | + resolve({ |
| 1442 | + key: result.key, |
| 1443 | + val: () => result.value, |
| 1444 | + exists: () => !!result.value |
| 1445 | + }); |
| 1446 | + }); |
| 1447 | + }); |
| 1448 | + } |
| 1449 | + |
| 1450 | + off(eventType?: string): void { |
| 1451 | + // Remove all events if none specified |
| 1452 | + if (!eventType) { |
| 1453 | + Query.eventListenerMap.forEach((value: any[], key: string) => { |
| 1454 | + firebase.removeEventListeners(value, this.path); |
| 1455 | + }); |
| 1456 | + } else { // Remove only the event specified by the user |
| 1457 | + if (Query.eventListenerMap.get(eventType)) { |
| 1458 | + firebase.removeEventListeners(Query.eventListenerMap.get(eventType), this.path); |
| 1459 | + } |
| 1460 | + } |
| 1461 | + } |
| 1462 | + |
| 1463 | + orderByChild(value: string): Query { |
| 1464 | + if (this.query) { |
| 1465 | + throw new Error("You can't combine multiple orderBy calls!"); |
| 1466 | + } |
| 1467 | + this.query = this.dbRef.queryOrderedByChild(value); |
| 1468 | + return this; |
| 1469 | + } |
| 1470 | + |
| 1471 | + orderByKey(): Query { |
| 1472 | + if (this.query) { |
| 1473 | + throw new Error("You can't combine multiple orderBy calls!"); |
| 1474 | + } |
| 1475 | + this.query = this.dbRef.queryOrderedByKey(); |
| 1476 | + return this; |
| 1477 | + } |
| 1478 | + |
| 1479 | + orderByPriority(): Query { |
| 1480 | + if (this.query) { |
| 1481 | + throw new Error("You can't combine multiple orderBy calls!"); |
| 1482 | + } |
| 1483 | + this.query = this.dbRef.queryOrderedByPriority(); |
| 1484 | + return this; |
| 1485 | + } |
| 1486 | + |
| 1487 | + orderByValue(): Query { |
| 1488 | + if (this.query) { |
| 1489 | + throw new Error("You can't combine multiple orderBy calls!"); |
| 1490 | + } |
| 1491 | + this.query = this.dbRef.queryOrderedByValue(); |
| 1492 | + return this; |
| 1493 | + } |
| 1494 | + |
| 1495 | + // Unlike the order-by methods, you can combine multiple limit or range functions. |
| 1496 | + // For example, you can combine the startAt() and endAt() methods to limit the results to a specified range of values. |
| 1497 | + |
| 1498 | + equalTo(value: any, key?: string): Query { |
| 1499 | + if (key) { |
| 1500 | + this.query = this.query.queryEqualToValueChildKey(value, key); |
| 1501 | + } else { |
| 1502 | + this.query = this.query.queryEqualToValue(value); |
| 1503 | + } |
| 1504 | + return this; |
| 1505 | + } |
| 1506 | + |
| 1507 | + startAt(value: any, key?: string): Query { |
| 1508 | + if (key) { |
| 1509 | + this.query = this.query.queryStartingAtValueChildKey(value, key); |
| 1510 | + } else { |
| 1511 | + this.query = this.query.queryStartingAtValue(value); |
| 1512 | + } |
| 1513 | + return this; |
| 1514 | + } |
| 1515 | + |
| 1516 | + endAt(value: any, key?: string): Query { |
| 1517 | + if (key) { |
| 1518 | + this.query = this.query.queryEndingAtValueChildKey(value, key); |
| 1519 | + } else { |
| 1520 | + this.query = this.query.queryEndingAtValue(value); |
| 1521 | + } |
| 1522 | + return this; |
| 1523 | + } |
| 1524 | + |
| 1525 | + limitToFirst(value: number): Query { |
| 1526 | + this.query = this.query.queryLimitedToFirst(value); |
| 1527 | + return this; |
| 1528 | + } |
| 1529 | + |
| 1530 | + limitToLast(value: number): Query { |
| 1531 | + this.query = this.query.queryLimitedToLast(value); |
| 1532 | + return this; |
| 1533 | + } |
| 1534 | + |
| 1535 | + /** |
| 1536 | + * Depending on the eventType, attach listeners at the specified Database location. Follow the WebApi which listens |
| 1537 | + * to specific events (Android is more generic value / child - which includes all events add, change, remove etc). |
| 1538 | + * Similar to firebase._addObserver but I do not want to listen for every event |
| 1539 | + */ |
| 1540 | + private attachEventObserver(dbRef: FIRDatabaseQuery | FIRDatabaseReference, eventType: string, callback): number { |
| 1541 | + let firEventType: FIRDataEventType; |
| 1542 | + switch (eventType) { |
| 1543 | + case "value": |
| 1544 | + firEventType = FIRDataEventType.Value; |
| 1545 | + break; |
| 1546 | + case "child_added": |
| 1547 | + firEventType = FIRDataEventType.ChildAdded; |
| 1548 | + break; |
| 1549 | + case "child_changed": |
| 1550 | + firEventType = FIRDataEventType.ChildChanged; |
| 1551 | + break; |
| 1552 | + case "child_removed": |
| 1553 | + firEventType = FIRDataEventType.ChildRemoved; |
| 1554 | + break; |
| 1555 | + case "child_moved": |
| 1556 | + firEventType = FIRDataEventType.ChildMoved; |
| 1557 | + break; |
| 1558 | + } |
| 1559 | + |
| 1560 | + const listener = dbRef.observeEventTypeWithBlockWithCancelBlock( |
| 1561 | + firEventType, |
| 1562 | + snapshot => { |
| 1563 | + callback(firebase.getCallbackData(eventType, snapshot)); |
| 1564 | + }, |
| 1565 | + firebaseError => { |
| 1566 | + callback({ |
| 1567 | + error: firebaseError.localizedDescription |
| 1568 | + }); |
| 1569 | + }); |
| 1570 | + return listener; |
| 1571 | + } |
| 1572 | +} |
1387 | 1573 |
|
1388 | 1574 | firebase.query = (updateCallback: (data: FBDataSingleEvent) => void, path: string, options: QueryOptions): Promise<any> => { |
1389 | 1575 | return new Promise<any>((resolve, reject) => { |
|
0 commit comments