{"id":15063790,"url":"https://github.com/gagangoku/streamlit-ws-localstorage","last_synced_at":"2025-03-31T11:01:27.791Z","repository":{"id":57750469,"uuid":"525631217","full_name":"gagangoku/streamlit-ws-localstorage","owner":"gagangoku","description":"A simple synchronous way of accessing localStorage from your Streamlit app.","archived":false,"fork":false,"pushed_at":"2025-02-22T09:09:41.000Z","size":48,"stargazers_count":54,"open_issues_count":0,"forks_count":6,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-03-24T10:12:12.291Z","etag":null,"topics":["cookies","localstorage","python","streamlit","websocket"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"agpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/gagangoku.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-08-17T03:53:00.000Z","updated_at":"2025-02-22T09:09:45.000Z","dependencies_parsed_at":"2024-09-25T00:07:20.385Z","dependency_job_id":"b2138f65-4906-4a2f-9b7b-71b5cfe032e5","html_url":"https://github.com/gagangoku/streamlit-ws-localstorage","commit_stats":{"total_commits":21,"total_committers":2,"mean_commits":10.5,"dds":"0.33333333333333337","last_synced_commit":"3ce6b06a7469e6dd6ffa5329bb5785089e3a1ccc"},"previous_names":[],"tags_count":6,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gagangoku%2Fstreamlit-ws-localstorage","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gagangoku%2Fstreamlit-ws-localstorage/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gagangoku%2Fstreamlit-ws-localstorage/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gagangoku%2Fstreamlit-ws-localstorage/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/gagangoku","download_url":"https://codeload.github.com/gagangoku/streamlit-ws-localstorage/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":246457967,"owners_count":20780676,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cookies","localstorage","python","streamlit","websocket"],"created_at":"2024-09-25T00:07:14.345Z","updated_at":"2025-03-31T11:01:27.762Z","avatar_url":"https://github.com/gagangoku.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# streamlit-ws-localstorage\nFinally a simple synchronous way of accessing localStorage from your Streamlit app.\n\n[![Downloads](https://static.pepy.tech/personalized-badge/streamlit-ws-localstorage?period=total\u0026units=international_system\u0026left_color=black\u0026right_color=orange\u0026left_text=Downloads)](https://pepy.tech/project/streamlit-ws-localstorage)\n\n# Why\nI wanted to include a login with Google / Linkedin button on my Streamlit app. Tried many modules but didn’t get the experience I wanted.\n\nI tried using extra-streamlit-components but found it too complex, because of the way Streamlit components works.\nSince the communication between the browser and streamlit app is async, your app is run multiple times, and nested if else blocks may not work properly. A simple call to get all cookies causes the app to be run 4 times in total. And the first call to get all cookies returns an empty dictionary which needs to be handled in the code.\n\nLooking at the complexity, I thought it would be easier to write a synchronous commmunication.\n\n\n# How to run\nUsing it is fairly simple:\n\n\n```python\nimport streamlit as st\nimport uuid\nfrom streamlit_ws_localstorage import injectWebsocketCode\n\n# You can use my server for now, or run your own websocket server and auth redirect servers from here:\n# https://github.com/gagangoku/streamlit-ws-localstorage/tree/main/streamlit_ws_localstorage\nHOST_PORT = 'wsauthserver.supergroup.ai'\n\n# Main call to the api, returns a communication object\nconn = injectWebsocketCode(hostPort=HOST_PORT, uid=str(uuid.uuid1()))\n\nst.write('setting into localStorage')\nret = conn.setLocalStorageVal(key='k1', val='v1')\nst.write('ret: ' + ret)\n\nst.write('getting from localStorage')\nret = conn.getLocalStorageVal(key='k1')\nst.write('ret: ' + ret)\n```\n\nYou can use the ```wsauthserver.supergroup.ai``` websocket relay server for testing. Alternately run your websocket relay server from the code in ```websocket-server/ws_server.py```\n\n\n# Oauth\nI’ve also built a Linkedin Oauth example using the same:\n\n```python\nimport uuid\n\nimport streamlit as st\nimport streamlit.components.v1 as components\nfrom linkedin_v2 import linkedin\nfrom streamlit_ws_localstorage import injectWebsocketCode\nfrom streamlit_ws_localstorage.auth_redirect_server.auth_util import loginWithOAuthComponent\n\n\nUSER_PROFILE_PIC_KEY = '_user.profilePic'\nUSER_EMAIL_ADDRESS_KEY = '_user.emailAddress'\nAUTH_CODE_KEY = '_linkedin.authCode'\n\n# You can use my server for now, or run your own websocket server and auth redirect servers from here:\n# https://github.com/gagangoku/streamlit-ws-localstorage/tree/main/streamlit_ws_localstorage\nHOST_PORT = 'wsauthserver.supergroup.ai'\n\n# Use this to avoid handling redirects in your app.\n# Don't forget to register this as a redirect url in your linkedin app.\nREDIRECT_URL = 'https://authredirect.supergroup.in/redirect'\n\n\ndef getLinkedinOauth(uid):\n    CLIENT_KEY = '\u003cyour client key\u003e'\n    CLIENT_SECRET = '\u003cyour client secret\u003e'\n    authentication = linkedin.LinkedInAuthentication(CLIENT_KEY, CLIENT_SECRET, REDIRECT_URL,\n                                                     ['r_liteprofile', 'r_emailaddress'])\n\n    # Set the state variable as the uid so the browser can receive the auth code corresponding to this request\n    authentication.state = uid\n    return authentication\n\n\ndef getLinkedinUserProfile(code):\n    authObj = getLinkedinOauth('')\n    authObj.authorization_code = code\n    authToken = authObj.get_access_token()\n\n    application = linkedin.LinkedInApplication(token=authToken)\n    profile = application.get_profile()\n    print('profile: ', profile)\n    firstName, lastName, displayImage, id = profile['localizedFirstName'], profile['localizedLastName'], profile['profilePicture']['displayImage'], profile['id']\n\n    # Get profile picture\n    response = application.make_request('GET', 'https://api.linkedin.com/v2/me?projection=(profilePicture(displayImage~:playableStreams))')\n    json_response = response.json()\n    print ('profile pic json_response: ', json_response)\n    profilePic = json_response['profilePicture']['displayImage~']['elements'][2]['identifiers'][0]['identifier']\n\n    # Get email\n    response = application.make_request('GET', 'https://api.linkedin.com/v2/emailAddress?q=members\u0026projection=(elements*(handle~))')\n    json_response = response.json()\n    print ('email json_response: ', json_response)\n    emailAddress = json_response['elements'][0]['handle~']['emailAddress']\n\n    rsp = (firstName, lastName, displayImage, id, profilePic, emailAddress)\n    print ('getLinkedinUserProfile: ', rsp)\n    return rsp\n\n\ndef logoutFn():\n    conn = injectWebsocketCode(hostPort=HOST_PORT, uid=str(uuid.uuid1()))\n    conn.setLocalStorageVal(key=USER_PROFILE_PIC_KEY, val='')\n    conn.setLocalStorageVal(key=USER_EMAIL_ADDRESS_KEY, val='')\n    conn.setLocalStorageVal(key=AUTH_CODE_KEY, val='')\n    st.write('Logged out, reloading the page')\n    code = \"\"\"\u003cscript\u003esetTimeout(() =\u003e window.parent.location.reload(), 1000)\u003c/script\u003e\"\"\"\n    components.html(code, height=100)\n\n\ndef main():\n    st.title('Login demo')\n\n    uid = str(uuid.uuid1())\n    conn = injectWebsocketCode(hostPort=HOST_PORT, uid=uid)\n    print('conn: ', conn)\n\n    emailAddress = conn.getLocalStorageVal(key=USER_EMAIL_ADDRESS_KEY)\n    authCode = conn.getLocalStorageVal(key=AUTH_CODE_KEY)\n    if authCode and not emailAddress:\n        (firstName, lastName, displayImage, id, profilePic, emailAddress) = getLinkedinUserProfile(authCode)\n        conn.setLocalStorageVal(key=USER_PROFILE_PIC_KEY, val=profilePic)\n        conn.setLocalStorageVal(key=USER_EMAIL_ADDRESS_KEY, val=emailAddress)\n\n    profilePic = conn.getLocalStorageVal(key=USER_PROFILE_PIC_KEY)\n    emailAddress = conn.getLocalStorageVal(key=USER_EMAIL_ADDRESS_KEY)\n    if emailAddress:\n        st.write('Welcome ' + emailAddress)\n        st.image(profilePic, width=200)\n        st.button('Logout', on_click=logoutFn)\n    else:\n        uid = str(uuid.uuid1())\n        authObj = getLinkedinOauth(uid)\n        st.markdown('\u003ca href=\"{}\" target=\"_blank\"\u003eLogin with LinkedIn\u003c/a\u003e'.format(authObj.authorization_url), unsafe_allow_html=True)\n        loginWithOAuthComponent(HOST_PORT, uid, AUTH_CODE_KEY, reloadInSecs=6, height=40)\n\n\nmain()\n```\n\nThis shows a `Login with LinkedIn` link which opens the Linkedin auth page in a new tab, which upon success redirects to https://authredirect.supergroup.in/redirect. You can use this to avoid handling page redirects in your streamlit app.\n\nIf there’s interest, I can make simple button components for Linkedin, Google, Github to simplify this even further.\n\n\n\n# Video demo of localstorage\nhttps://user-images.githubusercontent.com/544881/185042658-43dd3c58-a086-4457-a042-9d4d895e34ba.mp4\n\n# Video demo of oauth login with linkedin\nhttps://user-images.githubusercontent.com/544881/185403846-959c6c2c-493d-41e7-bcc0-665721f3cb13.mp4\n\n\n# Installation\n\nInstallation: `pip install streamlit-ws-localstorage`\n\nRepository: [GitHub - gagangoku/streamlit-ws-localstorage: A simple synchronous way of accessing localStorage from your Streamlit app. ](https://github.com/gagangoku/streamlit-ws-localstorage)\n\nOn pypi: [streamlit-ws-localstorage · PyPI](https://pypi.org/project/streamlit-ws-localstorage/)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgagangoku%2Fstreamlit-ws-localstorage","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgagangoku%2Fstreamlit-ws-localstorage","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgagangoku%2Fstreamlit-ws-localstorage/lists"}