[{"data":1,"prerenderedAt":1106},["ShallowReactive",2],{"docs-en-\u002Fdocs\u002Fstream\u002Fexamples":3},{"id":4,"title":5,"body":6,"description":1099,"extension":1100,"meta":1101,"navigation":84,"path":1102,"seo":1103,"stem":1104,"__hash__":1105},"docs_en\u002Fdocs\u002Fstream\u002Fexamples.md","Example clients",{"type":7,"value":8,"toc":1094},"minimark",[9,13,17,22,44,48,55,65,471,475,482,488,1087,1090],[10,11,5],"h1",{"id":12},"example-clients",[14,15,16],"p",{},"Two complete clients. Both run against a test server that sends the real\nprotocol frames — they are not merely typed out.",[18,19,21],"h2",{"id":20},"what-they-do-beyond-the-bare-minimum","What they do beyond the bare minimum",[23,24,25,34,41],"ul",{},[26,27,28,29,33],"li",{},"Display the attribution, looked up by source id — the same licence text\ncan repeat in ",[30,31,32],"code",{},"sources[]",", so keying on it would silently drop entries.",[26,35,36,37,40],{},"Watch ",[30,38,39],{},"dropped"," in the heartbeat: losing data does not stay hidden.",[26,42,43],{},"Reconnect after 5–30 s of jitter on close 4012, and not at all on close\n4002\u002F4010\u002F4011. A missing or invalid key never reaches this switch as a\nclose code: it is rejected with HTTP 401 before the WebSocket upgrade, so\nit surfaces as a failed connection attempt instead.",[18,45,47],{"id":46},"python","Python",[14,49,50,51,54],{},"Using the ",[30,52,53],{},"websockets"," library:",[56,57,62],"pre",{"className":58,"code":60,"language":61},[59],"language-text","pip install websockets\nexport PELYR_KEY=pk_…\npython stream.py\n","text",[30,63,60],{"__ignoreMap":64},"",[56,66,69],{"className":67,"code":68,"language":46,"meta":64,"style":64},"language-python shiki shiki-themes github-light github-dark","import asyncio, json, os, random, sys, websockets\n\nURL = os.environ.get(\"PELYR_URL\", \"wss:\u002F\u002Fstream.pelyr.com\u002Fv1\u002Fstream\")\nKEY = os.environ.get(\"PELYR_KEY\")\nif not KEY:\n    sys.exit(\"PELYR_KEY missing\")\n\nABO = {\n    \"type\": \"subscribe\",\n    \"id\": \"ostsee\",\n    \"bbox\": [{\"west\": 18.0, \"south\": 59.0, \"east\": 26.0, \"north\": 66.0}],\n    \"fields\": \"position\",\n}\n\nasync def einmal() -> int:\n    # Survives the welcome frame -- \"position\" below needs it for every\n    # single frame, not just once at connect time.\n    namensnennung: dict[str, str] = {}\n\n    async with websockets.connect(\n        URL,\n        additional_headers={\"Authorization\": f\"Bearer {KEY}\"},\n        ping_interval=None,\n    ) as ws:\n        async for roh in ws:\n            frame = json.loads(roh)\n            typ = frame.get(\"type\")\n\n            if typ == \"welcome\":\n                # Each feed has exactly one id from 100 up; several feeds\n                # share an id only when they deliberately run under one\n                # collective attribution (id 1 = Pelyr). Look up by id, never\n                # by licence text: every data frame carries its id in\n                # \"license\", and whoever wants the attribution of the data\n                # actually received remembers this mapping and looks it up\n                # per frame (see \"position\" below).\n                namensnennung = {q[\"id\"]: q[\"attribution\"] for q in frame[\"sources\"]}\n                for text in namensnennung.values():\n                    print(text)\n                await ws.send(json.dumps(ABO))\n\n            elif typ == \"position\":\n                d = frame[\"data\"]\n                # This frame's own attribution, looked up by license -- not\n                # the list of every possible source from above.\n                print(d[\"mmsi\"], d[\"lat\"], d[\"lon\"], d.get(\"sog\"),\n                      namensnennung.get(frame[\"license\"]))\n\n            elif typ == \"heartbeat\" and frame[\"dropped\"]:\n                print(\"Datenverlust:\", frame[\"dropped\"], file=sys.stderr)\n\n    return 1000\n\nasync def main() -> None:\n    while True:\n        try:\n            code = await einmal()\n        except websockets.ConnectionClosed as ende:\n            code = ende.code\n\n        # Endgueltig -- Wiederverbinden aendert nichts und erzeugt nur Last.\n        if code in (4002, 4010, 4011):\n            sys.exit(f\"beendet, Code {code}\")\n\n        # 4012 heisst Redeploy: mit Jitter wiederkommen, nicht sofort.\n        await asyncio.sleep(random.uniform(5, 30) if code == 4012 else 2.0)\n\nasyncio.run(main())\n",[30,70,71,79,86,92,98,104,110,115,121,127,133,139,145,151,156,162,168,174,180,185,191,197,203,209,215,221,227,233,238,244,250,256,262,268,274,280,286,292,298,304,310,315,321,327,333,339,345,351,356,362,368,373,379,384,390,396,402,408,414,420,425,431,437,443,448,454,460,465],{"__ignoreMap":64},[72,73,76],"span",{"class":74,"line":75},"line",1,[72,77,78],{},"import asyncio, json, os, random, sys, websockets\n",[72,80,82],{"class":74,"line":81},2,[72,83,85],{"emptyLinePlaceholder":84},true,"\n",[72,87,89],{"class":74,"line":88},3,[72,90,91],{},"URL = os.environ.get(\"PELYR_URL\", \"wss:\u002F\u002Fstream.pelyr.com\u002Fv1\u002Fstream\")\n",[72,93,95],{"class":74,"line":94},4,[72,96,97],{},"KEY = os.environ.get(\"PELYR_KEY\")\n",[72,99,101],{"class":74,"line":100},5,[72,102,103],{},"if not KEY:\n",[72,105,107],{"class":74,"line":106},6,[72,108,109],{},"    sys.exit(\"PELYR_KEY missing\")\n",[72,111,113],{"class":74,"line":112},7,[72,114,85],{"emptyLinePlaceholder":84},[72,116,118],{"class":74,"line":117},8,[72,119,120],{},"ABO = {\n",[72,122,124],{"class":74,"line":123},9,[72,125,126],{},"    \"type\": \"subscribe\",\n",[72,128,130],{"class":74,"line":129},10,[72,131,132],{},"    \"id\": \"ostsee\",\n",[72,134,136],{"class":74,"line":135},11,[72,137,138],{},"    \"bbox\": [{\"west\": 18.0, \"south\": 59.0, \"east\": 26.0, \"north\": 66.0}],\n",[72,140,142],{"class":74,"line":141},12,[72,143,144],{},"    \"fields\": \"position\",\n",[72,146,148],{"class":74,"line":147},13,[72,149,150],{},"}\n",[72,152,154],{"class":74,"line":153},14,[72,155,85],{"emptyLinePlaceholder":84},[72,157,159],{"class":74,"line":158},15,[72,160,161],{},"async def einmal() -> int:\n",[72,163,165],{"class":74,"line":164},16,[72,166,167],{},"    # Survives the welcome frame -- \"position\" below needs it for every\n",[72,169,171],{"class":74,"line":170},17,[72,172,173],{},"    # single frame, not just once at connect time.\n",[72,175,177],{"class":74,"line":176},18,[72,178,179],{},"    namensnennung: dict[str, str] = {}\n",[72,181,183],{"class":74,"line":182},19,[72,184,85],{"emptyLinePlaceholder":84},[72,186,188],{"class":74,"line":187},20,[72,189,190],{},"    async with websockets.connect(\n",[72,192,194],{"class":74,"line":193},21,[72,195,196],{},"        URL,\n",[72,198,200],{"class":74,"line":199},22,[72,201,202],{},"        additional_headers={\"Authorization\": f\"Bearer {KEY}\"},\n",[72,204,206],{"class":74,"line":205},23,[72,207,208],{},"        ping_interval=None,\n",[72,210,212],{"class":74,"line":211},24,[72,213,214],{},"    ) as ws:\n",[72,216,218],{"class":74,"line":217},25,[72,219,220],{},"        async for roh in ws:\n",[72,222,224],{"class":74,"line":223},26,[72,225,226],{},"            frame = json.loads(roh)\n",[72,228,230],{"class":74,"line":229},27,[72,231,232],{},"            typ = frame.get(\"type\")\n",[72,234,236],{"class":74,"line":235},28,[72,237,85],{"emptyLinePlaceholder":84},[72,239,241],{"class":74,"line":240},29,[72,242,243],{},"            if typ == \"welcome\":\n",[72,245,247],{"class":74,"line":246},30,[72,248,249],{},"                # Each feed has exactly one id from 100 up; several feeds\n",[72,251,253],{"class":74,"line":252},31,[72,254,255],{},"                # share an id only when they deliberately run under one\n",[72,257,259],{"class":74,"line":258},32,[72,260,261],{},"                # collective attribution (id 1 = Pelyr). Look up by id, never\n",[72,263,265],{"class":74,"line":264},33,[72,266,267],{},"                # by licence text: every data frame carries its id in\n",[72,269,271],{"class":74,"line":270},34,[72,272,273],{},"                # \"license\", and whoever wants the attribution of the data\n",[72,275,277],{"class":74,"line":276},35,[72,278,279],{},"                # actually received remembers this mapping and looks it up\n",[72,281,283],{"class":74,"line":282},36,[72,284,285],{},"                # per frame (see \"position\" below).\n",[72,287,289],{"class":74,"line":288},37,[72,290,291],{},"                namensnennung = {q[\"id\"]: q[\"attribution\"] for q in frame[\"sources\"]}\n",[72,293,295],{"class":74,"line":294},38,[72,296,297],{},"                for text in namensnennung.values():\n",[72,299,301],{"class":74,"line":300},39,[72,302,303],{},"                    print(text)\n",[72,305,307],{"class":74,"line":306},40,[72,308,309],{},"                await ws.send(json.dumps(ABO))\n",[72,311,313],{"class":74,"line":312},41,[72,314,85],{"emptyLinePlaceholder":84},[72,316,318],{"class":74,"line":317},42,[72,319,320],{},"            elif typ == \"position\":\n",[72,322,324],{"class":74,"line":323},43,[72,325,326],{},"                d = frame[\"data\"]\n",[72,328,330],{"class":74,"line":329},44,[72,331,332],{},"                # This frame's own attribution, looked up by license -- not\n",[72,334,336],{"class":74,"line":335},45,[72,337,338],{},"                # the list of every possible source from above.\n",[72,340,342],{"class":74,"line":341},46,[72,343,344],{},"                print(d[\"mmsi\"], d[\"lat\"], d[\"lon\"], d.get(\"sog\"),\n",[72,346,348],{"class":74,"line":347},47,[72,349,350],{},"                      namensnennung.get(frame[\"license\"]))\n",[72,352,354],{"class":74,"line":353},48,[72,355,85],{"emptyLinePlaceholder":84},[72,357,359],{"class":74,"line":358},49,[72,360,361],{},"            elif typ == \"heartbeat\" and frame[\"dropped\"]:\n",[72,363,365],{"class":74,"line":364},50,[72,366,367],{},"                print(\"Datenverlust:\", frame[\"dropped\"], file=sys.stderr)\n",[72,369,371],{"class":74,"line":370},51,[72,372,85],{"emptyLinePlaceholder":84},[72,374,376],{"class":74,"line":375},52,[72,377,378],{},"    return 1000\n",[72,380,382],{"class":74,"line":381},53,[72,383,85],{"emptyLinePlaceholder":84},[72,385,387],{"class":74,"line":386},54,[72,388,389],{},"async def main() -> None:\n",[72,391,393],{"class":74,"line":392},55,[72,394,395],{},"    while True:\n",[72,397,399],{"class":74,"line":398},56,[72,400,401],{},"        try:\n",[72,403,405],{"class":74,"line":404},57,[72,406,407],{},"            code = await einmal()\n",[72,409,411],{"class":74,"line":410},58,[72,412,413],{},"        except websockets.ConnectionClosed as ende:\n",[72,415,417],{"class":74,"line":416},59,[72,418,419],{},"            code = ende.code\n",[72,421,423],{"class":74,"line":422},60,[72,424,85],{"emptyLinePlaceholder":84},[72,426,428],{"class":74,"line":427},61,[72,429,430],{},"        # Endgueltig -- Wiederverbinden aendert nichts und erzeugt nur Last.\n",[72,432,434],{"class":74,"line":433},62,[72,435,436],{},"        if code in (4002, 4010, 4011):\n",[72,438,440],{"class":74,"line":439},63,[72,441,442],{},"            sys.exit(f\"beendet, Code {code}\")\n",[72,444,446],{"class":74,"line":445},64,[72,447,85],{"emptyLinePlaceholder":84},[72,449,451],{"class":74,"line":450},65,[72,452,453],{},"        # 4012 heisst Redeploy: mit Jitter wiederkommen, nicht sofort.\n",[72,455,457],{"class":74,"line":456},66,[72,458,459],{},"        await asyncio.sleep(random.uniform(5, 30) if code == 4012 else 2.0)\n",[72,461,463],{"class":74,"line":462},67,[72,464,85],{"emptyLinePlaceholder":84},[72,466,468],{"class":74,"line":467},68,[72,469,470],{},"asyncio.run(main())\n",[18,472,474],{"id":473},"go","Go",[14,476,477,478,481],{},"Using ",[30,479,480],{},"github.com\u002Fcoder\u002Fwebsocket",":",[56,483,486],{"className":484,"code":485,"language":61},[59],"go get github.com\u002Fcoder\u002Fwebsocket\nexport PELYR_KEY=pk_…\ngo run stream.go\n",[30,487,485],{"__ignoreMap":64},[56,489,492],{"className":490,"code":491,"language":473,"meta":64,"style":64},"language-go shiki shiki-themes github-light github-dark","package main\n\nimport (\n    \"context\"\n    \"encoding\u002Fjson\"\n    \"fmt\"\n    \"maps\"\n    \"math\u002Frand\"\n    \"net\u002Fhttp\"\n    \"os\"\n    \"slices\"\n    \"time\"\n\n    \"github.com\u002Fcoder\u002Fwebsocket\"\n)\n\ntype rahmen struct {\n    Typ     string `json:\"type\"`\n    Sources []struct {\n        ID          string `json:\"id\"`\n        License     string `json:\"license\"`\n        Attribution string `json:\"attribution\"`\n    } `json:\"sources\"`\n    License string `json:\"license\"`\n    Data    struct {\n        MMSI int64    `json:\"mmsi\"`\n        Lat  *float64 `json:\"lat\"`\n        Lon  *float64 `json:\"lon\"`\n    } `json:\"data\"`\n    Dropped uint64 `json:\"dropped\"`\n}\n\nconst abo = `{\"type\":\"subscribe\",\"id\":\"ostsee\",` +\n    `\"bbox\":[{\"west\":18,\"south\":59,\"east\":26,\"north\":66}],\"fields\":\"position\"}`\n\nfunc einmal(ctx context.Context, key string) int {\n    c, _, err := websocket.Dial(ctx, \"wss:\u002F\u002Fstream.pelyr.com\u002Fv1\u002Fstream\",\n        &websocket.DialOptions{\n            HTTPHeader: http.Header{\"Authorization\": {\"Bearer \" + key}},\n        })\n    if err != nil {\n        return 0\n    }\n    defer c.CloseNow()\n    c.SetReadLimit(1 \u003C\u003C 20)\n\n    \u002F\u002F Survives the welcome frame -- \"position\" below needs it for every\n    \u002F\u002F single frame, not just once at connect time.\n    namensnennung := map[string]string{}\n\n    for {\n        _, roh, err := c.Read(ctx)\n        if err != nil {\n            return int(websocket.CloseStatus(err))\n        }\n        var r rahmen\n        if json.Unmarshal(roh, &r) != nil {\n            continue\n        }\n\n        switch r.Typ {\n        case \"welcome\":\n            \u002F\u002F Each feed has exactly one id from 100 up; several feeds\n            \u002F\u002F share an id only when they deliberately run under one\n            \u002F\u002F collective attribution (id 1 = Pelyr). Look up by id, never\n            \u002F\u002F by licence text: every data frame carries its id in\n            \u002F\u002F \"license\", and whoever wants the attribution of the data\n            \u002F\u002F actually received remembers this mapping and looks it up\n            \u002F\u002F per frame (see \"position\" below).\n            namensnennung = map[string]string{}\n            for _, q := range r.Sources {\n                namensnennung[q.ID] = q.Attribution\n            }\n            \u002F\u002F Sorted by id -- map iteration order is randomized in Go.\n            for _, id := range slices.Sorted(maps.Keys(namensnennung)) {\n                fmt.Println(namensnennung[id])\n            }\n            if c.Write(ctx, websocket.MessageText, []byte(abo)) != nil {\n                return 0\n            }\n        case \"position\":\n            var lat, lon float64\n            if r.Data.Lat != nil {\n                lat = *r.Data.Lat\n            }\n            if r.Data.Lon != nil {\n                lon = *r.Data.Lon\n            }\n            \u002F\u002F This frame's own attribution, looked up by license -- not\n            \u002F\u002F the list of every possible source from above.\n            fmt.Println(r.Data.MMSI, lat, lon, namensnennung[r.License])\n        case \"heartbeat\":\n            if r.Dropped > 0 {\n                fmt.Fprintln(os.Stderr, \"Datenverlust:\", r.Dropped)\n            }\n        }\n    }\n}\n\nfunc main() {\n    key := os.Getenv(\"PELYR_KEY\")\n    ctx := context.Background()\n    for {\n        switch code := einmal(ctx, key); code {\n        case 4002, 4010, 4011:\n            fmt.Fprintf(os.Stderr, \"beendet, Code %d\\n\", code)\n            os.Exit(1)\n        case 4012:\n            time.Sleep(time.Duration(5+rand.Intn(25)) * time.Second)\n        default:\n            time.Sleep(2 * time.Second)\n        }\n    }\n}\n",[30,493,494,499,503,508,513,518,523,528,533,538,543,548,553,557,562,567,571,576,581,586,591,596,601,606,611,616,621,626,631,636,641,645,649,654,659,663,668,673,678,683,688,693,698,703,708,713,717,722,727,732,736,741,746,751,756,761,766,771,776,780,784,789,794,799,804,809,814,819,824,830,836,842,848,854,860,866,872,877,883,889,894,900,906,912,918,923,929,935,940,946,952,958,964,970,976,981,986,991,996,1001,1007,1013,1019,1024,1030,1036,1042,1048,1054,1060,1066,1072,1077,1082],{"__ignoreMap":64},[72,495,496],{"class":74,"line":75},[72,497,498],{},"package main\n",[72,500,501],{"class":74,"line":81},[72,502,85],{"emptyLinePlaceholder":84},[72,504,505],{"class":74,"line":88},[72,506,507],{},"import (\n",[72,509,510],{"class":74,"line":94},[72,511,512],{},"    \"context\"\n",[72,514,515],{"class":74,"line":100},[72,516,517],{},"    \"encoding\u002Fjson\"\n",[72,519,520],{"class":74,"line":106},[72,521,522],{},"    \"fmt\"\n",[72,524,525],{"class":74,"line":112},[72,526,527],{},"    \"maps\"\n",[72,529,530],{"class":74,"line":117},[72,531,532],{},"    \"math\u002Frand\"\n",[72,534,535],{"class":74,"line":123},[72,536,537],{},"    \"net\u002Fhttp\"\n",[72,539,540],{"class":74,"line":129},[72,541,542],{},"    \"os\"\n",[72,544,545],{"class":74,"line":135},[72,546,547],{},"    \"slices\"\n",[72,549,550],{"class":74,"line":141},[72,551,552],{},"    \"time\"\n",[72,554,555],{"class":74,"line":147},[72,556,85],{"emptyLinePlaceholder":84},[72,558,559],{"class":74,"line":153},[72,560,561],{},"    \"github.com\u002Fcoder\u002Fwebsocket\"\n",[72,563,564],{"class":74,"line":158},[72,565,566],{},")\n",[72,568,569],{"class":74,"line":164},[72,570,85],{"emptyLinePlaceholder":84},[72,572,573],{"class":74,"line":170},[72,574,575],{},"type rahmen struct {\n",[72,577,578],{"class":74,"line":176},[72,579,580],{},"    Typ     string `json:\"type\"`\n",[72,582,583],{"class":74,"line":182},[72,584,585],{},"    Sources []struct {\n",[72,587,588],{"class":74,"line":187},[72,589,590],{},"        ID          string `json:\"id\"`\n",[72,592,593],{"class":74,"line":193},[72,594,595],{},"        License     string `json:\"license\"`\n",[72,597,598],{"class":74,"line":199},[72,599,600],{},"        Attribution string `json:\"attribution\"`\n",[72,602,603],{"class":74,"line":205},[72,604,605],{},"    } `json:\"sources\"`\n",[72,607,608],{"class":74,"line":211},[72,609,610],{},"    License string `json:\"license\"`\n",[72,612,613],{"class":74,"line":217},[72,614,615],{},"    Data    struct {\n",[72,617,618],{"class":74,"line":223},[72,619,620],{},"        MMSI int64    `json:\"mmsi\"`\n",[72,622,623],{"class":74,"line":229},[72,624,625],{},"        Lat  *float64 `json:\"lat\"`\n",[72,627,628],{"class":74,"line":235},[72,629,630],{},"        Lon  *float64 `json:\"lon\"`\n",[72,632,633],{"class":74,"line":240},[72,634,635],{},"    } `json:\"data\"`\n",[72,637,638],{"class":74,"line":246},[72,639,640],{},"    Dropped uint64 `json:\"dropped\"`\n",[72,642,643],{"class":74,"line":252},[72,644,150],{},[72,646,647],{"class":74,"line":258},[72,648,85],{"emptyLinePlaceholder":84},[72,650,651],{"class":74,"line":264},[72,652,653],{},"const abo = `{\"type\":\"subscribe\",\"id\":\"ostsee\",` +\n",[72,655,656],{"class":74,"line":270},[72,657,658],{},"    `\"bbox\":[{\"west\":18,\"south\":59,\"east\":26,\"north\":66}],\"fields\":\"position\"}`\n",[72,660,661],{"class":74,"line":276},[72,662,85],{"emptyLinePlaceholder":84},[72,664,665],{"class":74,"line":282},[72,666,667],{},"func einmal(ctx context.Context, key string) int {\n",[72,669,670],{"class":74,"line":288},[72,671,672],{},"    c, _, err := websocket.Dial(ctx, \"wss:\u002F\u002Fstream.pelyr.com\u002Fv1\u002Fstream\",\n",[72,674,675],{"class":74,"line":294},[72,676,677],{},"        &websocket.DialOptions{\n",[72,679,680],{"class":74,"line":300},[72,681,682],{},"            HTTPHeader: http.Header{\"Authorization\": {\"Bearer \" + key}},\n",[72,684,685],{"class":74,"line":306},[72,686,687],{},"        })\n",[72,689,690],{"class":74,"line":312},[72,691,692],{},"    if err != nil {\n",[72,694,695],{"class":74,"line":317},[72,696,697],{},"        return 0\n",[72,699,700],{"class":74,"line":323},[72,701,702],{},"    }\n",[72,704,705],{"class":74,"line":329},[72,706,707],{},"    defer c.CloseNow()\n",[72,709,710],{"class":74,"line":335},[72,711,712],{},"    c.SetReadLimit(1 \u003C\u003C 20)\n",[72,714,715],{"class":74,"line":341},[72,716,85],{"emptyLinePlaceholder":84},[72,718,719],{"class":74,"line":347},[72,720,721],{},"    \u002F\u002F Survives the welcome frame -- \"position\" below needs it for every\n",[72,723,724],{"class":74,"line":353},[72,725,726],{},"    \u002F\u002F single frame, not just once at connect time.\n",[72,728,729],{"class":74,"line":358},[72,730,731],{},"    namensnennung := map[string]string{}\n",[72,733,734],{"class":74,"line":364},[72,735,85],{"emptyLinePlaceholder":84},[72,737,738],{"class":74,"line":370},[72,739,740],{},"    for {\n",[72,742,743],{"class":74,"line":375},[72,744,745],{},"        _, roh, err := c.Read(ctx)\n",[72,747,748],{"class":74,"line":381},[72,749,750],{},"        if err != nil {\n",[72,752,753],{"class":74,"line":386},[72,754,755],{},"            return int(websocket.CloseStatus(err))\n",[72,757,758],{"class":74,"line":392},[72,759,760],{},"        }\n",[72,762,763],{"class":74,"line":398},[72,764,765],{},"        var r rahmen\n",[72,767,768],{"class":74,"line":404},[72,769,770],{},"        if json.Unmarshal(roh, &r) != nil {\n",[72,772,773],{"class":74,"line":410},[72,774,775],{},"            continue\n",[72,777,778],{"class":74,"line":416},[72,779,760],{},[72,781,782],{"class":74,"line":422},[72,783,85],{"emptyLinePlaceholder":84},[72,785,786],{"class":74,"line":427},[72,787,788],{},"        switch r.Typ {\n",[72,790,791],{"class":74,"line":433},[72,792,793],{},"        case \"welcome\":\n",[72,795,796],{"class":74,"line":439},[72,797,798],{},"            \u002F\u002F Each feed has exactly one id from 100 up; several feeds\n",[72,800,801],{"class":74,"line":445},[72,802,803],{},"            \u002F\u002F share an id only when they deliberately run under one\n",[72,805,806],{"class":74,"line":450},[72,807,808],{},"            \u002F\u002F collective attribution (id 1 = Pelyr). Look up by id, never\n",[72,810,811],{"class":74,"line":456},[72,812,813],{},"            \u002F\u002F by licence text: every data frame carries its id in\n",[72,815,816],{"class":74,"line":462},[72,817,818],{},"            \u002F\u002F \"license\", and whoever wants the attribution of the data\n",[72,820,821],{"class":74,"line":467},[72,822,823],{},"            \u002F\u002F actually received remembers this mapping and looks it up\n",[72,825,827],{"class":74,"line":826},69,[72,828,829],{},"            \u002F\u002F per frame (see \"position\" below).\n",[72,831,833],{"class":74,"line":832},70,[72,834,835],{},"            namensnennung = map[string]string{}\n",[72,837,839],{"class":74,"line":838},71,[72,840,841],{},"            for _, q := range r.Sources {\n",[72,843,845],{"class":74,"line":844},72,[72,846,847],{},"                namensnennung[q.ID] = q.Attribution\n",[72,849,851],{"class":74,"line":850},73,[72,852,853],{},"            }\n",[72,855,857],{"class":74,"line":856},74,[72,858,859],{},"            \u002F\u002F Sorted by id -- map iteration order is randomized in Go.\n",[72,861,863],{"class":74,"line":862},75,[72,864,865],{},"            for _, id := range slices.Sorted(maps.Keys(namensnennung)) {\n",[72,867,869],{"class":74,"line":868},76,[72,870,871],{},"                fmt.Println(namensnennung[id])\n",[72,873,875],{"class":74,"line":874},77,[72,876,853],{},[72,878,880],{"class":74,"line":879},78,[72,881,882],{},"            if c.Write(ctx, websocket.MessageText, []byte(abo)) != nil {\n",[72,884,886],{"class":74,"line":885},79,[72,887,888],{},"                return 0\n",[72,890,892],{"class":74,"line":891},80,[72,893,853],{},[72,895,897],{"class":74,"line":896},81,[72,898,899],{},"        case \"position\":\n",[72,901,903],{"class":74,"line":902},82,[72,904,905],{},"            var lat, lon float64\n",[72,907,909],{"class":74,"line":908},83,[72,910,911],{},"            if r.Data.Lat != nil {\n",[72,913,915],{"class":74,"line":914},84,[72,916,917],{},"                lat = *r.Data.Lat\n",[72,919,921],{"class":74,"line":920},85,[72,922,853],{},[72,924,926],{"class":74,"line":925},86,[72,927,928],{},"            if r.Data.Lon != nil {\n",[72,930,932],{"class":74,"line":931},87,[72,933,934],{},"                lon = *r.Data.Lon\n",[72,936,938],{"class":74,"line":937},88,[72,939,853],{},[72,941,943],{"class":74,"line":942},89,[72,944,945],{},"            \u002F\u002F This frame's own attribution, looked up by license -- not\n",[72,947,949],{"class":74,"line":948},90,[72,950,951],{},"            \u002F\u002F the list of every possible source from above.\n",[72,953,955],{"class":74,"line":954},91,[72,956,957],{},"            fmt.Println(r.Data.MMSI, lat, lon, namensnennung[r.License])\n",[72,959,961],{"class":74,"line":960},92,[72,962,963],{},"        case \"heartbeat\":\n",[72,965,967],{"class":74,"line":966},93,[72,968,969],{},"            if r.Dropped > 0 {\n",[72,971,973],{"class":74,"line":972},94,[72,974,975],{},"                fmt.Fprintln(os.Stderr, \"Datenverlust:\", r.Dropped)\n",[72,977,979],{"class":74,"line":978},95,[72,980,853],{},[72,982,984],{"class":74,"line":983},96,[72,985,760],{},[72,987,989],{"class":74,"line":988},97,[72,990,702],{},[72,992,994],{"class":74,"line":993},98,[72,995,150],{},[72,997,999],{"class":74,"line":998},99,[72,1000,85],{"emptyLinePlaceholder":84},[72,1002,1004],{"class":74,"line":1003},100,[72,1005,1006],{},"func main() {\n",[72,1008,1010],{"class":74,"line":1009},101,[72,1011,1012],{},"    key := os.Getenv(\"PELYR_KEY\")\n",[72,1014,1016],{"class":74,"line":1015},102,[72,1017,1018],{},"    ctx := context.Background()\n",[72,1020,1022],{"class":74,"line":1021},103,[72,1023,740],{},[72,1025,1027],{"class":74,"line":1026},104,[72,1028,1029],{},"        switch code := einmal(ctx, key); code {\n",[72,1031,1033],{"class":74,"line":1032},105,[72,1034,1035],{},"        case 4002, 4010, 4011:\n",[72,1037,1039],{"class":74,"line":1038},106,[72,1040,1041],{},"            fmt.Fprintf(os.Stderr, \"beendet, Code %d\\n\", code)\n",[72,1043,1045],{"class":74,"line":1044},107,[72,1046,1047],{},"            os.Exit(1)\n",[72,1049,1051],{"class":74,"line":1050},108,[72,1052,1053],{},"        case 4012:\n",[72,1055,1057],{"class":74,"line":1056},109,[72,1058,1059],{},"            time.Sleep(time.Duration(5+rand.Intn(25)) * time.Second)\n",[72,1061,1063],{"class":74,"line":1062},110,[72,1064,1065],{},"        default:\n",[72,1067,1069],{"class":74,"line":1068},111,[72,1070,1071],{},"            time.Sleep(2 * time.Second)\n",[72,1073,1075],{"class":74,"line":1074},112,[72,1076,760],{},[72,1078,1080],{"class":74,"line":1079},113,[72,1081,702],{},[72,1083,1085],{"class":74,"line":1084},114,[72,1086,150],{},[14,1088,1089],{},"The key comes from the environment, not from the source.",[1091,1092,1093],"style",{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":64,"searchDepth":81,"depth":81,"links":1095},[1096,1097,1098],{"id":20,"depth":81,"text":21},{"id":46,"depth":81,"text":47},{"id":473,"depth":81,"text":474},"Complete clients in Python and Go against the live stream.","md",{},"\u002Fdocs\u002Fstream\u002Fexamples",{"title":5,"description":1099},"docs\u002Fstream\u002Fexamples","dQA2oLoPsuflyRvyFFlM3RxMmFjC8EeILvOxUxB1VIw",1789034411683]