pyios-firebase 0.2.0__py3-none-any.whl
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.
- pyios_firebase/__init__.py +26 -0
- pyios_firebase/analytics/__init__.py +434 -0
- pyios_firebase/analytics/names.py +251 -0
- pyios_firebase/app_check/__init__.py +483 -0
- pyios_firebase/auth/__init__.py +131 -0
- pyios_firebase/auth/_apple_name_cache.py +97 -0
- pyios_firebase/auth/_common.py +121 -0
- pyios_firebase/auth/_ui.py +21 -0
- pyios_firebase/auth/apple.py +326 -0
- pyios_firebase/auth/auth.py +592 -0
- pyios_firebase/auth/credentials.py +551 -0
- pyios_firebase/auth/errors.py +321 -0
- pyios_firebase/auth/facebook.py +240 -0
- pyios_firebase/auth/google.py +167 -0
- pyios_firebase/auth/multi_factor.py +394 -0
- pyios_firebase/auth/results.py +361 -0
- pyios_firebase/auth/user.py +457 -0
- pyios_firebase/core/__init__.py +32 -0
- pyios_firebase/crashlytics/__init__.py +463 -0
- pyios_firebase/database/__init__.py +62 -0
- pyios_firebase/database/database.py +144 -0
- pyios_firebase/database/errors.py +80 -0
- pyios_firebase/database/query.py +569 -0
- pyios_firebase/database/reference.py +428 -0
- pyios_firebase/database/snapshot.py +218 -0
- pyios_firebase/database/values.py +271 -0
- pyios_firebase/firestore/__init__.py +59 -0
- pyios_firebase/firestore/_limits.py +48 -0
- pyios_firebase/firestore/_query_state.py +228 -0
- pyios_firebase/firestore/_write.py +18 -0
- pyios_firebase/firestore/aggregate.py +137 -0
- pyios_firebase/firestore/collection_reference.py +100 -0
- pyios_firebase/firestore/document_change.py +63 -0
- pyios_firebase/firestore/document_reference.py +229 -0
- pyios_firebase/firestore/document_snapshot.py +102 -0
- pyios_firebase/firestore/enums.py +66 -0
- pyios_firebase/firestore/field_value.py +94 -0
- pyios_firebase/firestore/filter.py +319 -0
- pyios_firebase/firestore/firestore.py +184 -0
- pyios_firebase/firestore/listener_registration.py +55 -0
- pyios_firebase/firestore/query.py +679 -0
- pyios_firebase/firestore/query_snapshot.py +103 -0
- pyios_firebase/firestore/snapshot_metadata.py +36 -0
- pyios_firebase/firestore/transaction.py +130 -0
- pyios_firebase/firestore/write_batch.py +125 -0
- pyios_firebase/functions/__init__.py +329 -0
- pyios_firebase/messaging/__init__.py +37 -0
- pyios_firebase/messaging/messaging.py +435 -0
- pyios_firebase/messaging/notifications.py +292 -0
- pyios_firebase/performance/__init__.py +486 -0
- pyios_firebase/py.typed +0 -0
- pyios_firebase/remote_config/__init__.py +660 -0
- pyios_firebase/storage/__init__.py +41 -0
- pyios_firebase/storage/errors.py +103 -0
- pyios_firebase/storage/metadata.py +202 -0
- pyios_firebase/storage/reference.py +402 -0
- pyios_firebase/storage/storage.py +120 -0
- pyios_firebase/storage/task.py +198 -0
- pyios_firebase/types.py +93 -0
- pyios_firebase/util.py +413 -0
- pyios_firebase-0.2.0.dist-info/METADATA +1969 -0
- pyios_firebase-0.2.0.dist-info/RECORD +65 -0
- pyios_firebase-0.2.0.dist-info/WHEEL +5 -0
- pyios_firebase-0.2.0.dist-info/licenses/LICENSE +21 -0
- pyios_firebase-0.2.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"""Use Firebase from Python on iOS, through Rubicon-ObjC.
|
|
2
|
+
|
|
3
|
+
One subpackage per Firebase product. Import only the ones you use: each one
|
|
4
|
+
needs its Firebase framework in the app.
|
|
5
|
+
|
|
6
|
+
- ``pyios_firebase.core``: ``configure()``
|
|
7
|
+
- ``pyios_firebase.analytics``: Google Analytics
|
|
8
|
+
- ``pyios_firebase.app_check``: App Check providers and tokens
|
|
9
|
+
- ``pyios_firebase.auth``: Firebase Authentication
|
|
10
|
+
- ``pyios_firebase.crashlytics``: Crashlytics
|
|
11
|
+
- ``pyios_firebase.database``: Realtime Database
|
|
12
|
+
- ``pyios_firebase.firestore``: Cloud Firestore
|
|
13
|
+
- ``pyios_firebase.functions``: Cloud Functions (HTTPS callables)
|
|
14
|
+
- ``pyios_firebase.messaging``: Cloud Messaging and notifications
|
|
15
|
+
- ``pyios_firebase.performance``: Performance Monitoring
|
|
16
|
+
- ``pyios_firebase.remote_config``: Remote Config
|
|
17
|
+
- ``pyios_firebase.storage``: Cloud Storage
|
|
18
|
+
- ``pyios_firebase.types``: types for your own annotations
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from .core import configure
|
|
22
|
+
from .types import FirebaseError, QueryDict
|
|
23
|
+
|
|
24
|
+
__all__ = ("FirebaseError", "QueryDict", "__version__", "configure")
|
|
25
|
+
|
|
26
|
+
__version__ = "0.2.0"
|
|
@@ -0,0 +1,434 @@
|
|
|
1
|
+
"""Google Analytics for Firebase: log events and user properties.
|
|
2
|
+
|
|
3
|
+
from pyios_firebase.analytics import Analytics, Event, Param
|
|
4
|
+
|
|
5
|
+
Analytics.log_event(Event.SELECT_CONTENT, {
|
|
6
|
+
Param.CONTENT_TYPE: "image",
|
|
7
|
+
Param.ITEM_ID: "P12453",
|
|
8
|
+
})
|
|
9
|
+
Analytics.log_screen_view("Checkout", "CheckoutScreen")
|
|
10
|
+
Analytics.set_user_property("premium", "plan")
|
|
11
|
+
|
|
12
|
+
Screen views are not logged automatically for Kivy screens (the SDK only sees
|
|
13
|
+
the one ``UIViewController``): call ``log_screen_view`` when a screen opens.
|
|
14
|
+
|
|
15
|
+
Names and values are checked first: a bad one raises ``ValueError`` or
|
|
16
|
+
``TypeError`` instead of being dropped quietly by the SDK.
|
|
17
|
+
|
|
18
|
+
To see events as they are logged, run the app with ``-FIRDebugEnabled`` (in
|
|
19
|
+
Xcode: Product > Scheme > Edit Scheme > Arguments) and open DebugView in the
|
|
20
|
+
Firebase console.
|
|
21
|
+
|
|
22
|
+
Needs the ``FirebaseAnalytics`` framework.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
from __future__ import annotations
|
|
26
|
+
|
|
27
|
+
__all__ = (
|
|
28
|
+
"Analytics",
|
|
29
|
+
"ConsentStatus",
|
|
30
|
+
"ConsentType",
|
|
31
|
+
"Event",
|
|
32
|
+
"Param",
|
|
33
|
+
"UserProperty",
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
import math
|
|
37
|
+
import re
|
|
38
|
+
from collections.abc import Callable, Mapping
|
|
39
|
+
from ctypes import c_int64
|
|
40
|
+
from typing import Any, Final
|
|
41
|
+
|
|
42
|
+
from rubicon.objc import ns_from_py, objc_id
|
|
43
|
+
|
|
44
|
+
from ..types import FirebaseError, ObjCObject
|
|
45
|
+
from ..util import error_from_pointer, objc_class, one_shot_block
|
|
46
|
+
from .names import Event, Param, UserProperty
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class ConsentType:
|
|
50
|
+
"""What a consent covers (``FIRConsentType``)."""
|
|
51
|
+
|
|
52
|
+
AD_STORAGE: Final = "ad_storage"
|
|
53
|
+
"""Storage (such as device identifiers) for advertising."""
|
|
54
|
+
ANALYTICS_STORAGE: Final = "analytics_storage"
|
|
55
|
+
"""Storage (such as app identifiers) for analytics."""
|
|
56
|
+
AD_USER_DATA: Final = "ad_user_data"
|
|
57
|
+
"""Sending user data to Google for advertising."""
|
|
58
|
+
AD_PERSONALIZATION: Final = "ad_personalization"
|
|
59
|
+
"""Personalized advertising (remarketing)."""
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class ConsentStatus:
|
|
63
|
+
"""A consent's status (``FIRConsentStatus``)."""
|
|
64
|
+
|
|
65
|
+
GRANTED: Final = "granted"
|
|
66
|
+
DENIED: Final = "denied"
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
_NAME: Final = re.compile(r"[A-Za-z][A-Za-z0-9_]*\Z")
|
|
70
|
+
_RESERVED_PREFIXES: Final = ("firebase_", "google_", "ga_")
|
|
71
|
+
_RESERVED_EVENTS: Final = frozenset(
|
|
72
|
+
[
|
|
73
|
+
"ad_activeview",
|
|
74
|
+
"ad_click",
|
|
75
|
+
"ad_exposure",
|
|
76
|
+
"ad_query",
|
|
77
|
+
"ad_reward",
|
|
78
|
+
"adunit_exposure",
|
|
79
|
+
"app_clear_data",
|
|
80
|
+
"app_exception",
|
|
81
|
+
"app_remove",
|
|
82
|
+
"app_store_refund",
|
|
83
|
+
"app_store_subscription_cancel",
|
|
84
|
+
"app_store_subscription_convert",
|
|
85
|
+
"app_store_subscription_renew",
|
|
86
|
+
"app_update",
|
|
87
|
+
"app_upgrade",
|
|
88
|
+
"dynamic_link_app_open",
|
|
89
|
+
"dynamic_link_app_update",
|
|
90
|
+
"dynamic_link_first_open",
|
|
91
|
+
"error",
|
|
92
|
+
"firebase_campaign",
|
|
93
|
+
"first_open",
|
|
94
|
+
"first_visit",
|
|
95
|
+
"notification_dismiss",
|
|
96
|
+
"notification_foreground",
|
|
97
|
+
"notification_open",
|
|
98
|
+
"notification_receive",
|
|
99
|
+
"os_update",
|
|
100
|
+
"session_start",
|
|
101
|
+
"session_start_with_rollout",
|
|
102
|
+
"user_engagement",
|
|
103
|
+
]
|
|
104
|
+
)
|
|
105
|
+
_RESERVED_USER_PROPERTIES: Final = frozenset(
|
|
106
|
+
{"first_open_time", "last_deep_link_referrer", "user_id"}
|
|
107
|
+
)
|
|
108
|
+
_MAX_PARAMETERS: Final = 25
|
|
109
|
+
_CONSENT_TYPES: Final = frozenset(
|
|
110
|
+
v for k, v in vars(ConsentType).items() if not k.startswith("_")
|
|
111
|
+
)
|
|
112
|
+
_CONSENT_STATUSES: Final = frozenset({ConsentStatus.GRANTED, ConsentStatus.DENIED})
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def _check_name(name: str, what: str, max_length: int) -> None:
|
|
116
|
+
if not isinstance(name, str):
|
|
117
|
+
raise TypeError(f"{what} must be a str, not {type(name).__name__}")
|
|
118
|
+
if not 0 < len(name) <= max_length or not _NAME.match(name):
|
|
119
|
+
raise ValueError(
|
|
120
|
+
f"invalid {what} {name!r}: 1 to {max_length} letters, digits and "
|
|
121
|
+
"underscores, starting with a letter"
|
|
122
|
+
)
|
|
123
|
+
if name.startswith(_RESERVED_PREFIXES):
|
|
124
|
+
raise ValueError(
|
|
125
|
+
f"invalid {what} {name!r}: the prefixes firebase_, google_ and ga_ "
|
|
126
|
+
"are reserved"
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def _parameter_value(value: Any, path: str) -> Any:
|
|
131
|
+
if isinstance(value, bool):
|
|
132
|
+
raise TypeError(f"{path}: Analytics has no bool parameters; use 1 / 0 or a str")
|
|
133
|
+
if isinstance(value, float) and not math.isfinite(value):
|
|
134
|
+
raise ValueError(f"{path}: cannot log {value!r}")
|
|
135
|
+
if isinstance(value, str | int | float):
|
|
136
|
+
return value
|
|
137
|
+
if isinstance(value, list | tuple): # Param.ITEMS: a list of item dicts
|
|
138
|
+
items = []
|
|
139
|
+
for i, item in enumerate(value):
|
|
140
|
+
if not isinstance(item, Mapping):
|
|
141
|
+
raise TypeError(f"{path}[{i}]: items must be dicts")
|
|
142
|
+
items.append(_parameters(item, f"{path}[{i}]", limit=False))
|
|
143
|
+
return items
|
|
144
|
+
raise TypeError(
|
|
145
|
+
f"{path}: parameter values must be str, int or float (or a list of "
|
|
146
|
+
f"item dicts), not {type(value).__name__}"
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def _parameters(
|
|
151
|
+
parameters: Mapping[str, Any], path: str = "", *, limit: bool = True
|
|
152
|
+
) -> dict[str, Any]:
|
|
153
|
+
if not isinstance(parameters, Mapping):
|
|
154
|
+
raise TypeError(f"parameters must be a dict, not {type(parameters).__name__}")
|
|
155
|
+
if limit and len(parameters) > _MAX_PARAMETERS:
|
|
156
|
+
raise ValueError(
|
|
157
|
+
f"an event can have at most {_MAX_PARAMETERS} parameters, "
|
|
158
|
+
f"not {len(parameters)}"
|
|
159
|
+
)
|
|
160
|
+
result: dict[str, Any] = {}
|
|
161
|
+
for key, value in parameters.items():
|
|
162
|
+
_check_name(key, "parameter name", 40)
|
|
163
|
+
result[key] = _parameter_value(value, f"{path}.{key}" if path else key)
|
|
164
|
+
return result
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def _analytics() -> ObjCObject:
|
|
168
|
+
return objc_class("FIRAnalytics")
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
class Analytics:
|
|
172
|
+
"""Google Analytics. Every method is a static method, as in the SDK::
|
|
173
|
+
|
|
174
|
+
Analytics.log_event("level_up", {"level": 3})
|
|
175
|
+
"""
|
|
176
|
+
|
|
177
|
+
@staticmethod
|
|
178
|
+
def log_event(name: str, parameters: Mapping[str, Any] | None = None) -> None:
|
|
179
|
+
"""Log an event.
|
|
180
|
+
|
|
181
|
+
Args:
|
|
182
|
+
name: the event name: an ``Event`` constant, or your own (1 to 40
|
|
183
|
+
letters, digits and underscores, starting with a letter).
|
|
184
|
+
parameters: up to 25 parameters: ``Param`` constants or your own
|
|
185
|
+
names, to ``str``, ``int`` or ``float`` values. ``Param.ITEMS``
|
|
186
|
+
takes a list of item dicts. String values over 100 characters
|
|
187
|
+
are cut by Analytics.
|
|
188
|
+
|
|
189
|
+
Raises:
|
|
190
|
+
ValueError: an invalid or reserved name, or too many parameters.
|
|
191
|
+
TypeError: a value of another type (``bool`` too: use 1 / 0).
|
|
192
|
+
"""
|
|
193
|
+
_check_name(name, "event name", 40)
|
|
194
|
+
if name in _RESERVED_EVENTS:
|
|
195
|
+
raise ValueError(f"{name!r} is a reserved event name (Analytics drops it)")
|
|
196
|
+
native = None if parameters is None else ns_from_py(_parameters(parameters))
|
|
197
|
+
_analytics().logEventWithName_parameters_(name, native)
|
|
198
|
+
|
|
199
|
+
@staticmethod
|
|
200
|
+
def log_screen_view(
|
|
201
|
+
screen_name: str,
|
|
202
|
+
screen_class: str | None = None,
|
|
203
|
+
parameters: Mapping[str, Any] | None = None,
|
|
204
|
+
) -> None:
|
|
205
|
+
"""Log that a screen was shown (the ``screen_view`` event).
|
|
206
|
+
|
|
207
|
+
Args:
|
|
208
|
+
screen_name: the screen's name, for example ``"Checkout"``.
|
|
209
|
+
screen_class: the screen's class, for example the Kivy ``Screen``
|
|
210
|
+
class name.
|
|
211
|
+
parameters: more parameters for the event.
|
|
212
|
+
|
|
213
|
+
Raises:
|
|
214
|
+
ValueError: an invalid parameter name.
|
|
215
|
+
TypeError: a parameter value of another type.
|
|
216
|
+
"""
|
|
217
|
+
params: dict[str, Any] = {Param.SCREEN_NAME: screen_name}
|
|
218
|
+
if screen_class is not None:
|
|
219
|
+
params[Param.SCREEN_CLASS] = screen_class
|
|
220
|
+
params.update(parameters or {})
|
|
221
|
+
Analytics.log_event(Event.SCREEN_VIEW, params)
|
|
222
|
+
|
|
223
|
+
@staticmethod
|
|
224
|
+
def set_user_property(value: str | None, name: str) -> None:
|
|
225
|
+
"""Set a user property, to filter and compare users in reports. Register
|
|
226
|
+
it in the Firebase console (Custom definitions) to see it there.
|
|
227
|
+
|
|
228
|
+
Args:
|
|
229
|
+
value: the value (up to 36 characters), or ``None`` to remove it.
|
|
230
|
+
name: the property name (1 to 24 letters, digits and underscores,
|
|
231
|
+
starting with a letter).
|
|
232
|
+
|
|
233
|
+
Raises:
|
|
234
|
+
ValueError: an invalid or reserved name, or a value over 36 characters.
|
|
235
|
+
TypeError: ``value`` is not a ``str`` or ``None``.
|
|
236
|
+
"""
|
|
237
|
+
_check_name(name, "user property name", 24)
|
|
238
|
+
if name in _RESERVED_USER_PROPERTIES:
|
|
239
|
+
raise ValueError(f"{name!r} is a reserved user property name")
|
|
240
|
+
if value is not None:
|
|
241
|
+
if not isinstance(value, str):
|
|
242
|
+
raise TypeError(
|
|
243
|
+
f"user property values are str, not {type(value).__name__}"
|
|
244
|
+
)
|
|
245
|
+
if len(value) > 36:
|
|
246
|
+
raise ValueError("a user property value can be at most 36 characters")
|
|
247
|
+
_analytics().setUserPropertyString_forName_(value, name)
|
|
248
|
+
|
|
249
|
+
@staticmethod
|
|
250
|
+
def set_user_id(user_id: str | None) -> None:
|
|
251
|
+
"""Set the user ID (your own ID for the user; never personal data such
|
|
252
|
+
as an email address).
|
|
253
|
+
|
|
254
|
+
Args:
|
|
255
|
+
user_id: up to 256 characters, or ``None`` to remove it.
|
|
256
|
+
|
|
257
|
+
Raises:
|
|
258
|
+
ValueError: an empty ID or one over 256 characters.
|
|
259
|
+
"""
|
|
260
|
+
if user_id is not None and not 0 < len(user_id) <= 256:
|
|
261
|
+
raise ValueError("a user ID must be 1 to 256 characters")
|
|
262
|
+
_analytics().setUserID_(user_id)
|
|
263
|
+
|
|
264
|
+
@staticmethod
|
|
265
|
+
def set_analytics_collection_enabled(enabled: bool) -> None:
|
|
266
|
+
"""Turn collection on or off. The setting is kept across launches. To
|
|
267
|
+
start the app with it off, set ``FIREBASE_ANALYTICS_COLLECTION_ENABLED``
|
|
268
|
+
to ``NO`` in ``Info.plist``.
|
|
269
|
+
|
|
270
|
+
Args:
|
|
271
|
+
enabled: ``False`` to stop collecting.
|
|
272
|
+
"""
|
|
273
|
+
_analytics().setAnalyticsCollectionEnabled_(bool(enabled))
|
|
274
|
+
|
|
275
|
+
@staticmethod
|
|
276
|
+
def set_session_timeout_interval(seconds: float) -> None:
|
|
277
|
+
"""How long the app can be inactive before the session ends (default
|
|
278
|
+
1800 s).
|
|
279
|
+
|
|
280
|
+
Args:
|
|
281
|
+
seconds: the timeout.
|
|
282
|
+
"""
|
|
283
|
+
_analytics().setSessionTimeoutInterval_(float(seconds))
|
|
284
|
+
|
|
285
|
+
@staticmethod
|
|
286
|
+
def session_id(
|
|
287
|
+
callback: Callable[[int | None, FirebaseError | None], None],
|
|
288
|
+
) -> None:
|
|
289
|
+
"""The current session's ID.
|
|
290
|
+
|
|
291
|
+
Args:
|
|
292
|
+
callback: gets ``(session_id, None)``, or ``(None, error)`` (for
|
|
293
|
+
example with collection off). It runs on a background thread.
|
|
294
|
+
"""
|
|
295
|
+
|
|
296
|
+
def done(session: int, error_ptr: Any) -> None:
|
|
297
|
+
error = error_from_pointer(error_ptr)
|
|
298
|
+
callback(None if error is not None else int(session), error)
|
|
299
|
+
|
|
300
|
+
_analytics().sessionIDWithCompletion_(one_shot_block(done, c_int64, objc_id))
|
|
301
|
+
|
|
302
|
+
@staticmethod
|
|
303
|
+
def app_instance_id() -> str | None:
|
|
304
|
+
"""This app install's ID, or ``None`` when ``ANALYTICS_STORAGE``
|
|
305
|
+
consent is denied."""
|
|
306
|
+
value = _analytics().appInstanceID()
|
|
307
|
+
return None if value is None else str(value)
|
|
308
|
+
|
|
309
|
+
@staticmethod
|
|
310
|
+
def reset_analytics_data() -> None:
|
|
311
|
+
"""Clear the data on this device and make a new app instance ID."""
|
|
312
|
+
_analytics().resetAnalyticsData()
|
|
313
|
+
|
|
314
|
+
@staticmethod
|
|
315
|
+
def set_default_event_parameters(parameters: Mapping[str, Any] | None) -> None:
|
|
316
|
+
"""Parameters added to every event logged from now on (kept across
|
|
317
|
+
launches).
|
|
318
|
+
|
|
319
|
+
Args:
|
|
320
|
+
parameters: parameter names to values (a ``None`` value removes
|
|
321
|
+
that default), or ``None`` to remove all defaults.
|
|
322
|
+
|
|
323
|
+
Raises:
|
|
324
|
+
ValueError: an invalid name.
|
|
325
|
+
TypeError: a value of another type.
|
|
326
|
+
"""
|
|
327
|
+
if parameters is None:
|
|
328
|
+
_analytics().setDefaultEventParameters_(None)
|
|
329
|
+
return
|
|
330
|
+
checked = _parameters(
|
|
331
|
+
{k: v for k, v in parameters.items() if v is not None}, limit=False
|
|
332
|
+
)
|
|
333
|
+
native = objc_class("NSMutableDictionary").dictionary()
|
|
334
|
+
for key, value in parameters.items():
|
|
335
|
+
if value is None:
|
|
336
|
+
_check_name(key, "parameter name", 40)
|
|
337
|
+
native.setObject_forKey_(objc_class("NSNull").null(), key)
|
|
338
|
+
else:
|
|
339
|
+
native.setObject_forKey_(ns_from_py(checked[key]), key)
|
|
340
|
+
_analytics().setDefaultEventParameters_(native)
|
|
341
|
+
|
|
342
|
+
@staticmethod
|
|
343
|
+
def set_consent(consent: Mapping[str, str]) -> None:
|
|
344
|
+
"""Set the user's consent (Google consent mode). Types left out keep
|
|
345
|
+
their status.
|
|
346
|
+
|
|
347
|
+
::
|
|
348
|
+
|
|
349
|
+
Analytics.set_consent({
|
|
350
|
+
ConsentType.ANALYTICS_STORAGE: ConsentStatus.GRANTED,
|
|
351
|
+
ConsentType.AD_STORAGE: ConsentStatus.DENIED,
|
|
352
|
+
})
|
|
353
|
+
|
|
354
|
+
Args:
|
|
355
|
+
consent: ``ConsentType`` to ``ConsentStatus``.
|
|
356
|
+
|
|
357
|
+
Raises:
|
|
358
|
+
ValueError: an unknown type or status.
|
|
359
|
+
"""
|
|
360
|
+
for kind, status in consent.items():
|
|
361
|
+
if kind not in _CONSENT_TYPES:
|
|
362
|
+
raise ValueError(f"unknown consent type {kind!r}: use ConsentType")
|
|
363
|
+
if status not in _CONSENT_STATUSES:
|
|
364
|
+
raise ValueError(
|
|
365
|
+
f"unknown consent status {status!r}: use ConsentStatus"
|
|
366
|
+
)
|
|
367
|
+
_analytics().setConsent_(ns_from_py(dict(consent)))
|
|
368
|
+
|
|
369
|
+
@staticmethod
|
|
370
|
+
def initiate_on_device_conversion_measurement(
|
|
371
|
+
*,
|
|
372
|
+
email_address: str | None = None,
|
|
373
|
+
phone_number: str | None = None,
|
|
374
|
+
hashed_email_address: bytes | None = None,
|
|
375
|
+
hashed_phone_number: bytes | None = None,
|
|
376
|
+
) -> None:
|
|
377
|
+
"""Google Ads on-device conversion measurement, from what the user
|
|
378
|
+
consented to share. Give exactly one argument. Needs the
|
|
379
|
+
``GoogleAdsOnDeviceConversion`` package in the app (otherwise it does
|
|
380
|
+
nothing).
|
|
381
|
+
|
|
382
|
+
Args:
|
|
383
|
+
email_address: the email address.
|
|
384
|
+
phone_number: the phone number in E.164 format (``"+15555550100"``).
|
|
385
|
+
hashed_email_address: the SHA-256 of the normalized email address.
|
|
386
|
+
hashed_phone_number: the SHA-256 of the phone number in E.164 format.
|
|
387
|
+
|
|
388
|
+
Raises:
|
|
389
|
+
ValueError: not exactly one argument.
|
|
390
|
+
"""
|
|
391
|
+
given = [
|
|
392
|
+
email_address,
|
|
393
|
+
phone_number,
|
|
394
|
+
hashed_email_address,
|
|
395
|
+
hashed_phone_number,
|
|
396
|
+
]
|
|
397
|
+
if sum(value is not None for value in given) != 1:
|
|
398
|
+
raise ValueError(
|
|
399
|
+
"give exactly one of email_address, phone_number, "
|
|
400
|
+
"hashed_email_address, hashed_phone_number"
|
|
401
|
+
)
|
|
402
|
+
fir = _analytics()
|
|
403
|
+
if email_address is not None:
|
|
404
|
+
fir.initiateOnDeviceConversionMeasurementWithEmailAddress_(email_address)
|
|
405
|
+
elif phone_number is not None:
|
|
406
|
+
fir.initiateOnDeviceConversionMeasurementWithPhoneNumber_(phone_number)
|
|
407
|
+
elif hashed_email_address is not None:
|
|
408
|
+
fir.initiateOnDeviceConversionMeasurementWithHashedEmailAddress_(
|
|
409
|
+
ns_from_py(bytes(hashed_email_address))
|
|
410
|
+
)
|
|
411
|
+
elif hashed_phone_number is not None:
|
|
412
|
+
fir.initiateOnDeviceConversionMeasurementWithHashedPhoneNumber_(
|
|
413
|
+
ns_from_py(bytes(hashed_phone_number))
|
|
414
|
+
)
|
|
415
|
+
|
|
416
|
+
@staticmethod
|
|
417
|
+
def handle_open_url(url: str) -> None:
|
|
418
|
+
"""Tell Analytics the app was opened from a URL (campaign attribution).
|
|
419
|
+
Only needed when ``FirebaseAppDelegateProxyEnabled`` is ``NO``.
|
|
420
|
+
|
|
421
|
+
Args:
|
|
422
|
+
url: the URL the app was opened with.
|
|
423
|
+
"""
|
|
424
|
+
_analytics().handleOpenURL_(objc_class("NSURL").URLWithString_(url))
|
|
425
|
+
|
|
426
|
+
@staticmethod
|
|
427
|
+
def handle_user_activity(user_activity: ObjCObject) -> None:
|
|
428
|
+
"""Tell Analytics about a universal link the app was opened with. Only
|
|
429
|
+
needed when ``FirebaseAppDelegateProxyEnabled`` is ``NO``.
|
|
430
|
+
|
|
431
|
+
Args:
|
|
432
|
+
user_activity: the ``NSUserActivity``.
|
|
433
|
+
"""
|
|
434
|
+
_analytics().handleUserActivity_(user_activity)
|