Skip to content

Commit eadae62

Browse files
authored
feat: Add new cookie format (#61)
* feat: Add new cookie format * Fix new format test
1 parent b3195a3 commit eadae62

File tree

2 files changed

+69
-4
lines changed

2 files changed

+69
-4
lines changed

Diff for: src/amplitude_experiment/cookie.py

+42-4
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
import json
2+
import logging
3+
import urllib.parse
4+
15
from .user import User
26
import base64
37

@@ -6,29 +10,52 @@ class AmplitudeCookie:
610
"""This class provides utility functions for parsing and handling identity from Amplitude cookies."""
711

812
@staticmethod
9-
def cookie_name(api_key: str) -> str:
13+
def cookie_name(api_key: str, new_format: bool = False) -> str:
1014
"""
1115
Get the cookie name that Amplitude sets for the provided
1216
Parameters:
1317
api_key (str): The Amplitude API Key
18+
new_format (bool): True if cookie is in Browser SDK 2.0 format
1419
1520
Returns:
1621
The cookie name that Amplitude sets for the provided Amplitude API Key
1722
"""
1823
if not api_key:
1924
raise ValueError("Invalid Amplitude API Key")
25+
26+
if new_format:
27+
if len(api_key) < 10:
28+
raise ValueError("Invalid Amplitude API Key")
29+
return f"AMP_{api_key[0:10]}"
30+
31+
if len(api_key) < 6:
32+
raise ValueError("Invalid Amplitude API Key")
2033
return f"amp_{api_key[0:6]}"
2134

2235
@staticmethod
23-
def parse(amplitude_cookie: str) -> User:
36+
def parse(amplitude_cookie: str, new_format: bool = False) -> User:
2437
"""
2538
Parse a cookie string and returns user
2639
Parameters:
2740
amplitude_cookie (str): A string from the amplitude cookie
41+
new_format: True if cookie is in Browser SDK 2.0 format
2842
2943
Returns:
3044
Experiment User context containing a device_id and user_id (if available)
3145
"""
46+
47+
if new_format:
48+
decoding = base64.b64decode(amplitude_cookie).decode("utf-8")
49+
try:
50+
user_session = json.loads(urllib.parse.unquote_plus(decoding))
51+
if "userId" not in user_session:
52+
return User(user_id=None, device_id=user_session["deviceId"])
53+
return User(user_id=user_session["userId"], device_id=user_session["deviceId"])
54+
except Exception as e:
55+
logger = logging.getLogger("Amplitude")
56+
logger.error("Error parsing the Amplitude cookie: " + str(e))
57+
return User()
58+
3259
values = amplitude_cookie.split('.')
3360
user_id = None
3461
if values[1]:
@@ -39,13 +66,24 @@ def parse(amplitude_cookie: str) -> User:
3966
return User(user_id=user_id, device_id=values[0])
4067

4168
@staticmethod
42-
def generate(device_id: str) -> str:
69+
def generate(device_id: str, new_format: bool = False) -> str:
4370
"""
4471
Generates a cookie string to set for the Amplitude Javascript SDK
4572
Parameters:
4673
device_id (str): A device id to set
74+
new_format: True if cookie is in Browser SDK 2.0 format
4775
4876
Returns:
4977
A cookie string to set for the Amplitude Javascript SDK to read
5078
"""
51-
return f"{device_id}.........."
79+
if not new_format:
80+
return f"{device_id}.........."
81+
82+
user_session_hash = {
83+
"deviceId": device_id
84+
}
85+
json_data = json.dumps(user_session_hash)
86+
encoded_json = urllib.parse.quote(json_data)
87+
base64_encoded = base64.b64encode(bytearray(encoded_json, "utf-8")).decode("utf-8")
88+
89+
return base64_encoded

Diff for: tests/cookie_test.py

+27
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,33 @@ def test_parse_cookie_with_device_id_and_utf_user_id(self):
3030
def test_generate(self):
3131
self.assertEqual(AmplitudeCookie.generate('deviceId'), 'deviceId..........')
3232

33+
def test_new_format_valid_api_key_return_cookie_name(self):
34+
self.assertEqual(AmplitudeCookie.cookie_name('12345678901', new_format=True), 'AMP_1234567890')
35+
36+
def test_new_format_invalid_api_key_raise_error(self):
37+
self.assertRaises(ValueError, AmplitudeCookie.cookie_name, '12345678', new_format=True)
38+
39+
def test_new_format_parse_cookie_with_device_id_only(self):
40+
user = AmplitudeCookie.parse('JTdCJTIyZGV2aWNlSWQlMjIlM0ElMjJzb21lRGV2aWNlSWQlMjIlN0Q=', new_format=True)
41+
self.assertIsNotNone(user)
42+
self.assertEqual(user.device_id, 'someDeviceId')
43+
self.assertIsNone(user.user_id)
44+
45+
def test_new_format_parse_cookie_with_device_id_and_user_id(self):
46+
user = AmplitudeCookie.parse('JTdCJTIyZGV2aWNlSWQlMjIlM0ElMjJzb21lRGV2aWNlSWQlMjIlMkMlMjJ1c2VySWQlMjIlM0ElMjJleGFtcGxlJTQwYW1wbGl0dWRlLmNvbSUyMiU3RA==', new_format=True)
47+
self.assertIsNotNone(user)
48+
self.assertEqual(user.device_id, 'someDeviceId')
49+
self.assertEqual(user.user_id, '[email protected]')
50+
51+
def test_new_format_parse_cookie_with_device_id_and_utf_user_id(self):
52+
user = AmplitudeCookie.parse('JTdCJTIyZGV2aWNlSWQlMjIlM0ElMjJzb21lRGV2aWNlSWQlMjIlMkMlMjJ1c2VySWQlMjIlM0ElMjJjJUMzJUI3JTNFJTIyJTdE', new_format=True)
53+
self.assertIsNotNone(user)
54+
self.assertEqual(user.device_id, 'someDeviceId')
55+
self.assertEqual(user.user_id, 'c÷>')
56+
57+
def test_new_format_generate(self):
58+
self.assertEqual(AmplitudeCookie.generate('someDeviceId', new_format=True), 'JTdCJTIyZGV2aWNlSWQlMjIlM0ElMjAlMjJzb21lRGV2aWNlSWQlMjIlN0Q=')
59+
3360

3461
if __name__ == '__main__':
3562
unittest.main()

0 commit comments

Comments
 (0)